@adlc/core

中文文档
1.13.0发布于 2026/11/06MITTypeScriptES Module构建来源已验证近 30 天下载 9357

Shared core for ADLC tools: LLM providers, git helpers, CLI conventions, ledgers, tickets, mutations.

原始使用文档
文档目录10 节
  1. llm
  2. git
  3. cli
  4. ledger (persistence at .adlc/ )
  5. tickets (canonical logical store)
  6. mutate ( @adlc/core/mutate )
  7. syntax
  8. text (shared payload-capping helpers)
  9. diff-mask (removed-line credential masking)
  10. test-kit ( @adlc/core/test-kit )

@adlc/core

Shared library for all ADLC tools. Tools import from it and never keep a private copy of what it exports. When a second package needs a helper, promote it here: add a test in packages/core/test/, a declaration in index.d.ts, and no new runtime dependency. A defect in a core primitive is fixed here, with a regression test. The full rule is CONVENTIONS.md hard rule 2.

Import surface (@adlc/core and its subpath exports):

import { complete, fan, fanProviders, extractJson, detectProvider, resolveModel, PROVIDER_NAMES } from '@adlc/core';
import { git, gitDiff, changedFiles, isDirty, isGitRepo, coChange, pairKey, churn } from '@adlc/core';
import { parseArgs, pass, gateFail, opError, printJson, readStdin, promptOnly } from '@adlc/core';
import { ADLC_DIR, appendEntry, readEntries, ledgerPath, sha256, hashFiles } from '@adlc/core';
import { TICKETS_PATH, loadTickets, validateTicket, topoSort, computeFloat, globMatch, inScope, scopesOverlap } from '@adlc/core';
import { mutate } from '@adlc/core'; // mutate.generateMutants / applyMutant / changedLinesFromDiff / OPERATORS
import { syntaxLanguage, resolveTypeScript, checkSyntaxDetail, checkSyntax, syntaxCheckProblem } from '@adlc/core';

// Subpath exports (`package.json` "exports"):
// @adlc/core/llm | @adlc/core/git | @adlc/core/cli | @adlc/core/ledger
// @adlc/core/revision | @adlc/core/tickets | @adlc/core/mutate | @adlc/core/test-kit

llm

  • detectProvider(env?, forceProvider?) → { name, apiKey, models } | null. Order: anthropic, openai, gemini, agy. Force with ADLC_PROVIDER (env) or the forceProvider arg (per-invocation, e.g. a CLI --provider flag — takes precedence over ADLC_PROVIDER).
  • PROVIDER_NAMES → ['anthropic', 'openai', 'gemini', 'agy'], for CLI validation of --provider/--providers.
  • resolveModel(provider, { tier, model }, env?) → model id. Tiers: cheap | mid | frontier. Override via ADLC_MODEL_CHEAP/MID/FRONTIER.
  • complete({ tier, model, system, prompt, maxTokens, provider?, onUsage?, cacheable? }, env?) → Promise<string>. Throws if no provider (tools must catch and offer --prompt-only). provider is a per-invocation override, additive only — omit it and single-provider auto-detect remains the default (cost/latency per ADR-0007). onUsage?: (usage) => void is an optional side-channel fired with { inputTokens, outputTokens, cachedTokens, provider, model, tier } when the provider reports usage (Gemini outputTokens includes thinking tokens, thoughtsTokenCount, as Anthropic and OpenAI already do; agy never does — there's nothing real to report). cacheable?: boolean (default false) marks the system/prompt as a prompt-cache candidate on providers that support explicit cache breakpoints (currently anthropic only) — only set it when the SAME {system, prompt} will be sent again; a cache write costs more than a plain input token and only pays off on a later read.
  • fan(opts, n, env?) → Promise<[{ ok, value | error }]> — n independent stateless completions of the SAME (auto-detected or opts.provider-forced) provider. opts.cacheable defaults to true here (unlike complete()) — fan() exists specifically to resend one prompt N times, exactly the shape a cache write pays for itself on. Pass cacheable: false to opt out. opts.onUsage, if given, fires once per resample.
  • fanProviders(opts, providerNames, env?) → Promise<[{ ok, value | error, provider }]> — one completion per DISTINCT named provider (e.g. ['anthropic', 'openai', 'gemini']), instead of N resamples of one provider. A named provider missing its API key surfaces as { ok: false } for that entry only. Does NOT default cacheable to true — each provider gets exactly one call, so there's no repeat to amortize a cache write against.
  • extractJson(text) → parsed JSON value from messy model output. Throws if none.

git

  • git(args[], opts?) → stdout string (execFileSync; throws on non-zero).
  • isGitRepo(cwd?), gitDiff(base?, cwd?), changedFiles(base?, cwd?), isDirty(cwd?).
  • coChange(limit?, cwd?) → { pairCounts: {'a b': n}, fileCounts } (logical coupling; commits touching >50 files skipped). Pair keys via pairKey(a, b) (sorted).
  • churn(limit?, cwd?) → { file: commitCount }.

cli

  • parseArgs(config) — node:util parseArgs with allowPositionals: true default.
  • pass(msg?) exit 0 · gateFail(msg, details?) exit 2 · opError(msg) exit 1.
  • printJson(obj), readStdin(), promptOnly(promptOrArray) (print prompt(s), exit 0).

Exit codes are the contract: 0 = gate passes, 1 = operational error, 2 = gate fails.

ledger (persistence at .adlc/)

  • appendEntry(name, entry, dir?) → appends to .adlc/<name>.jsonl.
  • appendEntries(name, entriesOrFactory, dir?) → appends a durable batch under one owner-token lock; factories receive the byte-exact locked ledger tail for safe sequence and hash-chain allocation.
  • readEntries(name, dir?) → { entries, skipped } — malformed lines reported, never swallowed.
  • sha256(content), hashFiles(paths) → { path: hash | null }.

Well-known ledger names: manifest (gate-manifest entries), findings (prosecution findings: { ts, tool, file, line, category, severity, desc, verdict }).

tickets (canonical logical store)

Schema (see lib/tickets.mjs header): { id, title, body, scope[], rails[], edges[{to, contract}], duration, category, budget }.

  • loadTickets(path?) → { tickets, errors } (reads legacy JSON or the sibling sharded store and validates ids, duplicate ids, unknown edges).
  • validateTicket(t) → errors[].
  • topoSort(tickets) → { order, cycle | null }. Edges mean "completes before edge.to".
  • computeFloat(tickets) → { floats: {id: n}, criticalPath: [ids], makespan } (CPM; duration default 1) or { error } on cycle.
  • globMatch(pattern, path) (*, **), inScope(ticket, path), scopesOverlap(a, b) (conservative).

mutate (@adlc/core/mutate)

  • mutate.OPERATORS — invert-comparison, bool-flip, null-return, off-by-one, logic-swap, negate-guard-subclause, array-literal-shrink, ternary-swap.
  • mutate.generateMutants(content, { targetLines?, maxMutants? }) → [{ line, operator, original, mutated }]. Skips pure comment/import/console lines and unclosed block-comment openers (/* ...), while stripping a leading closed block-comment prefix (/* ... */ code) so executable code after the comment is still mutated (#372). off-by-one masks unobservable tuning constants (e.g. TIMEOUT_MS, maxBufferBytes, or negative duration sentinels) while still mutating countable boundaries (MAX_RETRIES, retryLimit) and zero sentinels (0 -> 1).
  • mutate.applyMutant(content, mutant) → mutated content (throws if line content drifted).
  • mutate.changedLinesFromDiff(diffText) → { file: Set<newSideLineNo> }. Hunk body lines are classified by their first character only, so a source line that itself begins with -- or ++ is still a deletion or addition.

syntax

  • syntaxLanguage(file) identifies JavaScript and the TypeScript family.
  • resolveTypeScript(filePath) walks upward for the nearest typescript with the JavaScript parser API, skipping native TypeScript 7 packages.
  • checkSyntaxDetail(filePath, cwd, execPath?) returns { verdict, detail }. The detail is null for syntax verdicts and explains an unknown result.
  • checkSyntax(filePath, cwd, execPath?) returns only valid, invalid, or unknown; checker failures never become syntax verdicts.
  • syntaxCheckProblem(filePath) explains a missing checker and includes the npm i -D typescript install hint for TypeScript-family files.

TypeScript is resolved from the repository under test and is not a dependency of @adlc/core.

text (shared payload-capping helpers)

  • tail(str, maxChars = 4000) → the last maxChars characters of str, unchanged if already within limit. Hoisted here (was duplicated in consensus-fix) so every caller capping a prompt payload shares one implementation.
  • fence(label, content, maxChars, opts?) → wraps content in <<UNTRUSTED:...>>/<<END:...>> markers declaring it inert data, capped to maxChars (maxChars is required, not optional; there is no uncapped call). Truncation keeps the tail by default; pass { bias: 'head' } to keep the opening (spec-shaped content). opts must be a plain object whose only key is bias, and bias must be 'head' or 'tail'; anything else throws. Used by packages/fleet to fence prior build/gate/prosecution logs into a fix charter.

diff-mask (removed-line credential masking)

  • maskRemovedSecrets(diff) → diff with credentials masked on its removed (-) lines only, for a diff a review model is about to read. A change that deletes a secret still carries it on its - lines; added and context lines are at HEAD and stay visible so a reviewer can flag a secret the change adds. Masks the known provider shapes the findings ledger refuses, credential assignments (any case and identifier style — password, API_TOKEN, clientSecret, "apiKey" — quoted or not, with =, :, := or =>), URL userinfo passwords, Basic auth, high-entropy tokens, and private key blocks (tracked on the old side of the file, back-filled from an END line whose BEGIN is outside the context). The function-context text after a @@ header is masked too, since git copies it from the file. ---/+++ headers are never touched; the line count is kept. Feed it plain git diff --no-color --no-ext-diff output: a colored or external-driver diff is not recognised. A removed line over MAX_MASKED_LINE (2000) characters is masked whole, which also bounds the regex work per line. Best-effort: a value under 8 characters, an unquoted value with spaces, or a low-entropy password with no credential-like name, is not caught.
  • maskSecretText(text) → the same masking for one line of text.
  • MASKED_SECRET ([redacted]), MASKED_PRIVATE_KEY ([redacted private key]), MASKED_LONG_LINE ([redacted long line]) — the placeholders; MAX_MASKED_LINE — the per-line length cap.

test-kit (@adlc/core/test-kit)

Fixture helpers for node:test suites; typed by lib/test-kit.d.mts.

  • tmp(t, prefix?) → a fresh realpath'd directory under os.tmpdir(), removed by a hook registered on t.after() (retrying transient ENOTEMPTY/EBUSY, FIXTURE_RM_OPTIONS). Fails closed: a t without a callable .after (none, null, a prefix string, an options object, or the context a describe-level before() receives) throws a TypeError and creates nothing.
  • gitRepo(t, options?) → { dir, git, g }: a repo inside tmp(t) with a test identity, commit.gpgsign=false, gc.auto=0 and gc.autoDetach=false (no detached maintenance child writing into .git during removal). options is { prefix, branch, userEmail, userName } or a prefix string. Same context rule as tmp.
  • createScope() → a context ({ after, dispose }) for fixtures that outlive one test callback: create in before(), await scope.dispose() in after(). Registering on a disposed scope throws.
  • withScopedContext(fn) → runs fn(ctx) with a fresh scope and disposes it when fn settles; for code that calls test functions outside a node:test callback.
  • runBin(binPath, args?, options?) → spawnSync of a Node script with DEFAULT_SCRUBBED_ENV (signing keys, bypass flags) removed unless allowEnv names them.

版本与发布记录

@adlc/core 共发布 19 个版本,首次发布于 2026/06/14;过去 12 个月发布了 19 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.13.0latest2026/11/06今天
1.12.02026/10/1027 天前
1.11.12026/09/091 个月前
1.11.02026/08/252 个月前
1.10.02026/08/112 个月前
1.9.02026/08/092 个月前

关于 @adlc/core 的常见问题

@adlc/core 是什么?

Shared core for ADLC tools: LLM providers, git helpers, CLI conventions, ledgers, tickets, mutations.

如何安装 @adlc/core?

在项目目录运行 npm install @adlc/core,或使用 pnpm add / yarn add / bun add。

@adlc/core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@adlc/core 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@adlc/core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@adlc/core 可以商用吗?

@adlc/core 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@adlc/core 的下载量有多少?

根据 npm 官方下载统计,@adlc/core 在 2026/10/05 – 2026/11/03 这 30 天内被下载 9,357 次,日均约 312 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@adlc/core 还在维护吗?

最新版本 1.13.0 发布于 2026/11/06(今天);至今共发布 19 个版本,其中过去 12 个月发布了 19 个;首次发布于 2026/06/14。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。