dld-kit 1.0.0-rc.1 → 1.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,7 @@ This writes:
36
36
 
37
37
  - `dld.config.yaml`, `decisions/records/` and `decisions/INDEX.md`
38
38
  - the `dld-*` skills for each selected agent: `.claude/skills/` for Claude Code, `.agents/skills/` for the others
39
- - the always-on rule: `.claude/rules/dld-workflow.md` for Claude Code, `.agents/rules/dld-workflow.md` for Antigravity, and a block between `<!-- dld-kit:start -->` and `<!-- dld-kit:end -->` in `AGENTS.md` (or `CLAUDE.md`) for Codex, Cursor, OpenCode and Pi
39
+ - the always-on rule, once per agent: a block between `<!-- dld-kit:start -->` and `<!-- dld-kit:end -->` in `AGENTS.md` (or `CLAUDE.md`) for Codex, Cursor, OpenCode and Pi, `.agents/rules/dld-workflow.md` for Antigravity, and `.claude/rules/dld-workflow.md` for Claude Code unless Claude Code already reads the block (see [Agents](#agents)). When the block goes into `AGENTS.md` and the project has no `CLAUDE.md` or `.claude/CLAUDE.md`, it writes `.claude/CLAUDE.md` instead, which imports `AGENTS.md` for Claude Code
40
40
 
41
41
  `init` detects the agents from files in the project (`.claude/`, `.codex/`, `.cursor/`, `opencode.json`, `.pi/`, `AGENTS.md` and so on) and asks you to confirm the list. Options:
42
42
 
@@ -114,18 +114,31 @@ Channels can be combined. These combinations need care:
114
114
 
115
115
  | Agent | Skills (`dld init`) | Always-on rule (`dld init`, `/dld-init`) | Run a skill |
116
116
  |---|---|---|---|
117
- | Claude Code | `.claude/skills/` | `.claude/rules/dld-workflow.md` | `/dld-plan` |
117
+ | Claude Code | `.claude/skills/` | `.claude/rules/dld-workflow.md`, or the block in `CLAUDE.md`, or in `AGENTS.md` imported from a CLAUDE file (`@AGENTS.md`) | `/dld-plan` |
118
118
  | Antigravity | `.agents/skills/` | `.agents/rules/dld-workflow.md` | Ask for the dld-plan skill |
119
119
  | Codex | `.agents/skills/` | Block in `AGENTS.md` | Ask for the dld-plan skill |
120
- | Cursor | `.agents/skills/` | Block in `AGENTS.md` | Ask for the dld-plan skill |
121
- | OpenCode | `.agents/skills/` | Block in `AGENTS.md`, or `CLAUDE.md` if there is no `AGENTS.md` | Ask for the dld-plan skill |
120
+ | Cursor | `.agents/skills/` | Block in `AGENTS.md` or `CLAUDE.md` (Cursor reads both) | Ask for the dld-plan skill |
121
+ | OpenCode | `.agents/skills/` | Block in `AGENTS.md` (OpenCode 2.x reads no `CLAUDE.md`) | Ask for the dld-plan skill |
122
122
  | Pi | `.agents/skills/` | Block in `AGENTS.md`, or `CLAUDE.md` if there is no `AGENTS.md` | `/skill:dld-plan` |
123
123
  | Copilot CLI | Plugin only | Block in `AGENTS.md` (installed as for Codex) | Ask for the dld-plan skill |
124
124
 
125
- A new block goes into `AGENTS.md` if it exists, else into `CLAUDE.md` if that exists, else into a new `AGENTS.md`. Codex and Cursor read only `AGENTS.md`: when the block ends up in `CLAUDE.md`, `dld install-rule` warns and says how to move it.
125
+ A new block goes into `AGENTS.md` if it exists. Otherwise it goes into an existing `CLAUDE.md` only when every selected agent that takes the block (Codex, Cursor, OpenCode, Pi) reads that file, which Cursor and Pi do, and into a new `AGENTS.md` in all other cases. Pi reads `CLAUDE.md` only while there is no `AGENTS.md`, so when `dld` creates `AGENTS.md` next to a `CLAUDE.md` for Pi, it warns you to move anything Pi needs. A block already in `CLAUDE.md` stays there; `dld install-rule` warns about agents that cannot see it (Codex, OpenCode) and says how to move it.
126
+
127
+ Each agent gets the rule once. Claude Code reads `AGENTS.md` by itself only when the project has no `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md`, so adding one of those (even an uncommitted `CLAUDE.local.md`) would stop it. When the block is in `AGENTS.md` and the project has no `CLAUDE.md` or `.claude/CLAUDE.md`, `init` writes `.claude/CLAUDE.md` with the line `@../AGENTS.md`. Claude Code then loads `AGENTS.md` through that import, once, whichever CLAUDE files are added later. If a `CLAUDE.md` of yours already imports `@AGENTS.md`, the block there is Claude Code's rule and no other file is written.
126
128
 
127
129
  The examples below use Claude Code's `/dld-plan` form. In other agents, name the skill instead, e.g. "use the dld-plan skill to plan the retry feature".
128
130
 
131
+ ### Upgrading from 0.x
132
+
133
+ Before 1.0, the skills were copied by hand or installed with Tessl, each skill carried `scripts/*.sh`, and `/dld-init` appended a `## DLD (Decision-Linked Development)` section to `CLAUDE.md`.
134
+
135
+ - **Your data carries over unchanged:** `dld.config.yaml`, `decisions/records/`, `decisions/INDEX.md` and `decisions/.dld-state.yaml`. 1.0 reads them as 0.x wrote them.
136
+ - **Skills copied into `.claude/skills/` or `.agents/skills/`:** in the project, run `npx dld-kit@latest update --agent <names>` (e.g. `--agent claude,codex`). It rewrites the `dld-*` skills, deletes their old `scripts/*.sh`, and installs the always-on rule. Commit the result.
137
+ - **Skills copied anywhere else** (e.g. `.cursor/skills/`, `.codex/skills/`): delete those `dld-*` directories, then run the same `update`.
138
+ - **Tessl installs:** remove the `dld-kit/dld` tile with Tessl, and delete any DLD rule text Tessl added. Then run `update`. The Tessl tile gets no new versions.
139
+ - **The old `CLAUDE.md` section:** once the rule is installed, delete the `## DLD (Decision-Linked Development)` section yourself. Until then Claude Code and Cursor read the rule twice. `update` warns while the section is there, and never edits it.
140
+ - **To use the plugin or `npx skills` instead:** delete the copied `dld-*` directories and the old `CLAUDE.md` section first, then install through that channel.
141
+
129
142
  ## Get started
130
143
 
131
144
  ### New feature or change
@@ -367,10 +380,12 @@ The `dld` command comes with the npm package (`npx dld-kit <command>`, or `npm i
367
380
  | `dld update` | Rewrites the installed skills and rule with this version; `--agent` adds agents |
368
381
  | `dld install-rule --agent <names>` | Installs or refreshes only the always-on rule |
369
382
  | `dld session-context --agent <name>` | Prints the rule for a session hook, unless the agent loads it already (used by the Claude Code plugin) |
370
- | `dld --help` | Lists every command; the rest are the operations the skills run |
383
+ | `dld --help` | Lists the setup commands above, then the commands the skills run |
371
384
 
372
385
  `init` and `update` refuse to overwrite files from a newer dld-kit unless given `--force`.
373
386
 
387
+ Semantic versioning covers the four commands above, their flags, exit codes and documented output, and `--help` and `--version`. The other commands (`next-id`, `create-decision`, `regenerate-index` and so on) are internal: the skills run them from their own bundled copy of the CLI, and their names, arguments and output can change in a minor release. Don't script against them.
388
+
374
389
  ## Development
375
390
 
376
391
  ```bash
@@ -411,7 +426,7 @@ DLD is under active development. Feature requests and ideas are welcome — [ope
411
426
 
412
427
  ## Manual rule setup
413
428
 
414
- The always-on rule tells the agent to read a decision before changing code annotated with it. `/dld-init` installs it with `dld install-rule --agent <harness>`, which picks the file the harness loads: `.claude/rules/dld-workflow.md` for Claude Code, `.agents/rules/dld-workflow.md` for Antigravity, or a marked block in `AGENTS.md` (or `CLAUDE.md`) for Codex, Cursor, OpenCode and Pi.
429
+ The always-on rule tells the agent to read a decision before changing code annotated with it. `/dld-init` installs it with `dld install-rule --agent <harness>`, which picks the file the harness loads: `.claude/rules/dld-workflow.md` for Claude Code (unless it reads the block in `AGENTS.md`, directly or through a `.claude/CLAUDE.md` import), `.agents/rules/dld-workflow.md` for Antigravity, or a marked block in `AGENTS.md` (or `CLAUDE.md`) for Codex, Cursor, OpenCode and Pi.
415
430
 
416
431
  To add it by hand instead, copy [`templates/rules/dld-workflow.md`](templates/rules/dld-workflow.md) into one of those files.
417
432
 
package/dist/dld.mjs CHANGED
@@ -7372,7 +7372,7 @@ import { createInterface } from "node:readline/promises";
7372
7372
  import { fileURLToPath } from "node:url";
7373
7373
 
7374
7374
  // package.json
7375
- var version = "1.0.0-rc.1";
7375
+ var version = "1.0.0-rc.3";
7376
7376
 
7377
7377
  // src/core/errors.ts
7378
7378
  var EXIT_ERROR = 1;
@@ -7944,6 +7944,7 @@ function updateSnapshotState(ctx, project, customArtifacts) {
7944
7944
  var collectActiveDecisionsCommand = {
7945
7945
  name: "collect-active-decisions",
7946
7946
  summary: "Print every accepted decision record",
7947
+ internal: true,
7947
7948
  usage: "Usage: dld collect-active-decisions\n\nPrint each accepted record in ID order, separated by ===DLD_DECISION_BOUNDARY=== lines.\n",
7948
7949
  run(args, io, ctx) {
7949
7950
  parseCommandArgs({ args: [...args], options: {} });
@@ -8326,6 +8327,7 @@ function skippedNotice(reason) {
8326
8327
  var commitReindexCommand = {
8327
8328
  name: "commit-reindex",
8328
8329
  summary: "Squash the branch into one reindex commit",
8330
+ internal: true,
8329
8331
  usage: `Usage: dld commit-reindex --base <ref> < plan
8330
8332
 
8331
8333
  Read a rename plan on standard input and squash the branch's commits since the merge-base
@@ -8386,6 +8388,7 @@ function createDirectories(ctx, { config, paths }) {
8386
8388
  var createConfigCommand = {
8387
8389
  name: "create-config",
8388
8390
  summary: "Create dld.config.yaml at the project root",
8391
+ internal: true,
8389
8392
  usage: "Usage: dld create-config <flat|namespaced> [namespace ...]\n\nCreate dld.config.yaml. Namespaced mode needs at least one namespace.\n",
8390
8393
  run(args, io, ctx) {
8391
8394
  const { positionals } = parseCommandArgs({
@@ -8443,6 +8446,7 @@ function isSafeDirName(name) {
8443
8446
  var createDecisionCommand = {
8444
8447
  name: "create-decision",
8445
8448
  summary: "Create a proposed decision record",
8449
+ internal: true,
8446
8450
  usage: `Usage: dld create-decision --id <DL-NNN> --title <title> [options]
8447
8451
 
8448
8452
  Create a decision record with status proposed and print its path.
@@ -8494,6 +8498,7 @@ Options:
8494
8498
  var createDirectoriesCommand = {
8495
8499
  name: "create-directories",
8496
8500
  summary: "Create the decisions directory structure from the config",
8501
+ internal: true,
8497
8502
  usage: "Usage: dld create-directories\n\nCreate the decisions and records directories, and one per namespace.\n",
8498
8503
  run(args, io, ctx) {
8499
8504
  parseCommandArgs({ args: [...args], options: {} });
@@ -8562,6 +8567,7 @@ function writeIndex(ctx, paths, content) {
8562
8567
  var createEmptyIndexCommand = {
8563
8568
  name: "create-empty-index",
8564
8569
  summary: "Write an INDEX.md with no decisions",
8570
+ internal: true,
8565
8571
  usage: "Usage: dld create-empty-index\n\nWrite INDEX.md in the decisions directory with only the table header.\n",
8566
8572
  run(args, io, ctx) {
8567
8573
  parseCommandArgs({ args: [...args], options: {} });
@@ -8577,6 +8583,7 @@ var createEmptyIndexCommand = {
8577
8583
  var detectSnapshotChangesCommand = {
8578
8584
  name: "detect-snapshot-changes",
8579
8585
  summary: "Report what changed since the last snapshot",
8586
+ internal: true,
8580
8587
  usage: "Usage: dld detect-snapshot-changes\n\nPrint mode (full or incremental), new_decisions, modified_decisions and commit_range.\n",
8581
8588
  run(args, io, ctx) {
8582
8589
  parseCommandArgs({ args: [...args], options: {} });
@@ -8589,7 +8596,14 @@ var detectSnapshotChangesCommand = {
8589
8596
  import { join as join11, relative as relative6, sep as sep3 } from "node:path";
8590
8597
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
8591
8598
  ".git",
8599
+ // Agent configuration directories, which hold installed skills.
8592
8600
  ".claude",
8601
+ ".agents",
8602
+ ".agent",
8603
+ ".codex",
8604
+ ".cursor",
8605
+ ".opencode",
8606
+ ".pi",
8593
8607
  ".tessl",
8594
8608
  "node_modules",
8595
8609
  "vendor",
@@ -8668,6 +8682,7 @@ function formatAnnotation({ file, line, id }) {
8668
8682
  var findAnnotationsCommand = {
8669
8683
  name: "find-annotations",
8670
8684
  summary: "List every annotation in the codebase",
8685
+ internal: true,
8671
8686
  usage: "Usage: dld find-annotations\n\nPrint <file>:<line>:<DL-NNN> for every annotation, one per line.\n",
8672
8687
  run(args, io, ctx) {
8673
8688
  parseCommandArgs({ args: [...args], options: {} });
@@ -8683,6 +8698,7 @@ var findAnnotationsCommand = {
8683
8698
  var findCollisionsCommand = {
8684
8699
  name: "find-collisions",
8685
8700
  summary: "List locally added decisions whose IDs are taken",
8701
+ internal: true,
8686
8702
  usage: `Usage: dld find-collisions [--base <ref>]
8687
8703
 
8688
8704
  Print <path>\\t<DL-NNN> for each decision added on this branch whose ID is taken on the base
@@ -8749,6 +8765,7 @@ function updateAuditState(ctx, { paths }) {
8749
8765
  var findMissingAmendsCommand = {
8750
8766
  name: "find-missing-amends",
8751
8767
  summary: "List decision IDs mentioned in a body but not declared",
8768
+ internal: true,
8752
8769
  usage: `Usage: dld find-missing-amends [--all]
8753
8770
 
8754
8771
  Print <source-id>:<referenced-id> for each decision ID a record's body mentions without
@@ -8859,6 +8876,7 @@ function formatStaleMention({ file, line, oldId, newId, text }) {
8859
8876
  var findStaleMentionsCommand = {
8860
8877
  name: "find-stale-mentions",
8861
8878
  summary: "List remaining mentions of renamed decision IDs",
8879
+ internal: true,
8862
8880
  usage: `Usage: dld find-stale-mentions --base <ref> < plan
8863
8881
 
8864
8882
  Read a rename plan (<path>\\t<DL-OLD>\\t<DL-NEW> per line) on standard input and print
@@ -9040,7 +9058,6 @@ var AGENTS_LAYOUT = {
9040
9058
  dir: ".agents/skills"
9041
9059
  };
9042
9060
  var LAYOUTS = [CLAUDE_LAYOUT, AGENTS_LAYOUT];
9043
- var AGENTS_FIRST = ["AGENTS.md", "CLAUDE.md"];
9044
9061
  var HARNESSES = [
9045
9062
  {
9046
9063
  name: "claude",
@@ -9048,7 +9065,8 @@ var HARNESSES = [
9048
9065
  layout: CLAUDE_LAYOUT,
9049
9066
  rule: "claude-file",
9050
9067
  markers: [".claude", "CLAUDE.md"],
9051
- instructions: ["CLAUDE.md", "AGENTS.md"]
9068
+ // @decision(DL-054) @decision(DL-055)
9069
+ instructions: ["CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md", "AGENTS.md"]
9052
9070
  },
9053
9071
  {
9054
9072
  name: "antigravity",
@@ -9072,7 +9090,9 @@ var HARNESSES = [
9072
9090
  layout: AGENTS_LAYOUT,
9073
9091
  rule: "block",
9074
9092
  markers: [".cursor"],
9075
- instructions: ["AGENTS.md"]
9093
+ // @decision(DL-061)
9094
+ instructions: ["AGENTS.md", "CLAUDE.md"],
9095
+ readsAll: true
9076
9096
  },
9077
9097
  {
9078
9098
  name: "opencode",
@@ -9080,7 +9100,8 @@ var HARNESSES = [
9080
9100
  layout: AGENTS_LAYOUT,
9081
9101
  rule: "block",
9082
9102
  markers: [".opencode", "opencode.json", "opencode.jsonc"],
9083
- instructions: AGENTS_FIRST
9103
+ // @decision(DL-061) OpenCode 2.x; 1.x also fell back to CLAUDE.md.
9104
+ instructions: ["AGENTS.md"]
9084
9105
  },
9085
9106
  {
9086
9107
  name: "pi",
@@ -9088,7 +9109,7 @@ var HARNESSES = [
9088
9109
  layout: AGENTS_LAYOUT,
9089
9110
  rule: "block",
9090
9111
  markers: [".pi"],
9091
- instructions: AGENTS_FIRST
9112
+ instructions: ["AGENTS.md", "CLAUDE.md"]
9092
9113
  }
9093
9114
  ];
9094
9115
  var GENERIC_MARKERS = ["AGENTS.md", ".agents/skills"];
@@ -9255,7 +9276,7 @@ function removeEmptyParents(ctx, dir, paths) {
9255
9276
  }
9256
9277
 
9257
9278
  // src/generate/rule.ts
9258
- import { dirname as dirname3, join as join15 } from "node:path";
9279
+ import { dirname as dirname3, join as join15, posix as posix4 } from "node:path";
9259
9280
 
9260
9281
  // templates/rules/dld-workflow.md
9261
9282
  var dld_workflow_default = "# DLD (Decision-Linked Development)\n\nThis project uses Decision-Linked Development. Decision records (DL-*.md) live in the `records/` subdirectory of the decisions directory set in `dld.config.yaml` (`decisions/` by default). High-level docs (INDEX.md, OVERVIEW.md, SNAPSHOT.md) live in the decisions directory.\n\n## Rules\n\n- When you encounter `@decision(DL-XXX)` annotations in code, read the referenced decision with the dld-lookup skill BEFORE modifying the annotated code.\n- ALWAYS look up and verify related decisions before modifying annotated code. Do not skip this step.\n- NEVER modify code in a way that contradicts an existing decision without first confirming with the user. If the change requires breaking a previous decision, a new decision must be recorded (with the dld-decide skill) that explicitly supersedes the old one. If it only partially modifies a previous decision, record it as an amendment instead.\n\n## Skills\n\n- dld-decide: record a new decision\n- dld-plan: break down a feature into multiple grouped decisions\n- dld-implement: implement proposed decisions\n- dld-lookup: query decisions by ID, tag, or code path\n- dld-adjust: adjust or update existing decisions\n- dld-audit: scan for drift between decisions and code\n- dld-snapshot: regenerate SNAPSHOT.md and OVERVIEW.md from the decision log\n- dld-status: a quick overview of the decision log state\n- dld-retrofit: generate decisions from an existing codebase\n- dld-reindex: resolve decision-ID collisions with the base branch (and open PRs) before rebasing\n";
@@ -9265,10 +9286,13 @@ var RULE_TEXT = dld_workflow_default;
9265
9286
  var RULE_SOURCE = "templates/rules/dld-workflow.md";
9266
9287
  var CLAUDE_RULE_FILE = ".claude/rules/dld-workflow.md";
9267
9288
  var AGENTS_RULE_FILE = ".agents/rules/dld-workflow.md";
9289
+ var CLAUDE_IMPORT_FILE = ".claude/CLAUDE.md";
9268
9290
  var BLOCK_START = "<!-- dld-kit:start -->";
9269
9291
  var BLOCK_END = "<!-- dld-kit:end -->";
9270
9292
  var AGENTS_MD = "AGENTS.md";
9271
9293
  var CLAUDE_MD = "CLAUDE.md";
9294
+ var CLAUDE_FILES = [CLAUDE_MD, CLAUDE_IMPORT_FILE, "CLAUDE.local.md"];
9295
+ var SHARED_CLAUDE_FILES = [CLAUDE_MD, CLAUDE_IMPORT_FILE];
9272
9296
  var RULE_FILES = {
9273
9297
  "claude-file": CLAUDE_RULE_FILE,
9274
9298
  "agents-file": AGENTS_RULE_FILE
@@ -9286,6 +9310,16 @@ function renderRuleFile(channel, text, version2) {
9286
9310
 
9287
9311
  ${text}`;
9288
9312
  }
9313
+ function renderImportFile(version2) {
9314
+ return `<!-- Generated by dld-kit ${version2} for Claude Code: it loads AGENTS.md, which holds the DLD rule, through the import below. Do not edit: updating the rule overwrites this file. -->
9315
+
9316
+ @../AGENTS.md
9317
+ `;
9318
+ }
9319
+ var IMPORT_STAMP = /^<!-- Generated by dld-kit (\S+) for Claude Code:/;
9320
+ function importFileVersion(content) {
9321
+ return IMPORT_STAMP.exec(content)?.[1];
9322
+ }
9289
9323
  function renderBlock(text, version2) {
9290
9324
  const body = text.endsWith("\n") ? text.slice(0, -1) : text;
9291
9325
  return [BLOCK_START, notice(version2, "block"), "", ...body.split("\n"), BLOCK_END];
@@ -9334,25 +9368,53 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
9334
9368
  let blockFiles = [AGENTS_MD, CLAUDE_MD].filter(
9335
9369
  (file) => regular(file) && findBlock(read(file), file) !== void 0
9336
9370
  );
9337
- if (blockFiles.length === 0 && channels.has("block")) blockFiles = [blockPlacement(ctx, root)];
9371
+ if (blockFiles.length === 0 && channels.has("block")) {
9372
+ const targets = harnesses.filter((h) => h.rule === "block");
9373
+ const file = blockPlacement(ctx, root, targets);
9374
+ blockFiles = [file];
9375
+ plan.warnings.push(...switchWarnings(ctx, root, file, targets));
9376
+ }
9377
+ const pending = /* @__PURE__ */ new Map();
9338
9378
  for (const file of blockFiles) {
9339
9379
  const content = read(file);
9340
9380
  const updated = upsertBlock(content, renderBlock(text, version2), file);
9381
+ pending.set(file, updated);
9341
9382
  if (updated !== content) plan.writes.push({ path: file, content: updated });
9342
9383
  }
9384
+ const linkedDir = symlinkedPart(ctx, root, dirname3(CLAUDE_IMPORT_FILE));
9385
+ const ownsImport = linkedDir === void 0 && importFileVersion(read(CLAUDE_IMPORT_FILE)) !== void 0;
9386
+ const wantsImport = channels.has("claude-file") && blockFiles.includes(AGENTS_MD) && !SHARED_CLAUDE_FILES.some((file) => ctx.fs.lexists(join15(root, file)));
9387
+ if (ownsImport || wantsImport && linkedDir === void 0) {
9388
+ const content = renderImportFile(version2);
9389
+ pending.set(CLAUDE_IMPORT_FILE, content);
9390
+ if (read(CLAUDE_IMPORT_FILE) !== content) {
9391
+ plan.writes.push({ path: CLAUDE_IMPORT_FILE, content });
9392
+ }
9393
+ } else if (wantsImport) {
9394
+ plan.warnings.push(
9395
+ `${linkedDir} is a symlink, so dld-kit does not write ${CLAUDE_IMPORT_FILE}, which would import AGENTS.md for Claude Code. Claude Code reads the rule block in AGENTS.md by itself only while the project has no CLAUDE.md, ${CLAUDE_IMPORT_FILE} or CLAUDE.local.md.`
9396
+ );
9397
+ }
9343
9398
  const withBlock = new Set(blockFiles.map(resolved));
9344
- const loads = (file) => withBlock.has(resolved(file));
9345
- const readsBlock = (harness) => loads(instructionFile(ctx, root, harness));
9399
+ const current = projectView(ctx, root);
9400
+ const view = {
9401
+ exists: (file) => pending.has(file) || current.exists(file),
9402
+ read: (file) => pending.get(file) ?? current.read(file)
9403
+ };
9404
+ const reads = (harness) => instructionFiles(harness, view)[0];
9405
+ const readsBlock = (harness) => instructionFiles(harness, view).some((file) => withBlock.has(resolved(file)));
9346
9406
  const ownerOf = (channel) => HARNESSES.find((h) => h.rule === channel);
9347
9407
  const loadsBlock = (channel) => {
9348
9408
  const owner = ownerOf(channel);
9349
9409
  return owner !== void 0 && readsBlock(owner);
9350
9410
  };
9351
- const blind = harnesses.filter((h) => h.rule === "block" && !readsBlock(h));
9352
- if (blockFiles.length > 0 && blind.length > 0) {
9353
- const names = blind.map((h) => h.title).join(" and ");
9411
+ if (blockFiles.length > 0) {
9354
9412
  plan.warnings.push(
9355
- `The dld-kit rule block is in ${blockFiles.join(" and ")}, but ${names} ${blind.length === 1 ? "reads" : "read"} ${AGENTS_MD}. To cover ${blind.length === 1 ? "it" : "them"}, move the block (the dld-kit:start line through the dld-kit:end line) into ${AGENTS_MD}, then run dld install-rule.`
9413
+ ...blindWarnings(
9414
+ blockFiles,
9415
+ harnesses.filter((h) => h.rule === "block" && !readsBlock(h)),
9416
+ reads
9417
+ )
9356
9418
  );
9357
9419
  }
9358
9420
  for (const channel of ["claude-file", "agents-file"]) {
@@ -9369,21 +9431,82 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
9369
9431
  plan.warnings.push(...legacyBlockWarnings(read(CLAUDE_MD), ruleInstalled));
9370
9432
  return plan;
9371
9433
  }
9372
- function instructionFile(ctx, root, harness) {
9373
- const { instructions } = harness;
9374
- return instructions.find((file) => ctx.fs.exists(join15(root, file))) ?? instructions.at(-1) ?? AGENTS_MD;
9434
+ function projectView(ctx, root) {
9435
+ const exists = (file) => ctx.fs.exists(join15(root, file));
9436
+ return {
9437
+ exists,
9438
+ read: (file) => exists(file) && !ctx.fs.isDirectory(join15(root, file)) ? ctx.fs.readFile(join15(root, file)) : ""
9439
+ };
9375
9440
  }
9376
- function blockPlacement(ctx, root) {
9377
- for (const file of [AGENTS_MD, CLAUDE_MD]) {
9378
- if (ctx.fs.isRegularFile(join15(root, file))) return file;
9441
+ function instructionFiles(harness, view) {
9442
+ if (harness.name !== "claude") {
9443
+ if (harness.readsAll) return harness.instructions.filter((f) => view.exists(f));
9444
+ const file = harness.instructions.find((f) => view.exists(f));
9445
+ return file === void 0 ? [] : [file];
9446
+ }
9447
+ const files = CLAUDE_FILES.filter((file) => view.exists(file));
9448
+ if (files.length === 0) return view.exists(AGENTS_MD) ? [AGENTS_MD] : [];
9449
+ const imported = files.some((file) => importsAgentsMd(view.read(file), file));
9450
+ return imported && view.exists(AGENTS_MD) ? [...files, AGENTS_MD] : files;
9451
+ }
9452
+ var FENCE = /^ {0,3}(`{3,}|~{3,})/;
9453
+ function importsAgentsMd(content, file) {
9454
+ const dir = posix4.dirname(file);
9455
+ let fence;
9456
+ for (const line of content.split(/\r?\n/)) {
9457
+ const marker = FENCE.exec(line)?.[1];
9458
+ if (marker !== void 0) {
9459
+ if (fence === void 0) fence = marker;
9460
+ else if (marker[0] === fence[0] && marker.length >= fence.length) fence = void 0;
9461
+ continue;
9462
+ }
9463
+ if (fence !== void 0) continue;
9464
+ const prose = line.replace(/`[^`]*`/g, "");
9465
+ for (const match of prose.matchAll(/(?:^|\s)@(\S+)/g)) {
9466
+ const target = match[1];
9467
+ if (target !== void 0 && posix4.normalize(posix4.join(dir, target)) === AGENTS_MD) {
9468
+ return true;
9469
+ }
9470
+ }
9471
+ }
9472
+ return false;
9473
+ }
9474
+ function blindWarnings(blockFiles, blind, reads) {
9475
+ const byFile = /* @__PURE__ */ new Map();
9476
+ for (const harness of blind) {
9477
+ const file = reads(harness) ?? harness.instructions[0] ?? AGENTS_MD;
9478
+ byFile.set(file, [...byFile.get(file) ?? [], harness]);
9379
9479
  }
9380
- if (ctx.fs.lexists(join15(root, AGENTS_MD))) {
9480
+ return [...byFile].map(([file, harnesses]) => {
9481
+ const names = harnesses.map((h) => h.title).join(" and ");
9482
+ const one = harnesses.length === 1;
9483
+ return `The dld-kit rule block is in ${blockFiles.join(" and ")}, but ${names} ${one ? "reads" : "read"} ${file}. To cover ${one ? "it" : "them"}, move the block (the dld-kit:start line through the dld-kit:end line) into ${file}, then run dld install-rule.`;
9484
+ });
9485
+ }
9486
+ function blockPlacement(ctx, root, targets) {
9487
+ if (ctx.fs.isRegularFile(join15(root, AGENTS_MD))) return AGENTS_MD;
9488
+ const agentsLinked = ctx.fs.lexists(join15(root, AGENTS_MD));
9489
+ if (ctx.fs.isRegularFile(join15(root, CLAUDE_MD)) && (agentsLinked || targets.every((h) => h.instructions.includes(CLAUDE_MD)))) {
9490
+ return CLAUDE_MD;
9491
+ }
9492
+ if (agentsLinked) {
9381
9493
  throw new DldError(
9382
9494
  `${AGENTS_MD} is not a regular file (a symlink?); dld-kit does not edit through it. Point it at a regular file or add the dld-kit block by hand.`
9383
9495
  );
9384
9496
  }
9385
9497
  return AGENTS_MD;
9386
9498
  }
9499
+ function switchWarnings(ctx, root, file, targets) {
9500
+ if (file !== AGENTS_MD || ctx.fs.lexists(join15(root, AGENTS_MD))) return [];
9501
+ const claudeMd = join15(root, CLAUDE_MD);
9502
+ if (!ctx.fs.exists(claudeMd) || ctx.fs.isDirectory(claudeMd)) return [];
9503
+ const switching = targets.filter((h) => !h.readsAll && h.instructions.includes(CLAUDE_MD));
9504
+ if (switching.length === 0) return [];
9505
+ const names = switching.map((h) => h.title).join(" and ");
9506
+ return [
9507
+ `dld-kit puts the rule block in a new AGENTS.md. ${names} will read AGENTS.md instead of CLAUDE.md from now on, so move anything ${switching.length === 1 ? "it needs" : "they need"} from CLAUDE.md into AGENTS.md.`
9508
+ ];
9509
+ }
9387
9510
  var LEGACY_HEADING = /^## DLD \(Decision-Linked Development\)\s*$/;
9388
9511
  function legacyBlockWarnings(claudeMd, ruleInstalled) {
9389
9512
  const span = findBlock(claudeMd, CLAUDE_MD);
@@ -9398,32 +9521,43 @@ function loadsRule(ctx, root, harness) {
9398
9521
  if (harness.rule !== "block" && ctx.fs.exists(join15(root, RULE_FILES[harness.rule]))) {
9399
9522
  return true;
9400
9523
  }
9401
- const file = instructionFile(ctx, root, harness);
9402
- if (!ctx.fs.exists(join15(root, file))) return false;
9403
- return ctx.fs.readFile(join15(root, file)).split(/\r?\n/).some((line) => line === BLOCK_START || LEGACY_HEADING.test(line));
9524
+ const view = projectView(ctx, root);
9525
+ return instructionFiles(harness, view).some(
9526
+ (file) => view.read(file).split(/\r?\n/).some((line) => line === BLOCK_START || LEGACY_HEADING.test(line))
9527
+ );
9404
9528
  }
9405
9529
  function sessionContext(ctx, root, harness, text = RULE_TEXT) {
9406
9530
  if (!ctx.fs.exists(join15(root, CONFIG_FILE))) return void 0;
9407
9531
  return loadsRule(ctx, root, harness) ? void 0 : text;
9408
9532
  }
9409
- function refuseSymlinkedDir(ctx, root, dir) {
9533
+ function symlinkedPart(ctx, root, dir) {
9410
9534
  let current = "";
9411
9535
  for (const part of dir.split("/")) {
9412
9536
  current = current === "" ? part : `${current}/${part}`;
9413
9537
  const full = join15(root, current);
9414
- if (!ctx.fs.lexists(full)) return;
9538
+ if (!ctx.fs.lexists(full)) return void 0;
9415
9539
  if (!ctx.fs.exists(full) || ctx.fs.realPath(full) !== join15(ctx.fs.realPath(root), current)) {
9416
- throw new DldError(
9417
- `${current} is a symlink; dld-kit does not install through symlinks. Replace it with a directory (dld-kit keeps each agent's copy separate).`
9418
- );
9540
+ return current;
9419
9541
  }
9420
9542
  }
9543
+ return void 0;
9544
+ }
9545
+ function refuseSymlinkedDir(ctx, root, dir) {
9546
+ const linked = symlinkedPart(ctx, root, dir);
9547
+ if (linked !== void 0) {
9548
+ throw new DldError(
9549
+ `${linked} is a symlink; dld-kit does not install through symlinks. Replace it with a directory (dld-kit keeps each agent's copy separate).`
9550
+ );
9551
+ }
9421
9552
  }
9422
9553
  function installedRuleChannels(ctx, root) {
9423
9554
  const channels = /* @__PURE__ */ new Set();
9424
9555
  for (const channel of ["claude-file", "agents-file"]) {
9425
9556
  if (ctx.fs.lexists(join15(root, RULE_FILES[channel]))) channels.add(channel);
9426
9557
  }
9558
+ if (importFileVersion(projectView(ctx, root).read(CLAUDE_IMPORT_FILE)) !== void 0) {
9559
+ channels.add("claude-file");
9560
+ }
9427
9561
  for (const file of [AGENTS_MD, CLAUDE_MD]) {
9428
9562
  const path = join15(root, file);
9429
9563
  if (ctx.fs.isRegularFile(path) && findBlock(ctx.fs.readFile(path), file) !== void 0) {
@@ -9438,6 +9572,8 @@ function installedRuleStamps(ctx, root) {
9438
9572
  for (const file of [CLAUDE_RULE_FILE, AGENTS_RULE_FILE].filter(regular)) {
9439
9573
  stamps.push({ path: file, version: ruleVersion(ctx.fs.readFile(join15(root, file))) });
9440
9574
  }
9575
+ const imported = importFileVersion(projectView(ctx, root).read(CLAUDE_IMPORT_FILE));
9576
+ if (imported !== void 0) stamps.push({ path: CLAUDE_IMPORT_FILE, version: imported });
9441
9577
  for (const file of [AGENTS_MD, CLAUDE_MD].filter(regular)) {
9442
9578
  const content = ctx.fs.readFile(join15(root, file));
9443
9579
  const span = findBlock(content, file);
@@ -9680,8 +9816,13 @@ function printReport(io, report2) {
9680
9816
  io.stdout(`${dir}: ${written} written, ${removed} removed, ${unchanged} unchanged
9681
9817
  `);
9682
9818
  }
9683
- for (const path of report2.ruleWritten) io.stdout(`Wrote the DLD rule to ${path}
9684
- `);
9819
+ for (const path of report2.ruleWritten) {
9820
+ io.stdout(
9821
+ path === CLAUDE_IMPORT_FILE ? `Wrote ${path} (imports AGENTS.md for Claude Code)
9822
+ ` : `Wrote the DLD rule to ${path}
9823
+ `
9824
+ );
9825
+ }
9685
9826
  for (const path of report2.ruleRemoved) io.stdout(`Removed ${path} (the rule is in the block)
9686
9827
  `);
9687
9828
  for (const warning of report2.warnings) io.stderr(`Warning: ${warning}
@@ -9810,6 +9951,7 @@ ${AGENT_HELP}
9810
9951
  var listTakenIdsCommand = {
9811
9952
  name: "list-taken-ids",
9812
9953
  summary: "List decision IDs taken on the base branch and in open PRs",
9954
+ internal: true,
9813
9955
  usage: `Usage: dld list-taken-ids [--base <ref>]
9814
9956
 
9815
9957
  Print the decision IDs on the base branch and in records touched by open pull requests
@@ -9833,6 +9975,7 @@ Options:
9833
9975
  var nextIdCommand = {
9834
9976
  name: "next-id",
9835
9977
  summary: "Print the next sequential decision ID",
9978
+ internal: true,
9836
9979
  usage: "Usage: dld next-id\n\nPrint the next decision ID, e.g. DL-004.\n",
9837
9980
  run(args, io, ctx) {
9838
9981
  parseCommandArgs({ args: [...args], options: {} });
@@ -9847,6 +9990,7 @@ var nextIdCommand = {
9847
9990
  var planRenamesCommand = {
9848
9991
  name: "plan-renames",
9849
9992
  summary: "Plan renames that resolve decision ID collisions",
9993
+ internal: true,
9850
9994
  usage: `Usage: dld plan-renames [--base <ref>]
9851
9995
 
9852
9996
  Print <path>\\t<DL-OLD>\\t<DL-NEW> for each colliding local decision, assigning the next free
@@ -9870,6 +10014,7 @@ Options:
9870
10014
  var regenerateIndexCommand = {
9871
10015
  name: "regenerate-index",
9872
10016
  summary: "Rebuild INDEX.md from the decision records",
10017
+ internal: true,
9873
10018
  usage: `Usage: dld regenerate-index [--include-base <ref>]
9874
10019
 
9875
10020
  Rebuild INDEX.md from every decision record.
@@ -9897,6 +10042,7 @@ Options:
9897
10042
  var renameDecisionCommand = {
9898
10043
  name: "rename-decision",
9899
10044
  summary: "Rename a local decision and rewrite its references",
10045
+ internal: true,
9900
10046
  usage: `Usage: dld rename-decision --old <DL-OLD> --new <DL-NEW> --path <path> [--base <ref>]
9901
10047
 
9902
10048
  Rename a locally added decision with git mv, rewrite its id and references in changed
@@ -9932,6 +10078,7 @@ Options:
9932
10078
  var resolveBaseCommand = {
9933
10079
  name: "resolve-base",
9934
10080
  summary: "Print the base ref to check decision IDs against",
10081
+ internal: true,
9935
10082
  usage: `Usage: dld resolve-base
9936
10083
 
9937
10084
  Print the current branch's upstream when it tracks a differently named branch, otherwise
@@ -10043,6 +10190,7 @@ ${AGENT_HELP}
10043
10190
  var updateAuditStateCommand = {
10044
10191
  name: "update-audit-state",
10045
10192
  summary: "Record the audit run in .dld-state.yaml",
10193
+ internal: true,
10046
10194
  usage: "Usage: dld update-audit-state\n\nRecord the current time and HEAD commit as the last audit.\n",
10047
10195
  run(args, io, ctx) {
10048
10196
  parseCommandArgs({ args: [...args], options: {} });
@@ -10057,6 +10205,7 @@ var updateAuditStateCommand = {
10057
10205
  var updateSnapshotStateCommand = {
10058
10206
  name: "update-snapshot-state",
10059
10207
  summary: "Record the snapshot run in .dld-state.yaml",
10208
+ internal: true,
10060
10209
  usage: `Usage: dld update-snapshot-state [artifact ...]
10061
10210
 
10062
10211
  Record the snapshot time, HEAD commit and highest accepted decision, with timestamps for
@@ -10079,6 +10228,7 @@ SNAPSHOT.md, OVERVIEW.md and any custom artifacts named.
10079
10228
  var updateStatusCommand = {
10080
10229
  name: "update-status",
10081
10230
  summary: "Set a decision's status",
10231
+ internal: true,
10082
10232
  usage: `Usage: dld update-status <DL-NNN> <${STATUSES.join("|")}>
10083
10233
 
10084
10234
  Change only the status line of a decision record.
@@ -10110,6 +10260,7 @@ var EXIT_MISSING = 1;
10110
10260
  var verifyAnnotationsCommand = {
10111
10261
  name: "verify-annotations",
10112
10262
  summary: "Check that decisions have annotations in the code",
10263
+ internal: true,
10113
10264
  usage: "Usage: dld verify-annotations <DL-NNN> [DL-NNN ...]\n\nExit 0 if every decision has at least one annotation, 1 if any are missing.\n",
10114
10265
  run(args, io, ctx) {
10115
10266
  const { positionals: ids } = parseCommandArgs({
@@ -10163,11 +10314,19 @@ var COMMANDS = [
10163
10314
  findStaleMentionsCommand,
10164
10315
  commitReindexCommand
10165
10316
  ];
10317
+ var INTERNAL_HEADING = "Commands the skills run (internal; may change in minor releases)";
10318
+ var INTERNAL_NOTE = "Internal: the dld-kit skills run this command. Its name, arguments and output may change in a minor release.\n";
10166
10319
  function usage(commands) {
10320
+ const list = (group) => group.map((c) => ` ${c.name.padEnd(25)} ${c.summary}`).join("\n");
10321
+ const setup = commands.filter((c) => !c.internal);
10322
+ const internal = commands.filter((c) => c.internal);
10323
+ const sections = [`Setup commands:
10324
+ ${list(setup)}`];
10325
+ if (internal.length > 0) sections.push(`${INTERNAL_HEADING}:
10326
+ ${list(internal)}`);
10167
10327
  return `Usage: dld <command> [options]
10168
10328
 
10169
- Commands:
10170
- ${commands.map((c) => ` ${c.name.padEnd(20)} ${c.summary}`).join("\n")}
10329
+ ${sections.join("\n\n")}
10171
10330
 
10172
10331
  Options:
10173
10332
  -h, --help Show this help, or a command's help after its name
@@ -10206,7 +10365,8 @@ ${usage(commands)}`);
10206
10365
  }
10207
10366
  function report(error, command, io) {
10208
10367
  if (error instanceof HelpRequested) {
10209
- io.stdout(command.usage);
10368
+ io.stdout(command.internal ? `${command.usage}
10369
+ ${INTERNAL_NOTE}` : command.usage);
10210
10370
  return EXIT_OK;
10211
10371
  }
10212
10372
  if (error instanceof UsageError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dld-kit",
3
- "version": "1.0.0-rc.1",
3
+ "version": "1.0.0-rc.3",
4
4
  "description": "Decision-Linked Development: record, link, and audit development decisions alongside code",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -3,7 +3,7 @@ name: dld-adjust
3
3
  description: Adjust or update existing decision records. Handles permission gating for accepted decisions and correctly interprets adjustment requests.
4
4
  compatibility: Requires Node.js 20+ and git.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-adjust/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-audit
3
3
  description: Scan for drift between decisions and code. Finds orphaned annotations, stale references, and undocumented changes.
4
4
  compatibility: Requires Node.js 20+ and git.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-audit/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-audit-auto
3
3
  description: Autonomous audit — detects drift, fixes issues, and opens a PR. Designed for scheduled/CI execution without human interaction.
4
4
  compatibility: Requires Node.js 20+ and git.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-audit-auto/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-common
3
3
  description: The dld CLI shared by the DLD skills. Not intended for direct invocation — the other DLD skills run it.
4
4
  compatibility: Requires Node.js 20+ and git.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-common/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -7372,7 +7372,7 @@ import { createInterface } from "node:readline/promises";
7372
7372
  import { fileURLToPath } from "node:url";
7373
7373
 
7374
7374
  // package.json
7375
- var version = "1.0.0-rc.1";
7375
+ var version = "1.0.0-rc.3";
7376
7376
 
7377
7377
  // src/core/errors.ts
7378
7378
  var EXIT_ERROR = 1;
@@ -7944,6 +7944,7 @@ function updateSnapshotState(ctx, project, customArtifacts) {
7944
7944
  var collectActiveDecisionsCommand = {
7945
7945
  name: "collect-active-decisions",
7946
7946
  summary: "Print every accepted decision record",
7947
+ internal: true,
7947
7948
  usage: "Usage: dld collect-active-decisions\n\nPrint each accepted record in ID order, separated by ===DLD_DECISION_BOUNDARY=== lines.\n",
7948
7949
  run(args, io, ctx) {
7949
7950
  parseCommandArgs({ args: [...args], options: {} });
@@ -8326,6 +8327,7 @@ function skippedNotice(reason) {
8326
8327
  var commitReindexCommand = {
8327
8328
  name: "commit-reindex",
8328
8329
  summary: "Squash the branch into one reindex commit",
8330
+ internal: true,
8329
8331
  usage: `Usage: dld commit-reindex --base <ref> < plan
8330
8332
 
8331
8333
  Read a rename plan on standard input and squash the branch's commits since the merge-base
@@ -8386,6 +8388,7 @@ function createDirectories(ctx, { config, paths }) {
8386
8388
  var createConfigCommand = {
8387
8389
  name: "create-config",
8388
8390
  summary: "Create dld.config.yaml at the project root",
8391
+ internal: true,
8389
8392
  usage: "Usage: dld create-config <flat|namespaced> [namespace ...]\n\nCreate dld.config.yaml. Namespaced mode needs at least one namespace.\n",
8390
8393
  run(args, io, ctx) {
8391
8394
  const { positionals } = parseCommandArgs({
@@ -8443,6 +8446,7 @@ function isSafeDirName(name) {
8443
8446
  var createDecisionCommand = {
8444
8447
  name: "create-decision",
8445
8448
  summary: "Create a proposed decision record",
8449
+ internal: true,
8446
8450
  usage: `Usage: dld create-decision --id <DL-NNN> --title <title> [options]
8447
8451
 
8448
8452
  Create a decision record with status proposed and print its path.
@@ -8494,6 +8498,7 @@ Options:
8494
8498
  var createDirectoriesCommand = {
8495
8499
  name: "create-directories",
8496
8500
  summary: "Create the decisions directory structure from the config",
8501
+ internal: true,
8497
8502
  usage: "Usage: dld create-directories\n\nCreate the decisions and records directories, and one per namespace.\n",
8498
8503
  run(args, io, ctx) {
8499
8504
  parseCommandArgs({ args: [...args], options: {} });
@@ -8562,6 +8567,7 @@ function writeIndex(ctx, paths, content) {
8562
8567
  var createEmptyIndexCommand = {
8563
8568
  name: "create-empty-index",
8564
8569
  summary: "Write an INDEX.md with no decisions",
8570
+ internal: true,
8565
8571
  usage: "Usage: dld create-empty-index\n\nWrite INDEX.md in the decisions directory with only the table header.\n",
8566
8572
  run(args, io, ctx) {
8567
8573
  parseCommandArgs({ args: [...args], options: {} });
@@ -8577,6 +8583,7 @@ var createEmptyIndexCommand = {
8577
8583
  var detectSnapshotChangesCommand = {
8578
8584
  name: "detect-snapshot-changes",
8579
8585
  summary: "Report what changed since the last snapshot",
8586
+ internal: true,
8580
8587
  usage: "Usage: dld detect-snapshot-changes\n\nPrint mode (full or incremental), new_decisions, modified_decisions and commit_range.\n",
8581
8588
  run(args, io, ctx) {
8582
8589
  parseCommandArgs({ args: [...args], options: {} });
@@ -8589,7 +8596,14 @@ var detectSnapshotChangesCommand = {
8589
8596
  import { join as join11, relative as relative6, sep as sep3 } from "node:path";
8590
8597
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
8591
8598
  ".git",
8599
+ // Agent configuration directories, which hold installed skills.
8592
8600
  ".claude",
8601
+ ".agents",
8602
+ ".agent",
8603
+ ".codex",
8604
+ ".cursor",
8605
+ ".opencode",
8606
+ ".pi",
8593
8607
  ".tessl",
8594
8608
  "node_modules",
8595
8609
  "vendor",
@@ -8668,6 +8682,7 @@ function formatAnnotation({ file, line, id }) {
8668
8682
  var findAnnotationsCommand = {
8669
8683
  name: "find-annotations",
8670
8684
  summary: "List every annotation in the codebase",
8685
+ internal: true,
8671
8686
  usage: "Usage: dld find-annotations\n\nPrint <file>:<line>:<DL-NNN> for every annotation, one per line.\n",
8672
8687
  run(args, io, ctx) {
8673
8688
  parseCommandArgs({ args: [...args], options: {} });
@@ -8683,6 +8698,7 @@ var findAnnotationsCommand = {
8683
8698
  var findCollisionsCommand = {
8684
8699
  name: "find-collisions",
8685
8700
  summary: "List locally added decisions whose IDs are taken",
8701
+ internal: true,
8686
8702
  usage: `Usage: dld find-collisions [--base <ref>]
8687
8703
 
8688
8704
  Print <path>\\t<DL-NNN> for each decision added on this branch whose ID is taken on the base
@@ -8749,6 +8765,7 @@ function updateAuditState(ctx, { paths }) {
8749
8765
  var findMissingAmendsCommand = {
8750
8766
  name: "find-missing-amends",
8751
8767
  summary: "List decision IDs mentioned in a body but not declared",
8768
+ internal: true,
8752
8769
  usage: `Usage: dld find-missing-amends [--all]
8753
8770
 
8754
8771
  Print <source-id>:<referenced-id> for each decision ID a record's body mentions without
@@ -8859,6 +8876,7 @@ function formatStaleMention({ file, line, oldId, newId, text }) {
8859
8876
  var findStaleMentionsCommand = {
8860
8877
  name: "find-stale-mentions",
8861
8878
  summary: "List remaining mentions of renamed decision IDs",
8879
+ internal: true,
8862
8880
  usage: `Usage: dld find-stale-mentions --base <ref> < plan
8863
8881
 
8864
8882
  Read a rename plan (<path>\\t<DL-OLD>\\t<DL-NEW> per line) on standard input and print
@@ -9040,7 +9058,6 @@ var AGENTS_LAYOUT = {
9040
9058
  dir: ".agents/skills"
9041
9059
  };
9042
9060
  var LAYOUTS = [CLAUDE_LAYOUT, AGENTS_LAYOUT];
9043
- var AGENTS_FIRST = ["AGENTS.md", "CLAUDE.md"];
9044
9061
  var HARNESSES = [
9045
9062
  {
9046
9063
  name: "claude",
@@ -9048,7 +9065,8 @@ var HARNESSES = [
9048
9065
  layout: CLAUDE_LAYOUT,
9049
9066
  rule: "claude-file",
9050
9067
  markers: [".claude", "CLAUDE.md"],
9051
- instructions: ["CLAUDE.md", "AGENTS.md"]
9068
+ // @decision(DL-054) @decision(DL-055)
9069
+ instructions: ["CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md", "AGENTS.md"]
9052
9070
  },
9053
9071
  {
9054
9072
  name: "antigravity",
@@ -9072,7 +9090,9 @@ var HARNESSES = [
9072
9090
  layout: AGENTS_LAYOUT,
9073
9091
  rule: "block",
9074
9092
  markers: [".cursor"],
9075
- instructions: ["AGENTS.md"]
9093
+ // @decision(DL-061)
9094
+ instructions: ["AGENTS.md", "CLAUDE.md"],
9095
+ readsAll: true
9076
9096
  },
9077
9097
  {
9078
9098
  name: "opencode",
@@ -9080,7 +9100,8 @@ var HARNESSES = [
9080
9100
  layout: AGENTS_LAYOUT,
9081
9101
  rule: "block",
9082
9102
  markers: [".opencode", "opencode.json", "opencode.jsonc"],
9083
- instructions: AGENTS_FIRST
9103
+ // @decision(DL-061) OpenCode 2.x; 1.x also fell back to CLAUDE.md.
9104
+ instructions: ["AGENTS.md"]
9084
9105
  },
9085
9106
  {
9086
9107
  name: "pi",
@@ -9088,7 +9109,7 @@ var HARNESSES = [
9088
9109
  layout: AGENTS_LAYOUT,
9089
9110
  rule: "block",
9090
9111
  markers: [".pi"],
9091
- instructions: AGENTS_FIRST
9112
+ instructions: ["AGENTS.md", "CLAUDE.md"]
9092
9113
  }
9093
9114
  ];
9094
9115
  var GENERIC_MARKERS = ["AGENTS.md", ".agents/skills"];
@@ -9255,7 +9276,7 @@ function removeEmptyParents(ctx, dir, paths) {
9255
9276
  }
9256
9277
 
9257
9278
  // src/generate/rule.ts
9258
- import { dirname as dirname3, join as join15 } from "node:path";
9279
+ import { dirname as dirname3, join as join15, posix as posix4 } from "node:path";
9259
9280
 
9260
9281
  // templates/rules/dld-workflow.md
9261
9282
  var dld_workflow_default = "# DLD (Decision-Linked Development)\n\nThis project uses Decision-Linked Development. Decision records (DL-*.md) live in the `records/` subdirectory of the decisions directory set in `dld.config.yaml` (`decisions/` by default). High-level docs (INDEX.md, OVERVIEW.md, SNAPSHOT.md) live in the decisions directory.\n\n## Rules\n\n- When you encounter `@decision(DL-XXX)` annotations in code, read the referenced decision with the dld-lookup skill BEFORE modifying the annotated code.\n- ALWAYS look up and verify related decisions before modifying annotated code. Do not skip this step.\n- NEVER modify code in a way that contradicts an existing decision without first confirming with the user. If the change requires breaking a previous decision, a new decision must be recorded (with the dld-decide skill) that explicitly supersedes the old one. If it only partially modifies a previous decision, record it as an amendment instead.\n\n## Skills\n\n- dld-decide: record a new decision\n- dld-plan: break down a feature into multiple grouped decisions\n- dld-implement: implement proposed decisions\n- dld-lookup: query decisions by ID, tag, or code path\n- dld-adjust: adjust or update existing decisions\n- dld-audit: scan for drift between decisions and code\n- dld-snapshot: regenerate SNAPSHOT.md and OVERVIEW.md from the decision log\n- dld-status: a quick overview of the decision log state\n- dld-retrofit: generate decisions from an existing codebase\n- dld-reindex: resolve decision-ID collisions with the base branch (and open PRs) before rebasing\n";
@@ -9265,10 +9286,13 @@ var RULE_TEXT = dld_workflow_default;
9265
9286
  var RULE_SOURCE = "templates/rules/dld-workflow.md";
9266
9287
  var CLAUDE_RULE_FILE = ".claude/rules/dld-workflow.md";
9267
9288
  var AGENTS_RULE_FILE = ".agents/rules/dld-workflow.md";
9289
+ var CLAUDE_IMPORT_FILE = ".claude/CLAUDE.md";
9268
9290
  var BLOCK_START = "<!-- dld-kit:start -->";
9269
9291
  var BLOCK_END = "<!-- dld-kit:end -->";
9270
9292
  var AGENTS_MD = "AGENTS.md";
9271
9293
  var CLAUDE_MD = "CLAUDE.md";
9294
+ var CLAUDE_FILES = [CLAUDE_MD, CLAUDE_IMPORT_FILE, "CLAUDE.local.md"];
9295
+ var SHARED_CLAUDE_FILES = [CLAUDE_MD, CLAUDE_IMPORT_FILE];
9272
9296
  var RULE_FILES = {
9273
9297
  "claude-file": CLAUDE_RULE_FILE,
9274
9298
  "agents-file": AGENTS_RULE_FILE
@@ -9286,6 +9310,16 @@ function renderRuleFile(channel, text, version2) {
9286
9310
 
9287
9311
  ${text}`;
9288
9312
  }
9313
+ function renderImportFile(version2) {
9314
+ return `<!-- Generated by dld-kit ${version2} for Claude Code: it loads AGENTS.md, which holds the DLD rule, through the import below. Do not edit: updating the rule overwrites this file. -->
9315
+
9316
+ @../AGENTS.md
9317
+ `;
9318
+ }
9319
+ var IMPORT_STAMP = /^<!-- Generated by dld-kit (\S+) for Claude Code:/;
9320
+ function importFileVersion(content) {
9321
+ return IMPORT_STAMP.exec(content)?.[1];
9322
+ }
9289
9323
  function renderBlock(text, version2) {
9290
9324
  const body = text.endsWith("\n") ? text.slice(0, -1) : text;
9291
9325
  return [BLOCK_START, notice(version2, "block"), "", ...body.split("\n"), BLOCK_END];
@@ -9334,25 +9368,53 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
9334
9368
  let blockFiles = [AGENTS_MD, CLAUDE_MD].filter(
9335
9369
  (file) => regular(file) && findBlock(read(file), file) !== void 0
9336
9370
  );
9337
- if (blockFiles.length === 0 && channels.has("block")) blockFiles = [blockPlacement(ctx, root)];
9371
+ if (blockFiles.length === 0 && channels.has("block")) {
9372
+ const targets = harnesses.filter((h) => h.rule === "block");
9373
+ const file = blockPlacement(ctx, root, targets);
9374
+ blockFiles = [file];
9375
+ plan.warnings.push(...switchWarnings(ctx, root, file, targets));
9376
+ }
9377
+ const pending = /* @__PURE__ */ new Map();
9338
9378
  for (const file of blockFiles) {
9339
9379
  const content = read(file);
9340
9380
  const updated = upsertBlock(content, renderBlock(text, version2), file);
9381
+ pending.set(file, updated);
9341
9382
  if (updated !== content) plan.writes.push({ path: file, content: updated });
9342
9383
  }
9384
+ const linkedDir = symlinkedPart(ctx, root, dirname3(CLAUDE_IMPORT_FILE));
9385
+ const ownsImport = linkedDir === void 0 && importFileVersion(read(CLAUDE_IMPORT_FILE)) !== void 0;
9386
+ const wantsImport = channels.has("claude-file") && blockFiles.includes(AGENTS_MD) && !SHARED_CLAUDE_FILES.some((file) => ctx.fs.lexists(join15(root, file)));
9387
+ if (ownsImport || wantsImport && linkedDir === void 0) {
9388
+ const content = renderImportFile(version2);
9389
+ pending.set(CLAUDE_IMPORT_FILE, content);
9390
+ if (read(CLAUDE_IMPORT_FILE) !== content) {
9391
+ plan.writes.push({ path: CLAUDE_IMPORT_FILE, content });
9392
+ }
9393
+ } else if (wantsImport) {
9394
+ plan.warnings.push(
9395
+ `${linkedDir} is a symlink, so dld-kit does not write ${CLAUDE_IMPORT_FILE}, which would import AGENTS.md for Claude Code. Claude Code reads the rule block in AGENTS.md by itself only while the project has no CLAUDE.md, ${CLAUDE_IMPORT_FILE} or CLAUDE.local.md.`
9396
+ );
9397
+ }
9343
9398
  const withBlock = new Set(blockFiles.map(resolved));
9344
- const loads = (file) => withBlock.has(resolved(file));
9345
- const readsBlock = (harness) => loads(instructionFile(ctx, root, harness));
9399
+ const current = projectView(ctx, root);
9400
+ const view = {
9401
+ exists: (file) => pending.has(file) || current.exists(file),
9402
+ read: (file) => pending.get(file) ?? current.read(file)
9403
+ };
9404
+ const reads = (harness) => instructionFiles(harness, view)[0];
9405
+ const readsBlock = (harness) => instructionFiles(harness, view).some((file) => withBlock.has(resolved(file)));
9346
9406
  const ownerOf = (channel) => HARNESSES.find((h) => h.rule === channel);
9347
9407
  const loadsBlock = (channel) => {
9348
9408
  const owner = ownerOf(channel);
9349
9409
  return owner !== void 0 && readsBlock(owner);
9350
9410
  };
9351
- const blind = harnesses.filter((h) => h.rule === "block" && !readsBlock(h));
9352
- if (blockFiles.length > 0 && blind.length > 0) {
9353
- const names = blind.map((h) => h.title).join(" and ");
9411
+ if (blockFiles.length > 0) {
9354
9412
  plan.warnings.push(
9355
- `The dld-kit rule block is in ${blockFiles.join(" and ")}, but ${names} ${blind.length === 1 ? "reads" : "read"} ${AGENTS_MD}. To cover ${blind.length === 1 ? "it" : "them"}, move the block (the dld-kit:start line through the dld-kit:end line) into ${AGENTS_MD}, then run dld install-rule.`
9413
+ ...blindWarnings(
9414
+ blockFiles,
9415
+ harnesses.filter((h) => h.rule === "block" && !readsBlock(h)),
9416
+ reads
9417
+ )
9356
9418
  );
9357
9419
  }
9358
9420
  for (const channel of ["claude-file", "agents-file"]) {
@@ -9369,21 +9431,82 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
9369
9431
  plan.warnings.push(...legacyBlockWarnings(read(CLAUDE_MD), ruleInstalled));
9370
9432
  return plan;
9371
9433
  }
9372
- function instructionFile(ctx, root, harness) {
9373
- const { instructions } = harness;
9374
- return instructions.find((file) => ctx.fs.exists(join15(root, file))) ?? instructions.at(-1) ?? AGENTS_MD;
9434
+ function projectView(ctx, root) {
9435
+ const exists = (file) => ctx.fs.exists(join15(root, file));
9436
+ return {
9437
+ exists,
9438
+ read: (file) => exists(file) && !ctx.fs.isDirectory(join15(root, file)) ? ctx.fs.readFile(join15(root, file)) : ""
9439
+ };
9375
9440
  }
9376
- function blockPlacement(ctx, root) {
9377
- for (const file of [AGENTS_MD, CLAUDE_MD]) {
9378
- if (ctx.fs.isRegularFile(join15(root, file))) return file;
9441
+ function instructionFiles(harness, view) {
9442
+ if (harness.name !== "claude") {
9443
+ if (harness.readsAll) return harness.instructions.filter((f) => view.exists(f));
9444
+ const file = harness.instructions.find((f) => view.exists(f));
9445
+ return file === void 0 ? [] : [file];
9446
+ }
9447
+ const files = CLAUDE_FILES.filter((file) => view.exists(file));
9448
+ if (files.length === 0) return view.exists(AGENTS_MD) ? [AGENTS_MD] : [];
9449
+ const imported = files.some((file) => importsAgentsMd(view.read(file), file));
9450
+ return imported && view.exists(AGENTS_MD) ? [...files, AGENTS_MD] : files;
9451
+ }
9452
+ var FENCE = /^ {0,3}(`{3,}|~{3,})/;
9453
+ function importsAgentsMd(content, file) {
9454
+ const dir = posix4.dirname(file);
9455
+ let fence;
9456
+ for (const line of content.split(/\r?\n/)) {
9457
+ const marker = FENCE.exec(line)?.[1];
9458
+ if (marker !== void 0) {
9459
+ if (fence === void 0) fence = marker;
9460
+ else if (marker[0] === fence[0] && marker.length >= fence.length) fence = void 0;
9461
+ continue;
9462
+ }
9463
+ if (fence !== void 0) continue;
9464
+ const prose = line.replace(/`[^`]*`/g, "");
9465
+ for (const match of prose.matchAll(/(?:^|\s)@(\S+)/g)) {
9466
+ const target = match[1];
9467
+ if (target !== void 0 && posix4.normalize(posix4.join(dir, target)) === AGENTS_MD) {
9468
+ return true;
9469
+ }
9470
+ }
9471
+ }
9472
+ return false;
9473
+ }
9474
+ function blindWarnings(blockFiles, blind, reads) {
9475
+ const byFile = /* @__PURE__ */ new Map();
9476
+ for (const harness of blind) {
9477
+ const file = reads(harness) ?? harness.instructions[0] ?? AGENTS_MD;
9478
+ byFile.set(file, [...byFile.get(file) ?? [], harness]);
9379
9479
  }
9380
- if (ctx.fs.lexists(join15(root, AGENTS_MD))) {
9480
+ return [...byFile].map(([file, harnesses]) => {
9481
+ const names = harnesses.map((h) => h.title).join(" and ");
9482
+ const one = harnesses.length === 1;
9483
+ return `The dld-kit rule block is in ${blockFiles.join(" and ")}, but ${names} ${one ? "reads" : "read"} ${file}. To cover ${one ? "it" : "them"}, move the block (the dld-kit:start line through the dld-kit:end line) into ${file}, then run dld install-rule.`;
9484
+ });
9485
+ }
9486
+ function blockPlacement(ctx, root, targets) {
9487
+ if (ctx.fs.isRegularFile(join15(root, AGENTS_MD))) return AGENTS_MD;
9488
+ const agentsLinked = ctx.fs.lexists(join15(root, AGENTS_MD));
9489
+ if (ctx.fs.isRegularFile(join15(root, CLAUDE_MD)) && (agentsLinked || targets.every((h) => h.instructions.includes(CLAUDE_MD)))) {
9490
+ return CLAUDE_MD;
9491
+ }
9492
+ if (agentsLinked) {
9381
9493
  throw new DldError(
9382
9494
  `${AGENTS_MD} is not a regular file (a symlink?); dld-kit does not edit through it. Point it at a regular file or add the dld-kit block by hand.`
9383
9495
  );
9384
9496
  }
9385
9497
  return AGENTS_MD;
9386
9498
  }
9499
+ function switchWarnings(ctx, root, file, targets) {
9500
+ if (file !== AGENTS_MD || ctx.fs.lexists(join15(root, AGENTS_MD))) return [];
9501
+ const claudeMd = join15(root, CLAUDE_MD);
9502
+ if (!ctx.fs.exists(claudeMd) || ctx.fs.isDirectory(claudeMd)) return [];
9503
+ const switching = targets.filter((h) => !h.readsAll && h.instructions.includes(CLAUDE_MD));
9504
+ if (switching.length === 0) return [];
9505
+ const names = switching.map((h) => h.title).join(" and ");
9506
+ return [
9507
+ `dld-kit puts the rule block in a new AGENTS.md. ${names} will read AGENTS.md instead of CLAUDE.md from now on, so move anything ${switching.length === 1 ? "it needs" : "they need"} from CLAUDE.md into AGENTS.md.`
9508
+ ];
9509
+ }
9387
9510
  var LEGACY_HEADING = /^## DLD \(Decision-Linked Development\)\s*$/;
9388
9511
  function legacyBlockWarnings(claudeMd, ruleInstalled) {
9389
9512
  const span = findBlock(claudeMd, CLAUDE_MD);
@@ -9398,32 +9521,43 @@ function loadsRule(ctx, root, harness) {
9398
9521
  if (harness.rule !== "block" && ctx.fs.exists(join15(root, RULE_FILES[harness.rule]))) {
9399
9522
  return true;
9400
9523
  }
9401
- const file = instructionFile(ctx, root, harness);
9402
- if (!ctx.fs.exists(join15(root, file))) return false;
9403
- return ctx.fs.readFile(join15(root, file)).split(/\r?\n/).some((line) => line === BLOCK_START || LEGACY_HEADING.test(line));
9524
+ const view = projectView(ctx, root);
9525
+ return instructionFiles(harness, view).some(
9526
+ (file) => view.read(file).split(/\r?\n/).some((line) => line === BLOCK_START || LEGACY_HEADING.test(line))
9527
+ );
9404
9528
  }
9405
9529
  function sessionContext(ctx, root, harness, text = RULE_TEXT) {
9406
9530
  if (!ctx.fs.exists(join15(root, CONFIG_FILE))) return void 0;
9407
9531
  return loadsRule(ctx, root, harness) ? void 0 : text;
9408
9532
  }
9409
- function refuseSymlinkedDir(ctx, root, dir) {
9533
+ function symlinkedPart(ctx, root, dir) {
9410
9534
  let current = "";
9411
9535
  for (const part of dir.split("/")) {
9412
9536
  current = current === "" ? part : `${current}/${part}`;
9413
9537
  const full = join15(root, current);
9414
- if (!ctx.fs.lexists(full)) return;
9538
+ if (!ctx.fs.lexists(full)) return void 0;
9415
9539
  if (!ctx.fs.exists(full) || ctx.fs.realPath(full) !== join15(ctx.fs.realPath(root), current)) {
9416
- throw new DldError(
9417
- `${current} is a symlink; dld-kit does not install through symlinks. Replace it with a directory (dld-kit keeps each agent's copy separate).`
9418
- );
9540
+ return current;
9419
9541
  }
9420
9542
  }
9543
+ return void 0;
9544
+ }
9545
+ function refuseSymlinkedDir(ctx, root, dir) {
9546
+ const linked = symlinkedPart(ctx, root, dir);
9547
+ if (linked !== void 0) {
9548
+ throw new DldError(
9549
+ `${linked} is a symlink; dld-kit does not install through symlinks. Replace it with a directory (dld-kit keeps each agent's copy separate).`
9550
+ );
9551
+ }
9421
9552
  }
9422
9553
  function installedRuleChannels(ctx, root) {
9423
9554
  const channels = /* @__PURE__ */ new Set();
9424
9555
  for (const channel of ["claude-file", "agents-file"]) {
9425
9556
  if (ctx.fs.lexists(join15(root, RULE_FILES[channel]))) channels.add(channel);
9426
9557
  }
9558
+ if (importFileVersion(projectView(ctx, root).read(CLAUDE_IMPORT_FILE)) !== void 0) {
9559
+ channels.add("claude-file");
9560
+ }
9427
9561
  for (const file of [AGENTS_MD, CLAUDE_MD]) {
9428
9562
  const path = join15(root, file);
9429
9563
  if (ctx.fs.isRegularFile(path) && findBlock(ctx.fs.readFile(path), file) !== void 0) {
@@ -9438,6 +9572,8 @@ function installedRuleStamps(ctx, root) {
9438
9572
  for (const file of [CLAUDE_RULE_FILE, AGENTS_RULE_FILE].filter(regular)) {
9439
9573
  stamps.push({ path: file, version: ruleVersion(ctx.fs.readFile(join15(root, file))) });
9440
9574
  }
9575
+ const imported = importFileVersion(projectView(ctx, root).read(CLAUDE_IMPORT_FILE));
9576
+ if (imported !== void 0) stamps.push({ path: CLAUDE_IMPORT_FILE, version: imported });
9441
9577
  for (const file of [AGENTS_MD, CLAUDE_MD].filter(regular)) {
9442
9578
  const content = ctx.fs.readFile(join15(root, file));
9443
9579
  const span = findBlock(content, file);
@@ -9680,8 +9816,13 @@ function printReport(io, report2) {
9680
9816
  io.stdout(`${dir}: ${written} written, ${removed} removed, ${unchanged} unchanged
9681
9817
  `);
9682
9818
  }
9683
- for (const path of report2.ruleWritten) io.stdout(`Wrote the DLD rule to ${path}
9684
- `);
9819
+ for (const path of report2.ruleWritten) {
9820
+ io.stdout(
9821
+ path === CLAUDE_IMPORT_FILE ? `Wrote ${path} (imports AGENTS.md for Claude Code)
9822
+ ` : `Wrote the DLD rule to ${path}
9823
+ `
9824
+ );
9825
+ }
9685
9826
  for (const path of report2.ruleRemoved) io.stdout(`Removed ${path} (the rule is in the block)
9686
9827
  `);
9687
9828
  for (const warning of report2.warnings) io.stderr(`Warning: ${warning}
@@ -9810,6 +9951,7 @@ ${AGENT_HELP}
9810
9951
  var listTakenIdsCommand = {
9811
9952
  name: "list-taken-ids",
9812
9953
  summary: "List decision IDs taken on the base branch and in open PRs",
9954
+ internal: true,
9813
9955
  usage: `Usage: dld list-taken-ids [--base <ref>]
9814
9956
 
9815
9957
  Print the decision IDs on the base branch and in records touched by open pull requests
@@ -9833,6 +9975,7 @@ Options:
9833
9975
  var nextIdCommand = {
9834
9976
  name: "next-id",
9835
9977
  summary: "Print the next sequential decision ID",
9978
+ internal: true,
9836
9979
  usage: "Usage: dld next-id\n\nPrint the next decision ID, e.g. DL-004.\n",
9837
9980
  run(args, io, ctx) {
9838
9981
  parseCommandArgs({ args: [...args], options: {} });
@@ -9847,6 +9990,7 @@ var nextIdCommand = {
9847
9990
  var planRenamesCommand = {
9848
9991
  name: "plan-renames",
9849
9992
  summary: "Plan renames that resolve decision ID collisions",
9993
+ internal: true,
9850
9994
  usage: `Usage: dld plan-renames [--base <ref>]
9851
9995
 
9852
9996
  Print <path>\\t<DL-OLD>\\t<DL-NEW> for each colliding local decision, assigning the next free
@@ -9870,6 +10014,7 @@ Options:
9870
10014
  var regenerateIndexCommand = {
9871
10015
  name: "regenerate-index",
9872
10016
  summary: "Rebuild INDEX.md from the decision records",
10017
+ internal: true,
9873
10018
  usage: `Usage: dld regenerate-index [--include-base <ref>]
9874
10019
 
9875
10020
  Rebuild INDEX.md from every decision record.
@@ -9897,6 +10042,7 @@ Options:
9897
10042
  var renameDecisionCommand = {
9898
10043
  name: "rename-decision",
9899
10044
  summary: "Rename a local decision and rewrite its references",
10045
+ internal: true,
9900
10046
  usage: `Usage: dld rename-decision --old <DL-OLD> --new <DL-NEW> --path <path> [--base <ref>]
9901
10047
 
9902
10048
  Rename a locally added decision with git mv, rewrite its id and references in changed
@@ -9932,6 +10078,7 @@ Options:
9932
10078
  var resolveBaseCommand = {
9933
10079
  name: "resolve-base",
9934
10080
  summary: "Print the base ref to check decision IDs against",
10081
+ internal: true,
9935
10082
  usage: `Usage: dld resolve-base
9936
10083
 
9937
10084
  Print the current branch's upstream when it tracks a differently named branch, otherwise
@@ -10043,6 +10190,7 @@ ${AGENT_HELP}
10043
10190
  var updateAuditStateCommand = {
10044
10191
  name: "update-audit-state",
10045
10192
  summary: "Record the audit run in .dld-state.yaml",
10193
+ internal: true,
10046
10194
  usage: "Usage: dld update-audit-state\n\nRecord the current time and HEAD commit as the last audit.\n",
10047
10195
  run(args, io, ctx) {
10048
10196
  parseCommandArgs({ args: [...args], options: {} });
@@ -10057,6 +10205,7 @@ var updateAuditStateCommand = {
10057
10205
  var updateSnapshotStateCommand = {
10058
10206
  name: "update-snapshot-state",
10059
10207
  summary: "Record the snapshot run in .dld-state.yaml",
10208
+ internal: true,
10060
10209
  usage: `Usage: dld update-snapshot-state [artifact ...]
10061
10210
 
10062
10211
  Record the snapshot time, HEAD commit and highest accepted decision, with timestamps for
@@ -10079,6 +10228,7 @@ SNAPSHOT.md, OVERVIEW.md and any custom artifacts named.
10079
10228
  var updateStatusCommand = {
10080
10229
  name: "update-status",
10081
10230
  summary: "Set a decision's status",
10231
+ internal: true,
10082
10232
  usage: `Usage: dld update-status <DL-NNN> <${STATUSES.join("|")}>
10083
10233
 
10084
10234
  Change only the status line of a decision record.
@@ -10110,6 +10260,7 @@ var EXIT_MISSING = 1;
10110
10260
  var verifyAnnotationsCommand = {
10111
10261
  name: "verify-annotations",
10112
10262
  summary: "Check that decisions have annotations in the code",
10263
+ internal: true,
10113
10264
  usage: "Usage: dld verify-annotations <DL-NNN> [DL-NNN ...]\n\nExit 0 if every decision has at least one annotation, 1 if any are missing.\n",
10114
10265
  run(args, io, ctx) {
10115
10266
  const { positionals: ids } = parseCommandArgs({
@@ -10163,11 +10314,19 @@ var COMMANDS = [
10163
10314
  findStaleMentionsCommand,
10164
10315
  commitReindexCommand
10165
10316
  ];
10317
+ var INTERNAL_HEADING = "Commands the skills run (internal; may change in minor releases)";
10318
+ var INTERNAL_NOTE = "Internal: the dld-kit skills run this command. Its name, arguments and output may change in a minor release.\n";
10166
10319
  function usage(commands) {
10320
+ const list = (group) => group.map((c) => ` ${c.name.padEnd(25)} ${c.summary}`).join("\n");
10321
+ const setup = commands.filter((c) => !c.internal);
10322
+ const internal = commands.filter((c) => c.internal);
10323
+ const sections = [`Setup commands:
10324
+ ${list(setup)}`];
10325
+ if (internal.length > 0) sections.push(`${INTERNAL_HEADING}:
10326
+ ${list(internal)}`);
10167
10327
  return `Usage: dld <command> [options]
10168
10328
 
10169
- Commands:
10170
- ${commands.map((c) => ` ${c.name.padEnd(20)} ${c.summary}`).join("\n")}
10329
+ ${sections.join("\n\n")}
10171
10330
 
10172
10331
  Options:
10173
10332
  -h, --help Show this help, or a command's help after its name
@@ -10206,7 +10365,8 @@ ${usage(commands)}`);
10206
10365
  }
10207
10366
  function report(error, command, io) {
10208
10367
  if (error instanceof HelpRequested) {
10209
- io.stdout(command.usage);
10368
+ io.stdout(command.internal ? `${command.usage}
10369
+ ${INTERNAL_NOTE}` : command.usage);
10210
10370
  return EXIT_OK;
10211
10371
  }
10212
10372
  if (error instanceof UsageError) {
@@ -3,7 +3,7 @@ name: dld-decide
3
3
  description: Record a single development decision as a markdown file with YAML frontmatter. Collects context, rationale, and code references interactively.
4
4
  compatibility: Requires Node.js 20+ and git.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-decide/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-implement
3
3
  description: Implement one or more proposed decisions. Makes code changes, adds `@decision` annotations, and updates decision status.
4
4
  compatibility: Requires Node.js 20+ and git.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-implement/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-init
3
3
  description: Bootstrap DLD (Decision-Linked Development) in a repository. Creates dld.config.yaml, the decisions/ directory, and INDEX.md. Run once per project.
4
4
  compatibility: Requires Node.js 20+ and git.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-init/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-lookup
3
3
  description: Look up decisions by ID, tag, code path, or keyword. IMPORTANT — use this proactively whenever you encounter `@decision` annotations in code you are about to read or modify.
4
4
  compatibility: No external dependencies.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-lookup/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-plan
3
3
  description: Break down a feature into multiple decisions interactively. Creates a set of decision records grouped by a shared tag.
4
4
  compatibility: Requires Node.js 20+ and git.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-plan/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-reindex
3
3
  description: Resolve decision ID collisions between a local branch and the base branch (and open PRs) before rebasing. Renames colliding local decisions with git mv, rewrites cross-references and annotations, then squashes branch commits into a single rebase-clean reindex commit.
4
4
  compatibility: Requires Node.js 20+ and git. Open-PR scanning additionally needs the `gh` CLI authenticated against a GitHub remote — the skill falls back gracefully when unavailable.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-reindex/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-retrofit
3
3
  description: Bootstrap DLD decisions from an existing codebase. Analyzes code to infer rationale, generates decision records, and adds `@decision` annotations.
4
4
  compatibility: Requires Node.js 20+ and git.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-retrofit/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-snapshot
3
3
  description: Generate SNAPSHOT.md (detailed decision reference) and OVERVIEW.md (narrative synthesis with diagrams) from the decision log.
4
4
  compatibility: Requires Node.js 20+ and git.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-snapshot/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9
 
@@ -3,7 +3,7 @@ name: dld-status
3
3
  description: Quick overview of the decision log state — counts by status, recent decisions, and run tracking info.
4
4
  compatibility: No external dependencies.
5
5
  metadata:
6
- dld-kit-version: "1.0.0-rc.1"
6
+ dld-kit-version: "1.0.0-rc.3"
7
7
  ---
8
8
  <!-- Generated by dld-kit from templates/skills/dld-status/SKILL.md. Do not edit: updating the skills overwrites this file. -->
9
9