dld-kit 1.0.0-rc.2 → 1.0.0-rc.4

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/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.2";
7375
+ var version = "1.0.0-rc.4";
7376
7376
 
7377
7377
  // src/core/errors.ts
7378
7378
  var EXIT_ERROR = 1;
@@ -7902,11 +7902,11 @@ function detectSnapshotChanges(ctx, project) {
7902
7902
  if ((stored === void 0 || stored === "unknown") && lastRun !== void 0) {
7903
7903
  stored = gitOrEmpty(git, "log", `--until=${lastRun}`, "--format=%h", "-1").trim() || void 0;
7904
7904
  }
7905
- const commit = resolveStateCommit(git, stored);
7905
+ const commit2 = resolveStateCommit(git, stored);
7906
7906
  let modifiedDecisions = [];
7907
7907
  let commitRange = "";
7908
- if (commit !== void 0 && commit !== shortHead(ctx, paths.root)) {
7909
- commitRange = `${commit}..HEAD`;
7908
+ if (commit2 !== void 0 && commit2 !== shortHead(ctx, paths.root)) {
7909
+ commitRange = `${commit2}..HEAD`;
7910
7910
  const numbers = nulSeparated(
7911
7911
  gitOrEmpty(git, "diff", "-z", "--name-only", commitRange, "--", recordsPathspec(paths))
7912
7912
  ).map((path) => basename3(path)).filter((name) => RECORD_FILE.test(name)).map((name) => recordNumber(name)).filter((number) => number <= includedNumber);
@@ -7927,23 +7927,24 @@ function formatSnapshotChanges(changes) {
7927
7927
  function updateSnapshotState(ctx, project, customArtifacts) {
7928
7928
  const { paths } = project;
7929
7929
  const timestamp = formatTimestamp(ctx.now());
7930
- const commit = shortHead(ctx, paths.root);
7930
+ const commit2 = shortHead(ctx, paths.root);
7931
7931
  const highest = readRecords(ctx, project).filter((record) => record.accepted).reduce((max, record) => Math.max(max, record.number), 0);
7932
7932
  const artifacts = /* @__PURE__ */ Object.create(null);
7933
7933
  for (const name of [...BUILT_IN_ARTIFACTS, ...customArtifacts]) artifacts[name] = timestamp;
7934
7934
  writeStateSection(ctx, paths, "snapshot", {
7935
7935
  last_run: timestamp,
7936
- commit_hash: commit,
7936
+ commit_hash: commit2,
7937
7937
  decisions_included: String(highest),
7938
7938
  artifacts
7939
7939
  });
7940
- return { timestamp, commit, highest };
7940
+ return { timestamp, commit: commit2, highest };
7941
7941
  }
7942
7942
 
7943
7943
  // src/cli/commands/collect-active-decisions.ts
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: {} });
@@ -7961,8 +7962,9 @@ import { basename as basename4 } from "node:path";
7961
7962
  // src/core/open-prs.ts
7962
7963
  var GITHUB_REMOTE = /github\.com[:/]/;
7963
7964
  var PR_LIMIT = "100";
7965
+ var COMMIT_ID = /^[0-9a-f]{40}([0-9a-f]{24})?$/;
7964
7966
  function openPrIds(ctx, paths, base) {
7965
- const skipped = (reason) => ({ ids: [], skipped: reason });
7967
+ const skipped = (reason) => ({ claims: [], skipped: reason });
7966
7968
  try {
7967
7969
  ctx.gh(["--version"]);
7968
7970
  } catch (error) {
@@ -7989,7 +7991,7 @@ function openPrIds(ctx, paths, base) {
7989
7991
  "open",
7990
7992
  `--base=${prBase}`,
7991
7993
  "--json",
7992
- "files,headRefName,isCrossRepository",
7994
+ "files,headRefName,headRefOid,isCrossRepository",
7993
7995
  "--limit",
7994
7996
  PR_LIMIT
7995
7997
  ]);
@@ -8003,14 +8005,15 @@ function openPrIds(ctx, paths, base) {
8003
8005
  if (prs === void 0) return skipped("gh pr list returned unexpected output");
8004
8006
  const current = gitOrEmpty(git, "rev-parse", "--abbrev-ref", "HEAD").trim();
8005
8007
  const prefix = `${recordsPathspec(paths)}/`;
8006
- const ids = [];
8008
+ const claims = [];
8007
8009
  for (const pr of prs) {
8008
8010
  if (!pr.isCrossRepository && pr.headRefName === current) continue;
8009
8011
  for (const path of pr.files) {
8010
- if (path.startsWith(prefix)) ids.push(...path.match(DECISION_MENTION) ?? []);
8012
+ if (!path.startsWith(prefix)) continue;
8013
+ for (const id of path.match(DECISION_MENTION) ?? []) claims.push({ id, head: pr.headRefOid });
8011
8014
  }
8012
8015
  }
8013
- return { ids };
8016
+ return { claims };
8014
8017
  }
8015
8018
  function parsePrList(output) {
8016
8019
  let value;
@@ -8026,6 +8029,7 @@ function parsePrList(output) {
8026
8029
  const files = Array.isArray(item.files) ? item.files : [];
8027
8030
  prs.push({
8028
8031
  headRefName: typeof item.headRefName === "string" ? item.headRefName : void 0,
8032
+ headRefOid: typeof item.headRefOid === "string" && COMMIT_ID.test(item.headRefOid) ? item.headRefOid : void 0,
8029
8033
  isCrossRepository: item.isCrossRepository === true,
8030
8034
  files: files.flatMap(
8031
8035
  (file) => isObject(file) && typeof file.path === "string" ? [file.path] : []
@@ -8094,24 +8098,56 @@ function takenIds(ctx, paths, base) {
8094
8098
  gitOrEmpty(git, "ls-tree", "-r", "-z", "--name-only", base, "--", recordsPathspec(paths))
8095
8099
  ).flatMap((path) => path.match(DECISION_MENTION) ?? []);
8096
8100
  const scan = openPrIds(ctx, paths, base);
8097
- const ids = [.../* @__PURE__ */ new Set([...onBase, ...scan.ids])].sort(compareIds);
8098
- return scan.skipped === void 0 ? { ids } : { ids, skipped: scan.skipped };
8101
+ const ids = [.../* @__PURE__ */ new Set([...onBase, ...scan.claims.map(({ id }) => id)])].sort(compareIds);
8102
+ const taken = { ids, onBase: new Set(onBase), claims: scan.claims };
8103
+ return scan.skipped === void 0 ? taken : { ...taken, skipped: scan.skipped };
8104
+ }
8105
+ function isAncestor(git, ancestor, commit2) {
8106
+ try {
8107
+ git("merge-base", "--is-ancestor", ancestor, commit2);
8108
+ return true;
8109
+ } catch (error) {
8110
+ if (error instanceof GitCommandError) return false;
8111
+ throw error;
8112
+ }
8113
+ }
8114
+ function collides(git, base, { path, id }, taken) {
8115
+ if (taken.onBase.has(id)) return true;
8116
+ const claims = taken.claims.filter((claim) => claim.id === id);
8117
+ if (claims.length === 0) return false;
8118
+ const added = git(
8119
+ "--literal-pathspecs",
8120
+ "log",
8121
+ // log.follow or log.showSignature in the user's config would change the output.
8122
+ "--no-follow",
8123
+ "--no-show-signature",
8124
+ "-1",
8125
+ "--format=%H",
8126
+ "--diff-filter=A",
8127
+ `${base}..HEAD`,
8128
+ "--",
8129
+ path
8130
+ ).trim();
8131
+ return claims.some(
8132
+ ({ head }) => head === void 0 || added === "" || !isAncestor(git, added, head)
8133
+ );
8099
8134
  }
8100
8135
  function listTakenIds(ctx, { paths }, base) {
8101
8136
  verifyBase(ctx, paths, base, " Fetch first or pass --base.");
8102
- return takenIds(ctx, paths, base);
8137
+ const { ids, skipped } = takenIds(ctx, paths, base);
8138
+ return skipped === void 0 ? { ids } : { ids, skipped };
8103
8139
  }
8104
8140
  function collisionsOf(ctx, paths, base) {
8105
- const added = localAdditions(gitAt(ctx, paths.root), paths, base);
8141
+ const git = gitAt(ctx, paths.root);
8142
+ const added = localAdditions(git, paths, base);
8106
8143
  const local = added.flatMap((path) => {
8107
8144
  const id = basename4(path, ".md");
8108
8145
  return RECORD_FILE.test(basename4(path)) ? [{ path, id }] : [];
8109
8146
  });
8110
8147
  if (local.length === 0) return { collisions: [], localIds: [], taken: [] };
8111
8148
  const taken = takenIds(ctx, paths, base);
8112
- const takenSet = new Set(taken.ids);
8113
8149
  const result = {
8114
- collisions: local.filter(({ id }) => takenSet.has(id)),
8150
+ collisions: local.filter((record) => collides(git, base, record, taken)),
8115
8151
  localIds: local.map(({ id }) => id),
8116
8152
  taken: taken.ids
8117
8153
  };
@@ -8189,7 +8225,7 @@ function parseRenamePlan(paths, text) {
8189
8225
 
8190
8226
  // src/core/commit-reindex.ts
8191
8227
  var SUBJECT_LIST_LIMIT = 3;
8192
- function reindexMessage(renames, originalSubjects) {
8228
+ function reindexMessage(renames, originalSubjects = "") {
8193
8229
  const pairs = renames.map(({ oldId, newId }) => `${oldId} -> ${newId}`);
8194
8230
  const subject = renames.length > SUBJECT_LIST_LIMIT ? `reindex ${renames.length} local decisions to avoid base-branch collisions` : `reindex local decisions: ${pairs.join(", ")}`;
8195
8231
  const message = `${subject}
@@ -8201,11 +8237,75 @@ ${pairs.map((pair) => `- ${pair}`).join("\n")}`;
8201
8237
  Squashed from original branch commits:
8202
8238
  ${originalSubjects}`;
8203
8239
  }
8204
- function commitReindex(ctx, { paths }, planText, base) {
8205
- const git = gitAt(ctx, paths.root);
8240
+ function commitReindex(ctx, { paths }, planText, base, { squash = false } = {}) {
8206
8241
  verifyBase(ctx, paths, base);
8207
8242
  const renames = parseRenamePlan(paths, planText);
8208
8243
  if (renames.length === 0) throw new DldError("no rename plan on stdin.");
8244
+ for (const { oldId, newId, path } of renames) {
8245
+ const renamed = posix2.join(posix2.dirname(path), `${newId}.md`);
8246
+ if (!ctx.fs.isRegularFile(join7(paths.root, renamed))) {
8247
+ throw new DldError(`${renamed} not found. Run rename-decision for ${oldId} first.`);
8248
+ }
8249
+ }
8250
+ const git = gitAt(ctx, paths.root);
8251
+ return squash ? squashReindex(ctx, git, paths, renames, base) : commitOnTop(ctx, git, paths, renames);
8252
+ }
8253
+ function commitOnTop(ctx, git, paths, renames) {
8254
+ const stage = /* @__PURE__ */ new Set();
8255
+ for (const { path, newId } of renames) {
8256
+ stage.add(path);
8257
+ stage.add(posix2.join(posix2.dirname(path), `${newId}.md`));
8258
+ }
8259
+ for (const file of nulSeparated(git("diff", "-z", "--no-renames", "--name-only", "HEAD"))) {
8260
+ stage.add(file);
8261
+ }
8262
+ const head = git("rev-parse", "HEAD").trim();
8263
+ const tree = git("write-tree").trim();
8264
+ let stepName = "staging";
8265
+ try {
8266
+ for (const path of [...stage].sort()) {
8267
+ stepName = `staging ${path}`;
8268
+ stagePath(ctx, git, join7(paths.root, path), path);
8269
+ }
8270
+ stepName = "checking the staged changes";
8271
+ if (!hasStagedChanges(git)) {
8272
+ throw new DldError(
8273
+ "nothing to commit. The reindex may have already been committed, or the plan didn't match the working tree."
8274
+ );
8275
+ }
8276
+ stepName = "committing";
8277
+ commit(git, reindexMessage(renames));
8278
+ } catch (error) {
8279
+ try {
8280
+ git("read-tree", tree);
8281
+ } catch (restoreError) {
8282
+ throw new DldError(
8283
+ `commit-reindex failed while ${stepName}: ${describe(error)}
8284
+ Restoring the index also failed: ${describe(restoreError)}
8285
+ To restore by hand: git read-tree ${tree}`
8286
+ );
8287
+ }
8288
+ if (!(error instanceof DldError)) throw error;
8289
+ throw new DldError(
8290
+ `commit-reindex failed while ${stepName}: ${error.message}
8291
+ The index was restored.`,
8292
+ error.exitCode
8293
+ );
8294
+ }
8295
+ return {
8296
+ commit: git("rev-parse", "--short", "HEAD").trim(),
8297
+ onto: git("rev-parse", "--short", head).trim()
8298
+ };
8299
+ }
8300
+ function commit(git, message) {
8301
+ try {
8302
+ git("commit", "--quiet", "-m", message);
8303
+ } catch (error) {
8304
+ if (error instanceof GitCommandError) throw new DldError(`git commit failed: ${error.stderr}`);
8305
+ throw error;
8306
+ }
8307
+ }
8308
+ function squashReindex(ctx, git, paths, renames, base) {
8209
8309
  const onto = mergeBase(ctx, paths, base);
8210
8310
  const head = git("rev-parse", "HEAD").trim();
8211
8311
  if (onto === head) throw new DldError("HEAD is already at the merge-base \u2014 nothing to squash.");
@@ -8254,15 +8354,7 @@ function commitReindex(ctx, { paths }, planText, base) {
8254
8354
  );
8255
8355
  }
8256
8356
  });
8257
- step("committing", () => {
8258
- try {
8259
- git("commit", "--quiet", "-m", message);
8260
- } catch (error) {
8261
- if (error instanceof GitCommandError)
8262
- throw new DldError(`git commit failed: ${error.stderr}`);
8263
- throw error;
8264
- }
8265
- });
8357
+ step("committing", () => commit(git, message));
8266
8358
  } catch (error) {
8267
8359
  throw rollback(ctx, git, indexFull, saved, stepName, error);
8268
8360
  }
@@ -8325,23 +8417,35 @@ function skippedNotice(reason) {
8325
8417
  // src/cli/commands/commit-reindex.ts
8326
8418
  var commitReindexCommand = {
8327
8419
  name: "commit-reindex",
8328
- summary: "Squash the branch into one reindex commit",
8329
- usage: `Usage: dld commit-reindex --base <ref> < plan
8420
+ summary: "Commit the renamed decisions",
8421
+ internal: true,
8422
+ usage: `Usage: dld commit-reindex --base <ref> [--squash] < plan
8423
+
8424
+ Read a rename plan on standard input and commit the renames, which rename-decision has
8425
+ already applied. By default this adds one commit on top of HEAD with the plan's paths and
8426
+ every tracked file changed since HEAD; untracked files are left out. On failure the index is
8427
+ restored.
8330
8428
 
8331
- Read a rename plan on standard input and squash the branch's commits since the merge-base
8332
- with <ref> into one reindex commit. INDEX.md is left at its merge-base state. On failure the
8333
- branch, index and INDEX.md are restored.
8429
+ With --squash, the branch's commits since the merge-base with <ref> are squashed into one
8430
+ reindex commit instead, and INDEX.md is left at its merge-base state. On failure the branch,
8431
+ index and INDEX.md are restored.
8334
8432
 
8335
8433
  Options:
8336
8434
  --base <ref> Base ref (required)
8435
+ --squash Squash the branch instead of adding a commit
8337
8436
  `,
8338
8437
  run(args, io, ctx) {
8339
- const { values } = parseCommandArgs({ args: [...args], options: { base: { type: "string" } } });
8438
+ const { values } = parseCommandArgs({
8439
+ args: [...args],
8440
+ options: { base: { type: "string" }, squash: { type: "boolean" } }
8441
+ });
8340
8442
  if (!values.base) throw new DldError("--base is required.");
8341
8443
  const base = baseOption(values.base);
8342
8444
  const project = loadProject(ctx);
8343
- const { commit, onto } = commitReindex(ctx, project, ctx.readStdin(), base);
8344
- io.stdout(`Created reindex commit ${commit} on top of ${onto}
8445
+ const { commit: commit2, onto } = commitReindex(ctx, project, ctx.readStdin(), base, {
8446
+ squash: values.squash === true
8447
+ });
8448
+ io.stdout(`Created reindex commit ${commit2} on top of ${onto}
8345
8449
  `);
8346
8450
  return EXIT_OK;
8347
8451
  }
@@ -8386,6 +8490,7 @@ function createDirectories(ctx, { config, paths }) {
8386
8490
  var createConfigCommand = {
8387
8491
  name: "create-config",
8388
8492
  summary: "Create dld.config.yaml at the project root",
8493
+ internal: true,
8389
8494
  usage: "Usage: dld create-config <flat|namespaced> [namespace ...]\n\nCreate dld.config.yaml. Namespaced mode needs at least one namespace.\n",
8390
8495
  run(args, io, ctx) {
8391
8496
  const { positionals } = parseCommandArgs({
@@ -8443,6 +8548,7 @@ function isSafeDirName(name) {
8443
8548
  var createDecisionCommand = {
8444
8549
  name: "create-decision",
8445
8550
  summary: "Create a proposed decision record",
8551
+ internal: true,
8446
8552
  usage: `Usage: dld create-decision --id <DL-NNN> --title <title> [options]
8447
8553
 
8448
8554
  Create a decision record with status proposed and print its path.
@@ -8494,6 +8600,7 @@ Options:
8494
8600
  var createDirectoriesCommand = {
8495
8601
  name: "create-directories",
8496
8602
  summary: "Create the decisions directory structure from the config",
8603
+ internal: true,
8497
8604
  usage: "Usage: dld create-directories\n\nCreate the decisions and records directories, and one per namespace.\n",
8498
8605
  run(args, io, ctx) {
8499
8606
  parseCommandArgs({ args: [...args], options: {} });
@@ -8562,6 +8669,7 @@ function writeIndex(ctx, paths, content) {
8562
8669
  var createEmptyIndexCommand = {
8563
8670
  name: "create-empty-index",
8564
8671
  summary: "Write an INDEX.md with no decisions",
8672
+ internal: true,
8565
8673
  usage: "Usage: dld create-empty-index\n\nWrite INDEX.md in the decisions directory with only the table header.\n",
8566
8674
  run(args, io, ctx) {
8567
8675
  parseCommandArgs({ args: [...args], options: {} });
@@ -8577,6 +8685,7 @@ var createEmptyIndexCommand = {
8577
8685
  var detectSnapshotChangesCommand = {
8578
8686
  name: "detect-snapshot-changes",
8579
8687
  summary: "Report what changed since the last snapshot",
8688
+ internal: true,
8580
8689
  usage: "Usage: dld detect-snapshot-changes\n\nPrint mode (full or incremental), new_decisions, modified_decisions and commit_range.\n",
8581
8690
  run(args, io, ctx) {
8582
8691
  parseCommandArgs({ args: [...args], options: {} });
@@ -8589,7 +8698,14 @@ var detectSnapshotChangesCommand = {
8589
8698
  import { join as join11, relative as relative6, sep as sep3 } from "node:path";
8590
8699
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
8591
8700
  ".git",
8701
+ // Agent configuration directories, which hold installed skills.
8592
8702
  ".claude",
8703
+ ".agents",
8704
+ ".agent",
8705
+ ".codex",
8706
+ ".cursor",
8707
+ ".opencode",
8708
+ ".pi",
8593
8709
  ".tessl",
8594
8710
  "node_modules",
8595
8711
  "vendor",
@@ -8668,6 +8784,7 @@ function formatAnnotation({ file, line, id }) {
8668
8784
  var findAnnotationsCommand = {
8669
8785
  name: "find-annotations",
8670
8786
  summary: "List every annotation in the codebase",
8787
+ internal: true,
8671
8788
  usage: "Usage: dld find-annotations\n\nPrint <file>:<line>:<DL-NNN> for every annotation, one per line.\n",
8672
8789
  run(args, io, ctx) {
8673
8790
  parseCommandArgs({ args: [...args], options: {} });
@@ -8683,6 +8800,7 @@ var findAnnotationsCommand = {
8683
8800
  var findCollisionsCommand = {
8684
8801
  name: "find-collisions",
8685
8802
  summary: "List locally added decisions whose IDs are taken",
8803
+ internal: true,
8686
8804
  usage: `Usage: dld find-collisions [--base <ref>]
8687
8805
 
8688
8806
  Print <path>\\t<DL-NNN> for each decision added on this branch whose ID is taken on the base
@@ -8710,11 +8828,11 @@ var idNumber2 = (id) => Number.parseInt(id.slice(3), 10);
8710
8828
  function recordsChangedSinceAudit(ctx, { paths }) {
8711
8829
  const git = gitAt(ctx, paths.root);
8712
8830
  const stored = stateString(readStateSection(ctx, paths, "audit"), "commit_hash");
8713
- const commit = resolveStateCommit(git, stored);
8714
- if (commit === void 0) return void 0;
8831
+ const commit2 = resolveStateCommit(git, stored);
8832
+ if (commit2 === void 0) return void 0;
8715
8833
  const records = recordsPathspec(paths);
8716
8834
  return /* @__PURE__ */ new Set([
8717
- ...nulSeparated(git("diff", "-z", "--name-only", commit, "--", records)),
8835
+ ...nulSeparated(git("diff", "-z", "--name-only", commit2, "--", records)),
8718
8836
  ...nulSeparated(git("ls-files", "-z", "--others", "--exclude-standard", "--", records))
8719
8837
  ]);
8720
8838
  }
@@ -8740,15 +8858,16 @@ function findMissingAmends(ctx, project, { all }) {
8740
8858
  }
8741
8859
  function updateAuditState(ctx, { paths }) {
8742
8860
  const timestamp = formatTimestamp(ctx.now());
8743
- const commit = shortHead(ctx, paths.root);
8744
- writeStateSection(ctx, paths, "audit", { last_run: timestamp, commit_hash: commit });
8745
- return { timestamp, commit };
8861
+ const commit2 = shortHead(ctx, paths.root);
8862
+ writeStateSection(ctx, paths, "audit", { last_run: timestamp, commit_hash: commit2 });
8863
+ return { timestamp, commit: commit2 };
8746
8864
  }
8747
8865
 
8748
8866
  // src/cli/commands/find-missing-amends.ts
8749
8867
  var findMissingAmendsCommand = {
8750
8868
  name: "find-missing-amends",
8751
8869
  summary: "List decision IDs mentioned in a body but not declared",
8870
+ internal: true,
8752
8871
  usage: `Usage: dld find-missing-amends [--all]
8753
8872
 
8754
8873
  Print <source-id>:<referenced-id> for each decision ID a record's body mentions without
@@ -8770,7 +8889,7 @@ Options:
8770
8889
  import { Buffer as Buffer2 } from "node:buffer";
8771
8890
  import { join as join12, posix as posix3 } from "node:path";
8772
8891
  var idPattern = (id, flags = "") => new RegExp(`${id}(?![0-9])`, flags);
8773
- function changedFiles(ctx, paths, commit) {
8892
+ function changedFiles(ctx, paths, commit2) {
8774
8893
  return nulSeparated(
8775
8894
  gitAt(ctx, paths.root)(
8776
8895
  "diff",
@@ -8778,7 +8897,7 @@ function changedFiles(ctx, paths, commit) {
8778
8897
  "--find-renames",
8779
8898
  "--name-only",
8780
8899
  "--diff-filter=AMR",
8781
- commit
8900
+ commit2
8782
8901
  )
8783
8902
  );
8784
8903
  }
@@ -8829,7 +8948,7 @@ function renameDecision(ctx, project, rename, base) {
8829
8948
  }
8830
8949
  function findStaleMentions(ctx, { paths }, planText, base) {
8831
8950
  const renames = parseRenamePlan(paths, planText);
8832
- if (renames.length === 0) return [];
8951
+ if (renames.length === 0) throw new DldError("no rename plan on stdin.");
8833
8952
  verifyBase(ctx, paths, base);
8834
8953
  const decisions = `${decisionsPathspec(paths)}/`;
8835
8954
  const files = changedFiles(ctx, paths, mergeBase(ctx, paths, base)).flatMap((file) => {
@@ -8859,6 +8978,7 @@ function formatStaleMention({ file, line, oldId, newId, text }) {
8859
8978
  var findStaleMentionsCommand = {
8860
8979
  name: "find-stale-mentions",
8861
8980
  summary: "List remaining mentions of renamed decision IDs",
8981
+ internal: true,
8862
8982
  usage: `Usage: dld find-stale-mentions --base <ref> < plan
8863
8983
 
8864
8984
  Read a rename plan (<path>\\t<DL-OLD>\\t<DL-NEW> per line) on standard input and print
@@ -9040,7 +9160,6 @@ var AGENTS_LAYOUT = {
9040
9160
  dir: ".agents/skills"
9041
9161
  };
9042
9162
  var LAYOUTS = [CLAUDE_LAYOUT, AGENTS_LAYOUT];
9043
- var AGENTS_FIRST = ["AGENTS.md", "CLAUDE.md"];
9044
9163
  var HARNESSES = [
9045
9164
  {
9046
9165
  name: "claude",
@@ -9048,7 +9167,7 @@ var HARNESSES = [
9048
9167
  layout: CLAUDE_LAYOUT,
9049
9168
  rule: "claude-file",
9050
9169
  markers: [".claude", "CLAUDE.md"],
9051
- // @decision(DL-054) Any of the first three stops Claude Code from reading AGENTS.md.
9170
+ // @decision(DL-054) @decision(DL-055)
9052
9171
  instructions: ["CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md", "AGENTS.md"]
9053
9172
  },
9054
9173
  {
@@ -9073,7 +9192,9 @@ var HARNESSES = [
9073
9192
  layout: AGENTS_LAYOUT,
9074
9193
  rule: "block",
9075
9194
  markers: [".cursor"],
9076
- instructions: ["AGENTS.md"]
9195
+ // @decision(DL-061)
9196
+ instructions: ["AGENTS.md", "CLAUDE.md"],
9197
+ readsAll: true
9077
9198
  },
9078
9199
  {
9079
9200
  name: "opencode",
@@ -9081,7 +9202,8 @@ var HARNESSES = [
9081
9202
  layout: AGENTS_LAYOUT,
9082
9203
  rule: "block",
9083
9204
  markers: [".opencode", "opencode.json", "opencode.jsonc"],
9084
- instructions: AGENTS_FIRST
9205
+ // @decision(DL-061) OpenCode 2.x; 1.x also fell back to CLAUDE.md.
9206
+ instructions: ["AGENTS.md"]
9085
9207
  },
9086
9208
  {
9087
9209
  name: "pi",
@@ -9089,7 +9211,7 @@ var HARNESSES = [
9089
9211
  layout: AGENTS_LAYOUT,
9090
9212
  rule: "block",
9091
9213
  markers: [".pi"],
9092
- instructions: AGENTS_FIRST
9214
+ instructions: ["AGENTS.md", "CLAUDE.md"]
9093
9215
  }
9094
9216
  ];
9095
9217
  var GENERIC_MARKERS = ["AGENTS.md", ".agents/skills"];
@@ -9256,20 +9378,23 @@ function removeEmptyParents(ctx, dir, paths) {
9256
9378
  }
9257
9379
 
9258
9380
  // src/generate/rule.ts
9259
- import { dirname as dirname3, join as join15 } from "node:path";
9381
+ import { dirname as dirname3, join as join15, posix as posix4 } from "node:path";
9260
9382
 
9261
9383
  // templates/rules/dld-workflow.md
9262
- 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";
9384
+ 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\n";
9263
9385
 
9264
9386
  // src/generate/rule.ts
9265
9387
  var RULE_TEXT = dld_workflow_default;
9266
9388
  var RULE_SOURCE = "templates/rules/dld-workflow.md";
9267
9389
  var CLAUDE_RULE_FILE = ".claude/rules/dld-workflow.md";
9268
9390
  var AGENTS_RULE_FILE = ".agents/rules/dld-workflow.md";
9391
+ var CLAUDE_IMPORT_FILE = ".claude/CLAUDE.md";
9269
9392
  var BLOCK_START = "<!-- dld-kit:start -->";
9270
9393
  var BLOCK_END = "<!-- dld-kit:end -->";
9271
9394
  var AGENTS_MD = "AGENTS.md";
9272
9395
  var CLAUDE_MD = "CLAUDE.md";
9396
+ var CLAUDE_FILES = [CLAUDE_MD, CLAUDE_IMPORT_FILE, "CLAUDE.local.md"];
9397
+ var SHARED_CLAUDE_FILES = [CLAUDE_MD, CLAUDE_IMPORT_FILE];
9273
9398
  var RULE_FILES = {
9274
9399
  "claude-file": CLAUDE_RULE_FILE,
9275
9400
  "agents-file": AGENTS_RULE_FILE
@@ -9287,6 +9412,16 @@ function renderRuleFile(channel, text, version2) {
9287
9412
 
9288
9413
  ${text}`;
9289
9414
  }
9415
+ function renderImportFile(version2) {
9416
+ 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. -->
9417
+
9418
+ @../AGENTS.md
9419
+ `;
9420
+ }
9421
+ var IMPORT_STAMP = /^<!-- Generated by dld-kit (\S+) for Claude Code:/;
9422
+ function importFileVersion(content) {
9423
+ return IMPORT_STAMP.exec(content)?.[1];
9424
+ }
9290
9425
  function renderBlock(text, version2) {
9291
9426
  const body = text.endsWith("\n") ? text.slice(0, -1) : text;
9292
9427
  return [BLOCK_START, notice(version2, "block"), "", ...body.split("\n"), BLOCK_END];
@@ -9335,20 +9470,41 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
9335
9470
  let blockFiles = [AGENTS_MD, CLAUDE_MD].filter(
9336
9471
  (file) => regular(file) && findBlock(read(file), file) !== void 0
9337
9472
  );
9338
- if (blockFiles.length === 0 && channels.has("block")) blockFiles = [blockPlacement(ctx, root)];
9473
+ if (blockFiles.length === 0 && channels.has("block")) {
9474
+ const targets = harnesses.filter((h) => h.rule === "block");
9475
+ const file = blockPlacement(ctx, root, targets);
9476
+ blockFiles = [file];
9477
+ plan.warnings.push(...switchWarnings(ctx, root, file, targets));
9478
+ }
9479
+ const pending = /* @__PURE__ */ new Map();
9339
9480
  for (const file of blockFiles) {
9340
9481
  const content = read(file);
9341
9482
  const updated = upsertBlock(content, renderBlock(text, version2), file);
9483
+ pending.set(file, updated);
9342
9484
  if (updated !== content) plan.writes.push({ path: file, content: updated });
9343
9485
  }
9486
+ const linkedDir = symlinkedPart(ctx, root, dirname3(CLAUDE_IMPORT_FILE));
9487
+ const ownsImport = linkedDir === void 0 && importFileVersion(read(CLAUDE_IMPORT_FILE)) !== void 0;
9488
+ const wantsImport = channels.has("claude-file") && blockFiles.includes(AGENTS_MD) && !SHARED_CLAUDE_FILES.some((file) => ctx.fs.lexists(join15(root, file)));
9489
+ if (ownsImport || wantsImport && linkedDir === void 0) {
9490
+ const content = renderImportFile(version2);
9491
+ pending.set(CLAUDE_IMPORT_FILE, content);
9492
+ if (read(CLAUDE_IMPORT_FILE) !== content) {
9493
+ plan.writes.push({ path: CLAUDE_IMPORT_FILE, content });
9494
+ }
9495
+ } else if (wantsImport) {
9496
+ plan.warnings.push(
9497
+ `${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.`
9498
+ );
9499
+ }
9344
9500
  const withBlock = new Set(blockFiles.map(resolved));
9345
- const loads = (file) => withBlock.has(resolved(file));
9346
- const planned = new Set(blockFiles);
9347
- const reads = (harness) => instructionFile(ctx, root, harness, planned);
9348
- const readsBlock = (harness) => {
9349
- const file = reads(harness);
9350
- return file !== void 0 && loads(file);
9501
+ const current = projectView(ctx, root);
9502
+ const view = {
9503
+ exists: (file) => pending.has(file) || current.exists(file),
9504
+ read: (file) => pending.get(file) ?? current.read(file)
9351
9505
  };
9506
+ const reads = (harness) => instructionFiles(harness, view)[0];
9507
+ const readsBlock = (harness) => instructionFiles(harness, view).some((file) => withBlock.has(resolved(file)));
9352
9508
  const ownerOf = (channel) => HARNESSES.find((h) => h.rule === channel);
9353
9509
  const loadsBlock = (channel) => {
9354
9510
  const owner = ownerOf(channel);
@@ -9362,11 +9518,6 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
9362
9518
  reads
9363
9519
  )
9364
9520
  );
9365
- const claude = harnesses.find((h) => h.rule === "claude-file");
9366
- const blockWritten = plan.writes.some((write) => write.path === AGENTS_MD);
9367
- if (claude !== void 0 && reads(claude) === AGENTS_MD && readsBlock(claude) && blockWritten) {
9368
- plan.warnings.push(CLAUDE_READS_AGENTS_NOTE);
9369
- }
9370
9521
  }
9371
9522
  for (const channel of ["claude-file", "agents-file"]) {
9372
9523
  const path = RULE_FILES[channel];
@@ -9382,8 +9533,45 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
9382
9533
  plan.warnings.push(...legacyBlockWarnings(read(CLAUDE_MD), ruleInstalled));
9383
9534
  return plan;
9384
9535
  }
9385
- function instructionFile(ctx, root, harness, planned = /* @__PURE__ */ new Set()) {
9386
- return harness.instructions.find((file) => planned.has(file) || ctx.fs.exists(join15(root, file)));
9536
+ function projectView(ctx, root) {
9537
+ const exists = (file) => ctx.fs.exists(join15(root, file));
9538
+ return {
9539
+ exists,
9540
+ read: (file) => exists(file) && !ctx.fs.isDirectory(join15(root, file)) ? ctx.fs.readFile(join15(root, file)) : ""
9541
+ };
9542
+ }
9543
+ function instructionFiles(harness, view) {
9544
+ if (harness.name !== "claude") {
9545
+ if (harness.readsAll) return harness.instructions.filter((f) => view.exists(f));
9546
+ const file = harness.instructions.find((f) => view.exists(f));
9547
+ return file === void 0 ? [] : [file];
9548
+ }
9549
+ const files = CLAUDE_FILES.filter((file) => view.exists(file));
9550
+ if (files.length === 0) return view.exists(AGENTS_MD) ? [AGENTS_MD] : [];
9551
+ const imported = files.some((file) => importsAgentsMd(view.read(file), file));
9552
+ return imported && view.exists(AGENTS_MD) ? [...files, AGENTS_MD] : files;
9553
+ }
9554
+ var FENCE = /^ {0,3}(`{3,}|~{3,})/;
9555
+ function importsAgentsMd(content, file) {
9556
+ const dir = posix4.dirname(file);
9557
+ let fence;
9558
+ for (const line of content.split(/\r?\n/)) {
9559
+ const marker = FENCE.exec(line)?.[1];
9560
+ if (marker !== void 0) {
9561
+ if (fence === void 0) fence = marker;
9562
+ else if (marker[0] === fence[0] && marker.length >= fence.length) fence = void 0;
9563
+ continue;
9564
+ }
9565
+ if (fence !== void 0) continue;
9566
+ const prose = line.replace(/`[^`]*`/g, "");
9567
+ for (const match of prose.matchAll(/(?:^|\s)@(\S+)/g)) {
9568
+ const target = match[1];
9569
+ if (target !== void 0 && posix4.normalize(posix4.join(dir, target)) === AGENTS_MD) {
9570
+ return true;
9571
+ }
9572
+ }
9573
+ }
9574
+ return false;
9387
9575
  }
9388
9576
  function blindWarnings(blockFiles, blind, reads) {
9389
9577
  const byFile = /* @__PURE__ */ new Map();
@@ -9397,18 +9585,30 @@ function blindWarnings(blockFiles, blind, reads) {
9397
9585
  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.`;
9398
9586
  });
9399
9587
  }
9400
- var CLAUDE_READS_AGENTS_NOTE = "Claude Code reads the DLD rule from the AGENTS.md block, because the project has no CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md. Adding one of those later stops Claude Code from reading AGENTS.md; run dld update then, which installs .claude/rules/dld-workflow.md.";
9401
- function blockPlacement(ctx, root) {
9402
- for (const file of [AGENTS_MD, CLAUDE_MD]) {
9403
- if (ctx.fs.isRegularFile(join15(root, file))) return file;
9588
+ function blockPlacement(ctx, root, targets) {
9589
+ if (ctx.fs.isRegularFile(join15(root, AGENTS_MD))) return AGENTS_MD;
9590
+ const agentsLinked = ctx.fs.lexists(join15(root, AGENTS_MD));
9591
+ if (ctx.fs.isRegularFile(join15(root, CLAUDE_MD)) && (agentsLinked || targets.every((h) => h.instructions.includes(CLAUDE_MD)))) {
9592
+ return CLAUDE_MD;
9404
9593
  }
9405
- if (ctx.fs.lexists(join15(root, AGENTS_MD))) {
9594
+ if (agentsLinked) {
9406
9595
  throw new DldError(
9407
9596
  `${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.`
9408
9597
  );
9409
9598
  }
9410
9599
  return AGENTS_MD;
9411
9600
  }
9601
+ function switchWarnings(ctx, root, file, targets) {
9602
+ if (file !== AGENTS_MD || ctx.fs.lexists(join15(root, AGENTS_MD))) return [];
9603
+ const claudeMd = join15(root, CLAUDE_MD);
9604
+ if (!ctx.fs.exists(claudeMd) || ctx.fs.isDirectory(claudeMd)) return [];
9605
+ const switching = targets.filter((h) => !h.readsAll && h.instructions.includes(CLAUDE_MD));
9606
+ if (switching.length === 0) return [];
9607
+ const names = switching.map((h) => h.title).join(" and ");
9608
+ return [
9609
+ `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.`
9610
+ ];
9611
+ }
9412
9612
  var LEGACY_HEADING = /^## DLD \(Decision-Linked Development\)\s*$/;
9413
9613
  function legacyBlockWarnings(claudeMd, ruleInstalled) {
9414
9614
  const span = findBlock(claudeMd, CLAUDE_MD);
@@ -9423,32 +9623,43 @@ function loadsRule(ctx, root, harness) {
9423
9623
  if (harness.rule !== "block" && ctx.fs.exists(join15(root, RULE_FILES[harness.rule]))) {
9424
9624
  return true;
9425
9625
  }
9426
- const file = instructionFile(ctx, root, harness);
9427
- if (file === void 0) return false;
9428
- return ctx.fs.readFile(join15(root, file)).split(/\r?\n/).some((line) => line === BLOCK_START || LEGACY_HEADING.test(line));
9626
+ const view = projectView(ctx, root);
9627
+ return instructionFiles(harness, view).some(
9628
+ (file) => view.read(file).split(/\r?\n/).some((line) => line === BLOCK_START || LEGACY_HEADING.test(line))
9629
+ );
9429
9630
  }
9430
9631
  function sessionContext(ctx, root, harness, text = RULE_TEXT) {
9431
9632
  if (!ctx.fs.exists(join15(root, CONFIG_FILE))) return void 0;
9432
9633
  return loadsRule(ctx, root, harness) ? void 0 : text;
9433
9634
  }
9434
- function refuseSymlinkedDir(ctx, root, dir) {
9635
+ function symlinkedPart(ctx, root, dir) {
9435
9636
  let current = "";
9436
9637
  for (const part of dir.split("/")) {
9437
9638
  current = current === "" ? part : `${current}/${part}`;
9438
9639
  const full = join15(root, current);
9439
- if (!ctx.fs.lexists(full)) return;
9640
+ if (!ctx.fs.lexists(full)) return void 0;
9440
9641
  if (!ctx.fs.exists(full) || ctx.fs.realPath(full) !== join15(ctx.fs.realPath(root), current)) {
9441
- throw new DldError(
9442
- `${current} is a symlink; dld-kit does not install through symlinks. Replace it with a directory (dld-kit keeps each agent's copy separate).`
9443
- );
9642
+ return current;
9444
9643
  }
9445
9644
  }
9645
+ return void 0;
9646
+ }
9647
+ function refuseSymlinkedDir(ctx, root, dir) {
9648
+ const linked = symlinkedPart(ctx, root, dir);
9649
+ if (linked !== void 0) {
9650
+ throw new DldError(
9651
+ `${linked} is a symlink; dld-kit does not install through symlinks. Replace it with a directory (dld-kit keeps each agent's copy separate).`
9652
+ );
9653
+ }
9446
9654
  }
9447
9655
  function installedRuleChannels(ctx, root) {
9448
9656
  const channels = /* @__PURE__ */ new Set();
9449
9657
  for (const channel of ["claude-file", "agents-file"]) {
9450
9658
  if (ctx.fs.lexists(join15(root, RULE_FILES[channel]))) channels.add(channel);
9451
9659
  }
9660
+ if (importFileVersion(projectView(ctx, root).read(CLAUDE_IMPORT_FILE)) !== void 0) {
9661
+ channels.add("claude-file");
9662
+ }
9452
9663
  for (const file of [AGENTS_MD, CLAUDE_MD]) {
9453
9664
  const path = join15(root, file);
9454
9665
  if (ctx.fs.isRegularFile(path) && findBlock(ctx.fs.readFile(path), file) !== void 0) {
@@ -9463,6 +9674,8 @@ function installedRuleStamps(ctx, root) {
9463
9674
  for (const file of [CLAUDE_RULE_FILE, AGENTS_RULE_FILE].filter(regular)) {
9464
9675
  stamps.push({ path: file, version: ruleVersion(ctx.fs.readFile(join15(root, file))) });
9465
9676
  }
9677
+ const imported = importFileVersion(projectView(ctx, root).read(CLAUDE_IMPORT_FILE));
9678
+ if (imported !== void 0) stamps.push({ path: CLAUDE_IMPORT_FILE, version: imported });
9466
9679
  for (const file of [AGENTS_MD, CLAUDE_MD].filter(regular)) {
9467
9680
  const content = ctx.fs.readFile(join15(root, file));
9468
9681
  const span = findBlock(content, file);
@@ -9705,8 +9918,13 @@ function printReport(io, report2) {
9705
9918
  io.stdout(`${dir}: ${written} written, ${removed} removed, ${unchanged} unchanged
9706
9919
  `);
9707
9920
  }
9708
- for (const path of report2.ruleWritten) io.stdout(`Wrote the DLD rule to ${path}
9709
- `);
9921
+ for (const path of report2.ruleWritten) {
9922
+ io.stdout(
9923
+ path === CLAUDE_IMPORT_FILE ? `Wrote ${path} (imports AGENTS.md for Claude Code)
9924
+ ` : `Wrote the DLD rule to ${path}
9925
+ `
9926
+ );
9927
+ }
9710
9928
  for (const path of report2.ruleRemoved) io.stdout(`Removed ${path} (the rule is in the block)
9711
9929
  `);
9712
9930
  for (const warning of report2.warnings) io.stderr(`Warning: ${warning}
@@ -9835,6 +10053,7 @@ ${AGENT_HELP}
9835
10053
  var listTakenIdsCommand = {
9836
10054
  name: "list-taken-ids",
9837
10055
  summary: "List decision IDs taken on the base branch and in open PRs",
10056
+ internal: true,
9838
10057
  usage: `Usage: dld list-taken-ids [--base <ref>]
9839
10058
 
9840
10059
  Print the decision IDs on the base branch and in records touched by open pull requests
@@ -9858,6 +10077,7 @@ Options:
9858
10077
  var nextIdCommand = {
9859
10078
  name: "next-id",
9860
10079
  summary: "Print the next sequential decision ID",
10080
+ internal: true,
9861
10081
  usage: "Usage: dld next-id\n\nPrint the next decision ID, e.g. DL-004.\n",
9862
10082
  run(args, io, ctx) {
9863
10083
  parseCommandArgs({ args: [...args], options: {} });
@@ -9872,6 +10092,7 @@ var nextIdCommand = {
9872
10092
  var planRenamesCommand = {
9873
10093
  name: "plan-renames",
9874
10094
  summary: "Plan renames that resolve decision ID collisions",
10095
+ internal: true,
9875
10096
  usage: `Usage: dld plan-renames [--base <ref>]
9876
10097
 
9877
10098
  Print <path>\\t<DL-OLD>\\t<DL-NEW> for each colliding local decision, assigning the next free
@@ -9895,6 +10116,7 @@ Options:
9895
10116
  var regenerateIndexCommand = {
9896
10117
  name: "regenerate-index",
9897
10118
  summary: "Rebuild INDEX.md from the decision records",
10119
+ internal: true,
9898
10120
  usage: `Usage: dld regenerate-index [--include-base <ref>]
9899
10121
 
9900
10122
  Rebuild INDEX.md from every decision record.
@@ -9922,6 +10144,7 @@ Options:
9922
10144
  var renameDecisionCommand = {
9923
10145
  name: "rename-decision",
9924
10146
  summary: "Rename a local decision and rewrite its references",
10147
+ internal: true,
9925
10148
  usage: `Usage: dld rename-decision --old <DL-OLD> --new <DL-NEW> --path <path> [--base <ref>]
9926
10149
 
9927
10150
  Rename a locally added decision with git mv, rewrite its id and references in changed
@@ -9957,6 +10180,7 @@ Options:
9957
10180
  var resolveBaseCommand = {
9958
10181
  name: "resolve-base",
9959
10182
  summary: "Print the base ref to check decision IDs against",
10183
+ internal: true,
9960
10184
  usage: `Usage: dld resolve-base
9961
10185
 
9962
10186
  Print the current branch's upstream when it tracks a differently named branch, otherwise
@@ -10068,11 +10292,12 @@ ${AGENT_HELP}
10068
10292
  var updateAuditStateCommand = {
10069
10293
  name: "update-audit-state",
10070
10294
  summary: "Record the audit run in .dld-state.yaml",
10295
+ internal: true,
10071
10296
  usage: "Usage: dld update-audit-state\n\nRecord the current time and HEAD commit as the last audit.\n",
10072
10297
  run(args, io, ctx) {
10073
10298
  parseCommandArgs({ args: [...args], options: {} });
10074
- const { timestamp, commit } = updateAuditState(ctx, loadProject(ctx));
10075
- io.stdout(`Audit state updated: ${timestamp} at ${commit}
10299
+ const { timestamp, commit: commit2 } = updateAuditState(ctx, loadProject(ctx));
10300
+ io.stdout(`Audit state updated: ${timestamp} at ${commit2}
10076
10301
  `);
10077
10302
  return EXIT_OK;
10078
10303
  }
@@ -10082,6 +10307,7 @@ var updateAuditStateCommand = {
10082
10307
  var updateSnapshotStateCommand = {
10083
10308
  name: "update-snapshot-state",
10084
10309
  summary: "Record the snapshot run in .dld-state.yaml",
10310
+ internal: true,
10085
10311
  usage: `Usage: dld update-snapshot-state [artifact ...]
10086
10312
 
10087
10313
  Record the snapshot time, HEAD commit and highest accepted decision, with timestamps for
@@ -10093,8 +10319,8 @@ SNAPSHOT.md, OVERVIEW.md and any custom artifacts named.
10093
10319
  options: {},
10094
10320
  allowPositionals: true
10095
10321
  });
10096
- const { timestamp, commit, highest } = updateSnapshotState(ctx, loadProject(ctx), positionals);
10097
- io.stdout(`Snapshot state updated: ${timestamp} at ${commit} (through ${formatId(highest)})
10322
+ const { timestamp, commit: commit2, highest } = updateSnapshotState(ctx, loadProject(ctx), positionals);
10323
+ io.stdout(`Snapshot state updated: ${timestamp} at ${commit2} (through ${formatId(highest)})
10098
10324
  `);
10099
10325
  return EXIT_OK;
10100
10326
  }
@@ -10104,6 +10330,7 @@ SNAPSHOT.md, OVERVIEW.md and any custom artifacts named.
10104
10330
  var updateStatusCommand = {
10105
10331
  name: "update-status",
10106
10332
  summary: "Set a decision's status",
10333
+ internal: true,
10107
10334
  usage: `Usage: dld update-status <DL-NNN> <${STATUSES.join("|")}>
10108
10335
 
10109
10336
  Change only the status line of a decision record.
@@ -10135,6 +10362,7 @@ var EXIT_MISSING = 1;
10135
10362
  var verifyAnnotationsCommand = {
10136
10363
  name: "verify-annotations",
10137
10364
  summary: "Check that decisions have annotations in the code",
10365
+ internal: true,
10138
10366
  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",
10139
10367
  run(args, io, ctx) {
10140
10368
  const { positionals: ids } = parseCommandArgs({
@@ -10188,11 +10416,19 @@ var COMMANDS = [
10188
10416
  findStaleMentionsCommand,
10189
10417
  commitReindexCommand
10190
10418
  ];
10419
+ var INTERNAL_HEADING = "Commands the skills run (internal; may change in minor releases)";
10420
+ var INTERNAL_NOTE = "Internal: the dld-kit skills run this command. Its name, arguments and output may change in a minor release.\n";
10191
10421
  function usage(commands) {
10422
+ const list = (group) => group.map((c) => ` ${c.name.padEnd(25)} ${c.summary}`).join("\n");
10423
+ const setup = commands.filter((c) => !c.internal);
10424
+ const internal = commands.filter((c) => c.internal);
10425
+ const sections = [`Setup commands:
10426
+ ${list(setup)}`];
10427
+ if (internal.length > 0) sections.push(`${INTERNAL_HEADING}:
10428
+ ${list(internal)}`);
10192
10429
  return `Usage: dld <command> [options]
10193
10430
 
10194
- Commands:
10195
- ${commands.map((c) => ` ${c.name.padEnd(20)} ${c.summary}`).join("\n")}
10431
+ ${sections.join("\n\n")}
10196
10432
 
10197
10433
  Options:
10198
10434
  -h, --help Show this help, or a command's help after its name
@@ -10231,7 +10467,8 @@ ${usage(commands)}`);
10231
10467
  }
10232
10468
  function report(error, command, io) {
10233
10469
  if (error instanceof HelpRequested) {
10234
- io.stdout(command.usage);
10470
+ io.stdout(command.internal ? `${command.usage}
10471
+ ${INTERNAL_NOTE}` : command.usage);
10235
10472
  return EXIT_OK;
10236
10473
  }
10237
10474
  if (error instanceof UsageError) {