dld-kit 1.0.0-rc.2 → 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 +21 -8
- package/dist/dld.mjs +174 -39
- package/package.json +1 -1
- package/skills/dld-adjust/SKILL.md +1 -1
- package/skills/dld-audit/SKILL.md +1 -1
- package/skills/dld-audit-auto/SKILL.md +1 -1
- package/skills/dld-common/SKILL.md +1 -1
- package/skills/dld-common/scripts/dld.mjs +174 -39
- package/skills/dld-decide/SKILL.md +1 -1
- package/skills/dld-implement/SKILL.md +1 -1
- package/skills/dld-init/SKILL.md +1 -1
- package/skills/dld-lookup/SKILL.md +1 -1
- package/skills/dld-plan/SKILL.md +1 -1
- package/skills/dld-reindex/SKILL.md +1 -1
- package/skills/dld-retrofit/SKILL.md +1 -1
- package/skills/dld-snapshot/SKILL.md +1 -1
- package/skills/dld-status/SKILL.md +1 -1
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, 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))
|
|
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,20 +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`, or the block in `
|
|
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
|
|
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
|
|
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
126
|
|
|
127
|
-
Each agent gets the rule once. Claude Code reads `AGENTS.md` only when the project has no `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md
|
|
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.
|
|
128
128
|
|
|
129
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".
|
|
130
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
|
+
|
|
131
142
|
## Get started
|
|
132
143
|
|
|
133
144
|
### New feature or change
|
|
@@ -369,10 +380,12 @@ The `dld` command comes with the npm package (`npx dld-kit <command>`, or `npm i
|
|
|
369
380
|
| `dld update` | Rewrites the installed skills and rule with this version; `--agent` adds agents |
|
|
370
381
|
| `dld install-rule --agent <names>` | Installs or refreshes only the always-on rule |
|
|
371
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) |
|
|
372
|
-
| `dld --help` | Lists
|
|
383
|
+
| `dld --help` | Lists the setup commands above, then the commands the skills run |
|
|
373
384
|
|
|
374
385
|
`init` and `update` refuse to overwrite files from a newer dld-kit unless given `--force`.
|
|
375
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
|
+
|
|
376
389
|
## Development
|
|
377
390
|
|
|
378
391
|
```bash
|
|
@@ -413,7 +426,7 @@ DLD is under active development. Feature requests and ideas are welcome — [ope
|
|
|
413
426
|
|
|
414
427
|
## Manual rule setup
|
|
415
428
|
|
|
416
|
-
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
|
|
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.
|
|
417
430
|
|
|
418
431
|
To add it by hand instead, copy [`templates/rules/dld-workflow.md`](templates/rules/dld-workflow.md) into one of those files.
|
|
419
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.
|
|
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,7 @@ var HARNESSES = [
|
|
|
9048
9065
|
layout: CLAUDE_LAYOUT,
|
|
9049
9066
|
rule: "claude-file",
|
|
9050
9067
|
markers: [".claude", "CLAUDE.md"],
|
|
9051
|
-
// @decision(DL-054)
|
|
9068
|
+
// @decision(DL-054) @decision(DL-055)
|
|
9052
9069
|
instructions: ["CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md", "AGENTS.md"]
|
|
9053
9070
|
},
|
|
9054
9071
|
{
|
|
@@ -9073,7 +9090,9 @@ var HARNESSES = [
|
|
|
9073
9090
|
layout: AGENTS_LAYOUT,
|
|
9074
9091
|
rule: "block",
|
|
9075
9092
|
markers: [".cursor"],
|
|
9076
|
-
|
|
9093
|
+
// @decision(DL-061)
|
|
9094
|
+
instructions: ["AGENTS.md", "CLAUDE.md"],
|
|
9095
|
+
readsAll: true
|
|
9077
9096
|
},
|
|
9078
9097
|
{
|
|
9079
9098
|
name: "opencode",
|
|
@@ -9081,7 +9100,8 @@ var HARNESSES = [
|
|
|
9081
9100
|
layout: AGENTS_LAYOUT,
|
|
9082
9101
|
rule: "block",
|
|
9083
9102
|
markers: [".opencode", "opencode.json", "opencode.jsonc"],
|
|
9084
|
-
|
|
9103
|
+
// @decision(DL-061) OpenCode 2.x; 1.x also fell back to CLAUDE.md.
|
|
9104
|
+
instructions: ["AGENTS.md"]
|
|
9085
9105
|
},
|
|
9086
9106
|
{
|
|
9087
9107
|
name: "pi",
|
|
@@ -9089,7 +9109,7 @@ var HARNESSES = [
|
|
|
9089
9109
|
layout: AGENTS_LAYOUT,
|
|
9090
9110
|
rule: "block",
|
|
9091
9111
|
markers: [".pi"],
|
|
9092
|
-
instructions:
|
|
9112
|
+
instructions: ["AGENTS.md", "CLAUDE.md"]
|
|
9093
9113
|
}
|
|
9094
9114
|
];
|
|
9095
9115
|
var GENERIC_MARKERS = ["AGENTS.md", ".agents/skills"];
|
|
@@ -9256,7 +9276,7 @@ function removeEmptyParents(ctx, dir, paths) {
|
|
|
9256
9276
|
}
|
|
9257
9277
|
|
|
9258
9278
|
// src/generate/rule.ts
|
|
9259
|
-
import { dirname as dirname3, join as join15 } from "node:path";
|
|
9279
|
+
import { dirname as dirname3, join as join15, posix as posix4 } from "node:path";
|
|
9260
9280
|
|
|
9261
9281
|
// templates/rules/dld-workflow.md
|
|
9262
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";
|
|
@@ -9266,10 +9286,13 @@ var RULE_TEXT = dld_workflow_default;
|
|
|
9266
9286
|
var RULE_SOURCE = "templates/rules/dld-workflow.md";
|
|
9267
9287
|
var CLAUDE_RULE_FILE = ".claude/rules/dld-workflow.md";
|
|
9268
9288
|
var AGENTS_RULE_FILE = ".agents/rules/dld-workflow.md";
|
|
9289
|
+
var CLAUDE_IMPORT_FILE = ".claude/CLAUDE.md";
|
|
9269
9290
|
var BLOCK_START = "<!-- dld-kit:start -->";
|
|
9270
9291
|
var BLOCK_END = "<!-- dld-kit:end -->";
|
|
9271
9292
|
var AGENTS_MD = "AGENTS.md";
|
|
9272
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];
|
|
9273
9296
|
var RULE_FILES = {
|
|
9274
9297
|
"claude-file": CLAUDE_RULE_FILE,
|
|
9275
9298
|
"agents-file": AGENTS_RULE_FILE
|
|
@@ -9287,6 +9310,16 @@ function renderRuleFile(channel, text, version2) {
|
|
|
9287
9310
|
|
|
9288
9311
|
${text}`;
|
|
9289
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
|
+
}
|
|
9290
9323
|
function renderBlock(text, version2) {
|
|
9291
9324
|
const body = text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
9292
9325
|
return [BLOCK_START, notice(version2, "block"), "", ...body.split("\n"), BLOCK_END];
|
|
@@ -9335,20 +9368,41 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
|
|
|
9335
9368
|
let blockFiles = [AGENTS_MD, CLAUDE_MD].filter(
|
|
9336
9369
|
(file) => regular(file) && findBlock(read(file), file) !== void 0
|
|
9337
9370
|
);
|
|
9338
|
-
if (blockFiles.length === 0 && channels.has("block"))
|
|
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();
|
|
9339
9378
|
for (const file of blockFiles) {
|
|
9340
9379
|
const content = read(file);
|
|
9341
9380
|
const updated = upsertBlock(content, renderBlock(text, version2), file);
|
|
9381
|
+
pending.set(file, updated);
|
|
9342
9382
|
if (updated !== content) plan.writes.push({ path: file, content: updated });
|
|
9343
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
|
+
}
|
|
9344
9398
|
const withBlock = new Set(blockFiles.map(resolved));
|
|
9345
|
-
const
|
|
9346
|
-
const
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
const file = reads(harness);
|
|
9350
|
-
return file !== void 0 && loads(file);
|
|
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)
|
|
9351
9403
|
};
|
|
9404
|
+
const reads = (harness) => instructionFiles(harness, view)[0];
|
|
9405
|
+
const readsBlock = (harness) => instructionFiles(harness, view).some((file) => withBlock.has(resolved(file)));
|
|
9352
9406
|
const ownerOf = (channel) => HARNESSES.find((h) => h.rule === channel);
|
|
9353
9407
|
const loadsBlock = (channel) => {
|
|
9354
9408
|
const owner = ownerOf(channel);
|
|
@@ -9362,11 +9416,6 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
|
|
|
9362
9416
|
reads
|
|
9363
9417
|
)
|
|
9364
9418
|
);
|
|
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
9419
|
}
|
|
9371
9420
|
for (const channel of ["claude-file", "agents-file"]) {
|
|
9372
9421
|
const path = RULE_FILES[channel];
|
|
@@ -9382,8 +9431,45 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
|
|
|
9382
9431
|
plan.warnings.push(...legacyBlockWarnings(read(CLAUDE_MD), ruleInstalled));
|
|
9383
9432
|
return plan;
|
|
9384
9433
|
}
|
|
9385
|
-
function
|
|
9386
|
-
|
|
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
|
+
};
|
|
9440
|
+
}
|
|
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;
|
|
9387
9473
|
}
|
|
9388
9474
|
function blindWarnings(blockFiles, blind, reads) {
|
|
9389
9475
|
const byFile = /* @__PURE__ */ new Map();
|
|
@@ -9397,18 +9483,30 @@ function blindWarnings(blockFiles, blind, reads) {
|
|
|
9397
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.`;
|
|
9398
9484
|
});
|
|
9399
9485
|
}
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
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;
|
|
9404
9491
|
}
|
|
9405
|
-
if (
|
|
9492
|
+
if (agentsLinked) {
|
|
9406
9493
|
throw new DldError(
|
|
9407
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.`
|
|
9408
9495
|
);
|
|
9409
9496
|
}
|
|
9410
9497
|
return AGENTS_MD;
|
|
9411
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
|
+
}
|
|
9412
9510
|
var LEGACY_HEADING = /^## DLD \(Decision-Linked Development\)\s*$/;
|
|
9413
9511
|
function legacyBlockWarnings(claudeMd, ruleInstalled) {
|
|
9414
9512
|
const span = findBlock(claudeMd, CLAUDE_MD);
|
|
@@ -9423,32 +9521,43 @@ function loadsRule(ctx, root, harness) {
|
|
|
9423
9521
|
if (harness.rule !== "block" && ctx.fs.exists(join15(root, RULE_FILES[harness.rule]))) {
|
|
9424
9522
|
return true;
|
|
9425
9523
|
}
|
|
9426
|
-
const
|
|
9427
|
-
|
|
9428
|
-
|
|
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
|
+
);
|
|
9429
9528
|
}
|
|
9430
9529
|
function sessionContext(ctx, root, harness, text = RULE_TEXT) {
|
|
9431
9530
|
if (!ctx.fs.exists(join15(root, CONFIG_FILE))) return void 0;
|
|
9432
9531
|
return loadsRule(ctx, root, harness) ? void 0 : text;
|
|
9433
9532
|
}
|
|
9434
|
-
function
|
|
9533
|
+
function symlinkedPart(ctx, root, dir) {
|
|
9435
9534
|
let current = "";
|
|
9436
9535
|
for (const part of dir.split("/")) {
|
|
9437
9536
|
current = current === "" ? part : `${current}/${part}`;
|
|
9438
9537
|
const full = join15(root, current);
|
|
9439
|
-
if (!ctx.fs.lexists(full)) return;
|
|
9538
|
+
if (!ctx.fs.lexists(full)) return void 0;
|
|
9440
9539
|
if (!ctx.fs.exists(full) || ctx.fs.realPath(full) !== join15(ctx.fs.realPath(root), current)) {
|
|
9441
|
-
|
|
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
|
-
);
|
|
9540
|
+
return current;
|
|
9444
9541
|
}
|
|
9445
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
|
+
}
|
|
9446
9552
|
}
|
|
9447
9553
|
function installedRuleChannels(ctx, root) {
|
|
9448
9554
|
const channels = /* @__PURE__ */ new Set();
|
|
9449
9555
|
for (const channel of ["claude-file", "agents-file"]) {
|
|
9450
9556
|
if (ctx.fs.lexists(join15(root, RULE_FILES[channel]))) channels.add(channel);
|
|
9451
9557
|
}
|
|
9558
|
+
if (importFileVersion(projectView(ctx, root).read(CLAUDE_IMPORT_FILE)) !== void 0) {
|
|
9559
|
+
channels.add("claude-file");
|
|
9560
|
+
}
|
|
9452
9561
|
for (const file of [AGENTS_MD, CLAUDE_MD]) {
|
|
9453
9562
|
const path = join15(root, file);
|
|
9454
9563
|
if (ctx.fs.isRegularFile(path) && findBlock(ctx.fs.readFile(path), file) !== void 0) {
|
|
@@ -9463,6 +9572,8 @@ function installedRuleStamps(ctx, root) {
|
|
|
9463
9572
|
for (const file of [CLAUDE_RULE_FILE, AGENTS_RULE_FILE].filter(regular)) {
|
|
9464
9573
|
stamps.push({ path: file, version: ruleVersion(ctx.fs.readFile(join15(root, file))) });
|
|
9465
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 });
|
|
9466
9577
|
for (const file of [AGENTS_MD, CLAUDE_MD].filter(regular)) {
|
|
9467
9578
|
const content = ctx.fs.readFile(join15(root, file));
|
|
9468
9579
|
const span = findBlock(content, file);
|
|
@@ -9705,8 +9816,13 @@ function printReport(io, report2) {
|
|
|
9705
9816
|
io.stdout(`${dir}: ${written} written, ${removed} removed, ${unchanged} unchanged
|
|
9706
9817
|
`);
|
|
9707
9818
|
}
|
|
9708
|
-
for (const path of report2.ruleWritten)
|
|
9709
|
-
|
|
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
|
+
}
|
|
9710
9826
|
for (const path of report2.ruleRemoved) io.stdout(`Removed ${path} (the rule is in the block)
|
|
9711
9827
|
`);
|
|
9712
9828
|
for (const warning of report2.warnings) io.stderr(`Warning: ${warning}
|
|
@@ -9835,6 +9951,7 @@ ${AGENT_HELP}
|
|
|
9835
9951
|
var listTakenIdsCommand = {
|
|
9836
9952
|
name: "list-taken-ids",
|
|
9837
9953
|
summary: "List decision IDs taken on the base branch and in open PRs",
|
|
9954
|
+
internal: true,
|
|
9838
9955
|
usage: `Usage: dld list-taken-ids [--base <ref>]
|
|
9839
9956
|
|
|
9840
9957
|
Print the decision IDs on the base branch and in records touched by open pull requests
|
|
@@ -9858,6 +9975,7 @@ Options:
|
|
|
9858
9975
|
var nextIdCommand = {
|
|
9859
9976
|
name: "next-id",
|
|
9860
9977
|
summary: "Print the next sequential decision ID",
|
|
9978
|
+
internal: true,
|
|
9861
9979
|
usage: "Usage: dld next-id\n\nPrint the next decision ID, e.g. DL-004.\n",
|
|
9862
9980
|
run(args, io, ctx) {
|
|
9863
9981
|
parseCommandArgs({ args: [...args], options: {} });
|
|
@@ -9872,6 +9990,7 @@ var nextIdCommand = {
|
|
|
9872
9990
|
var planRenamesCommand = {
|
|
9873
9991
|
name: "plan-renames",
|
|
9874
9992
|
summary: "Plan renames that resolve decision ID collisions",
|
|
9993
|
+
internal: true,
|
|
9875
9994
|
usage: `Usage: dld plan-renames [--base <ref>]
|
|
9876
9995
|
|
|
9877
9996
|
Print <path>\\t<DL-OLD>\\t<DL-NEW> for each colliding local decision, assigning the next free
|
|
@@ -9895,6 +10014,7 @@ Options:
|
|
|
9895
10014
|
var regenerateIndexCommand = {
|
|
9896
10015
|
name: "regenerate-index",
|
|
9897
10016
|
summary: "Rebuild INDEX.md from the decision records",
|
|
10017
|
+
internal: true,
|
|
9898
10018
|
usage: `Usage: dld regenerate-index [--include-base <ref>]
|
|
9899
10019
|
|
|
9900
10020
|
Rebuild INDEX.md from every decision record.
|
|
@@ -9922,6 +10042,7 @@ Options:
|
|
|
9922
10042
|
var renameDecisionCommand = {
|
|
9923
10043
|
name: "rename-decision",
|
|
9924
10044
|
summary: "Rename a local decision and rewrite its references",
|
|
10045
|
+
internal: true,
|
|
9925
10046
|
usage: `Usage: dld rename-decision --old <DL-OLD> --new <DL-NEW> --path <path> [--base <ref>]
|
|
9926
10047
|
|
|
9927
10048
|
Rename a locally added decision with git mv, rewrite its id and references in changed
|
|
@@ -9957,6 +10078,7 @@ Options:
|
|
|
9957
10078
|
var resolveBaseCommand = {
|
|
9958
10079
|
name: "resolve-base",
|
|
9959
10080
|
summary: "Print the base ref to check decision IDs against",
|
|
10081
|
+
internal: true,
|
|
9960
10082
|
usage: `Usage: dld resolve-base
|
|
9961
10083
|
|
|
9962
10084
|
Print the current branch's upstream when it tracks a differently named branch, otherwise
|
|
@@ -10068,6 +10190,7 @@ ${AGENT_HELP}
|
|
|
10068
10190
|
var updateAuditStateCommand = {
|
|
10069
10191
|
name: "update-audit-state",
|
|
10070
10192
|
summary: "Record the audit run in .dld-state.yaml",
|
|
10193
|
+
internal: true,
|
|
10071
10194
|
usage: "Usage: dld update-audit-state\n\nRecord the current time and HEAD commit as the last audit.\n",
|
|
10072
10195
|
run(args, io, ctx) {
|
|
10073
10196
|
parseCommandArgs({ args: [...args], options: {} });
|
|
@@ -10082,6 +10205,7 @@ var updateAuditStateCommand = {
|
|
|
10082
10205
|
var updateSnapshotStateCommand = {
|
|
10083
10206
|
name: "update-snapshot-state",
|
|
10084
10207
|
summary: "Record the snapshot run in .dld-state.yaml",
|
|
10208
|
+
internal: true,
|
|
10085
10209
|
usage: `Usage: dld update-snapshot-state [artifact ...]
|
|
10086
10210
|
|
|
10087
10211
|
Record the snapshot time, HEAD commit and highest accepted decision, with timestamps for
|
|
@@ -10104,6 +10228,7 @@ SNAPSHOT.md, OVERVIEW.md and any custom artifacts named.
|
|
|
10104
10228
|
var updateStatusCommand = {
|
|
10105
10229
|
name: "update-status",
|
|
10106
10230
|
summary: "Set a decision's status",
|
|
10231
|
+
internal: true,
|
|
10107
10232
|
usage: `Usage: dld update-status <DL-NNN> <${STATUSES.join("|")}>
|
|
10108
10233
|
|
|
10109
10234
|
Change only the status line of a decision record.
|
|
@@ -10135,6 +10260,7 @@ var EXIT_MISSING = 1;
|
|
|
10135
10260
|
var verifyAnnotationsCommand = {
|
|
10136
10261
|
name: "verify-annotations",
|
|
10137
10262
|
summary: "Check that decisions have annotations in the code",
|
|
10263
|
+
internal: true,
|
|
10138
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",
|
|
10139
10265
|
run(args, io, ctx) {
|
|
10140
10266
|
const { positionals: ids } = parseCommandArgs({
|
|
@@ -10188,11 +10314,19 @@ var COMMANDS = [
|
|
|
10188
10314
|
findStaleMentionsCommand,
|
|
10189
10315
|
commitReindexCommand
|
|
10190
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";
|
|
10191
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)}`);
|
|
10192
10327
|
return `Usage: dld <command> [options]
|
|
10193
10328
|
|
|
10194
|
-
|
|
10195
|
-
${commands.map((c) => ` ${c.name.padEnd(20)} ${c.summary}`).join("\n")}
|
|
10329
|
+
${sections.join("\n\n")}
|
|
10196
10330
|
|
|
10197
10331
|
Options:
|
|
10198
10332
|
-h, --help Show this help, or a command's help after its name
|
|
@@ -10231,7 +10365,8 @@ ${usage(commands)}`);
|
|
|
10231
10365
|
}
|
|
10232
10366
|
function report(error, command, io) {
|
|
10233
10367
|
if (error instanceof HelpRequested) {
|
|
10234
|
-
io.stdout(command.usage
|
|
10368
|
+
io.stdout(command.internal ? `${command.usage}
|
|
10369
|
+
${INTERNAL_NOTE}` : command.usage);
|
|
10235
10370
|
return EXIT_OK;
|
|
10236
10371
|
}
|
|
10237
10372
|
if (error instanceof UsageError) {
|
package/package.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,7 @@ var HARNESSES = [
|
|
|
9048
9065
|
layout: CLAUDE_LAYOUT,
|
|
9049
9066
|
rule: "claude-file",
|
|
9050
9067
|
markers: [".claude", "CLAUDE.md"],
|
|
9051
|
-
// @decision(DL-054)
|
|
9068
|
+
// @decision(DL-054) @decision(DL-055)
|
|
9052
9069
|
instructions: ["CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md", "AGENTS.md"]
|
|
9053
9070
|
},
|
|
9054
9071
|
{
|
|
@@ -9073,7 +9090,9 @@ var HARNESSES = [
|
|
|
9073
9090
|
layout: AGENTS_LAYOUT,
|
|
9074
9091
|
rule: "block",
|
|
9075
9092
|
markers: [".cursor"],
|
|
9076
|
-
|
|
9093
|
+
// @decision(DL-061)
|
|
9094
|
+
instructions: ["AGENTS.md", "CLAUDE.md"],
|
|
9095
|
+
readsAll: true
|
|
9077
9096
|
},
|
|
9078
9097
|
{
|
|
9079
9098
|
name: "opencode",
|
|
@@ -9081,7 +9100,8 @@ var HARNESSES = [
|
|
|
9081
9100
|
layout: AGENTS_LAYOUT,
|
|
9082
9101
|
rule: "block",
|
|
9083
9102
|
markers: [".opencode", "opencode.json", "opencode.jsonc"],
|
|
9084
|
-
|
|
9103
|
+
// @decision(DL-061) OpenCode 2.x; 1.x also fell back to CLAUDE.md.
|
|
9104
|
+
instructions: ["AGENTS.md"]
|
|
9085
9105
|
},
|
|
9086
9106
|
{
|
|
9087
9107
|
name: "pi",
|
|
@@ -9089,7 +9109,7 @@ var HARNESSES = [
|
|
|
9089
9109
|
layout: AGENTS_LAYOUT,
|
|
9090
9110
|
rule: "block",
|
|
9091
9111
|
markers: [".pi"],
|
|
9092
|
-
instructions:
|
|
9112
|
+
instructions: ["AGENTS.md", "CLAUDE.md"]
|
|
9093
9113
|
}
|
|
9094
9114
|
];
|
|
9095
9115
|
var GENERIC_MARKERS = ["AGENTS.md", ".agents/skills"];
|
|
@@ -9256,7 +9276,7 @@ function removeEmptyParents(ctx, dir, paths) {
|
|
|
9256
9276
|
}
|
|
9257
9277
|
|
|
9258
9278
|
// src/generate/rule.ts
|
|
9259
|
-
import { dirname as dirname3, join as join15 } from "node:path";
|
|
9279
|
+
import { dirname as dirname3, join as join15, posix as posix4 } from "node:path";
|
|
9260
9280
|
|
|
9261
9281
|
// templates/rules/dld-workflow.md
|
|
9262
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";
|
|
@@ -9266,10 +9286,13 @@ var RULE_TEXT = dld_workflow_default;
|
|
|
9266
9286
|
var RULE_SOURCE = "templates/rules/dld-workflow.md";
|
|
9267
9287
|
var CLAUDE_RULE_FILE = ".claude/rules/dld-workflow.md";
|
|
9268
9288
|
var AGENTS_RULE_FILE = ".agents/rules/dld-workflow.md";
|
|
9289
|
+
var CLAUDE_IMPORT_FILE = ".claude/CLAUDE.md";
|
|
9269
9290
|
var BLOCK_START = "<!-- dld-kit:start -->";
|
|
9270
9291
|
var BLOCK_END = "<!-- dld-kit:end -->";
|
|
9271
9292
|
var AGENTS_MD = "AGENTS.md";
|
|
9272
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];
|
|
9273
9296
|
var RULE_FILES = {
|
|
9274
9297
|
"claude-file": CLAUDE_RULE_FILE,
|
|
9275
9298
|
"agents-file": AGENTS_RULE_FILE
|
|
@@ -9287,6 +9310,16 @@ function renderRuleFile(channel, text, version2) {
|
|
|
9287
9310
|
|
|
9288
9311
|
${text}`;
|
|
9289
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
|
+
}
|
|
9290
9323
|
function renderBlock(text, version2) {
|
|
9291
9324
|
const body = text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
9292
9325
|
return [BLOCK_START, notice(version2, "block"), "", ...body.split("\n"), BLOCK_END];
|
|
@@ -9335,20 +9368,41 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
|
|
|
9335
9368
|
let blockFiles = [AGENTS_MD, CLAUDE_MD].filter(
|
|
9336
9369
|
(file) => regular(file) && findBlock(read(file), file) !== void 0
|
|
9337
9370
|
);
|
|
9338
|
-
if (blockFiles.length === 0 && channels.has("block"))
|
|
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();
|
|
9339
9378
|
for (const file of blockFiles) {
|
|
9340
9379
|
const content = read(file);
|
|
9341
9380
|
const updated = upsertBlock(content, renderBlock(text, version2), file);
|
|
9381
|
+
pending.set(file, updated);
|
|
9342
9382
|
if (updated !== content) plan.writes.push({ path: file, content: updated });
|
|
9343
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
|
+
}
|
|
9344
9398
|
const withBlock = new Set(blockFiles.map(resolved));
|
|
9345
|
-
const
|
|
9346
|
-
const
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
const file = reads(harness);
|
|
9350
|
-
return file !== void 0 && loads(file);
|
|
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)
|
|
9351
9403
|
};
|
|
9404
|
+
const reads = (harness) => instructionFiles(harness, view)[0];
|
|
9405
|
+
const readsBlock = (harness) => instructionFiles(harness, view).some((file) => withBlock.has(resolved(file)));
|
|
9352
9406
|
const ownerOf = (channel) => HARNESSES.find((h) => h.rule === channel);
|
|
9353
9407
|
const loadsBlock = (channel) => {
|
|
9354
9408
|
const owner = ownerOf(channel);
|
|
@@ -9362,11 +9416,6 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
|
|
|
9362
9416
|
reads
|
|
9363
9417
|
)
|
|
9364
9418
|
);
|
|
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
9419
|
}
|
|
9371
9420
|
for (const channel of ["claude-file", "agents-file"]) {
|
|
9372
9421
|
const path = RULE_FILES[channel];
|
|
@@ -9382,8 +9431,45 @@ function planRule(ctx, root, channels, version2, { harnesses = [] } = {}, text =
|
|
|
9382
9431
|
plan.warnings.push(...legacyBlockWarnings(read(CLAUDE_MD), ruleInstalled));
|
|
9383
9432
|
return plan;
|
|
9384
9433
|
}
|
|
9385
|
-
function
|
|
9386
|
-
|
|
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
|
+
};
|
|
9440
|
+
}
|
|
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;
|
|
9387
9473
|
}
|
|
9388
9474
|
function blindWarnings(blockFiles, blind, reads) {
|
|
9389
9475
|
const byFile = /* @__PURE__ */ new Map();
|
|
@@ -9397,18 +9483,30 @@ function blindWarnings(blockFiles, blind, reads) {
|
|
|
9397
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.`;
|
|
9398
9484
|
});
|
|
9399
9485
|
}
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
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;
|
|
9404
9491
|
}
|
|
9405
|
-
if (
|
|
9492
|
+
if (agentsLinked) {
|
|
9406
9493
|
throw new DldError(
|
|
9407
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.`
|
|
9408
9495
|
);
|
|
9409
9496
|
}
|
|
9410
9497
|
return AGENTS_MD;
|
|
9411
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
|
+
}
|
|
9412
9510
|
var LEGACY_HEADING = /^## DLD \(Decision-Linked Development\)\s*$/;
|
|
9413
9511
|
function legacyBlockWarnings(claudeMd, ruleInstalled) {
|
|
9414
9512
|
const span = findBlock(claudeMd, CLAUDE_MD);
|
|
@@ -9423,32 +9521,43 @@ function loadsRule(ctx, root, harness) {
|
|
|
9423
9521
|
if (harness.rule !== "block" && ctx.fs.exists(join15(root, RULE_FILES[harness.rule]))) {
|
|
9424
9522
|
return true;
|
|
9425
9523
|
}
|
|
9426
|
-
const
|
|
9427
|
-
|
|
9428
|
-
|
|
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
|
+
);
|
|
9429
9528
|
}
|
|
9430
9529
|
function sessionContext(ctx, root, harness, text = RULE_TEXT) {
|
|
9431
9530
|
if (!ctx.fs.exists(join15(root, CONFIG_FILE))) return void 0;
|
|
9432
9531
|
return loadsRule(ctx, root, harness) ? void 0 : text;
|
|
9433
9532
|
}
|
|
9434
|
-
function
|
|
9533
|
+
function symlinkedPart(ctx, root, dir) {
|
|
9435
9534
|
let current = "";
|
|
9436
9535
|
for (const part of dir.split("/")) {
|
|
9437
9536
|
current = current === "" ? part : `${current}/${part}`;
|
|
9438
9537
|
const full = join15(root, current);
|
|
9439
|
-
if (!ctx.fs.lexists(full)) return;
|
|
9538
|
+
if (!ctx.fs.lexists(full)) return void 0;
|
|
9440
9539
|
if (!ctx.fs.exists(full) || ctx.fs.realPath(full) !== join15(ctx.fs.realPath(root), current)) {
|
|
9441
|
-
|
|
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
|
-
);
|
|
9540
|
+
return current;
|
|
9444
9541
|
}
|
|
9445
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
|
+
}
|
|
9446
9552
|
}
|
|
9447
9553
|
function installedRuleChannels(ctx, root) {
|
|
9448
9554
|
const channels = /* @__PURE__ */ new Set();
|
|
9449
9555
|
for (const channel of ["claude-file", "agents-file"]) {
|
|
9450
9556
|
if (ctx.fs.lexists(join15(root, RULE_FILES[channel]))) channels.add(channel);
|
|
9451
9557
|
}
|
|
9558
|
+
if (importFileVersion(projectView(ctx, root).read(CLAUDE_IMPORT_FILE)) !== void 0) {
|
|
9559
|
+
channels.add("claude-file");
|
|
9560
|
+
}
|
|
9452
9561
|
for (const file of [AGENTS_MD, CLAUDE_MD]) {
|
|
9453
9562
|
const path = join15(root, file);
|
|
9454
9563
|
if (ctx.fs.isRegularFile(path) && findBlock(ctx.fs.readFile(path), file) !== void 0) {
|
|
@@ -9463,6 +9572,8 @@ function installedRuleStamps(ctx, root) {
|
|
|
9463
9572
|
for (const file of [CLAUDE_RULE_FILE, AGENTS_RULE_FILE].filter(regular)) {
|
|
9464
9573
|
stamps.push({ path: file, version: ruleVersion(ctx.fs.readFile(join15(root, file))) });
|
|
9465
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 });
|
|
9466
9577
|
for (const file of [AGENTS_MD, CLAUDE_MD].filter(regular)) {
|
|
9467
9578
|
const content = ctx.fs.readFile(join15(root, file));
|
|
9468
9579
|
const span = findBlock(content, file);
|
|
@@ -9705,8 +9816,13 @@ function printReport(io, report2) {
|
|
|
9705
9816
|
io.stdout(`${dir}: ${written} written, ${removed} removed, ${unchanged} unchanged
|
|
9706
9817
|
`);
|
|
9707
9818
|
}
|
|
9708
|
-
for (const path of report2.ruleWritten)
|
|
9709
|
-
|
|
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
|
+
}
|
|
9710
9826
|
for (const path of report2.ruleRemoved) io.stdout(`Removed ${path} (the rule is in the block)
|
|
9711
9827
|
`);
|
|
9712
9828
|
for (const warning of report2.warnings) io.stderr(`Warning: ${warning}
|
|
@@ -9835,6 +9951,7 @@ ${AGENT_HELP}
|
|
|
9835
9951
|
var listTakenIdsCommand = {
|
|
9836
9952
|
name: "list-taken-ids",
|
|
9837
9953
|
summary: "List decision IDs taken on the base branch and in open PRs",
|
|
9954
|
+
internal: true,
|
|
9838
9955
|
usage: `Usage: dld list-taken-ids [--base <ref>]
|
|
9839
9956
|
|
|
9840
9957
|
Print the decision IDs on the base branch and in records touched by open pull requests
|
|
@@ -9858,6 +9975,7 @@ Options:
|
|
|
9858
9975
|
var nextIdCommand = {
|
|
9859
9976
|
name: "next-id",
|
|
9860
9977
|
summary: "Print the next sequential decision ID",
|
|
9978
|
+
internal: true,
|
|
9861
9979
|
usage: "Usage: dld next-id\n\nPrint the next decision ID, e.g. DL-004.\n",
|
|
9862
9980
|
run(args, io, ctx) {
|
|
9863
9981
|
parseCommandArgs({ args: [...args], options: {} });
|
|
@@ -9872,6 +9990,7 @@ var nextIdCommand = {
|
|
|
9872
9990
|
var planRenamesCommand = {
|
|
9873
9991
|
name: "plan-renames",
|
|
9874
9992
|
summary: "Plan renames that resolve decision ID collisions",
|
|
9993
|
+
internal: true,
|
|
9875
9994
|
usage: `Usage: dld plan-renames [--base <ref>]
|
|
9876
9995
|
|
|
9877
9996
|
Print <path>\\t<DL-OLD>\\t<DL-NEW> for each colliding local decision, assigning the next free
|
|
@@ -9895,6 +10014,7 @@ Options:
|
|
|
9895
10014
|
var regenerateIndexCommand = {
|
|
9896
10015
|
name: "regenerate-index",
|
|
9897
10016
|
summary: "Rebuild INDEX.md from the decision records",
|
|
10017
|
+
internal: true,
|
|
9898
10018
|
usage: `Usage: dld regenerate-index [--include-base <ref>]
|
|
9899
10019
|
|
|
9900
10020
|
Rebuild INDEX.md from every decision record.
|
|
@@ -9922,6 +10042,7 @@ Options:
|
|
|
9922
10042
|
var renameDecisionCommand = {
|
|
9923
10043
|
name: "rename-decision",
|
|
9924
10044
|
summary: "Rename a local decision and rewrite its references",
|
|
10045
|
+
internal: true,
|
|
9925
10046
|
usage: `Usage: dld rename-decision --old <DL-OLD> --new <DL-NEW> --path <path> [--base <ref>]
|
|
9926
10047
|
|
|
9927
10048
|
Rename a locally added decision with git mv, rewrite its id and references in changed
|
|
@@ -9957,6 +10078,7 @@ Options:
|
|
|
9957
10078
|
var resolveBaseCommand = {
|
|
9958
10079
|
name: "resolve-base",
|
|
9959
10080
|
summary: "Print the base ref to check decision IDs against",
|
|
10081
|
+
internal: true,
|
|
9960
10082
|
usage: `Usage: dld resolve-base
|
|
9961
10083
|
|
|
9962
10084
|
Print the current branch's upstream when it tracks a differently named branch, otherwise
|
|
@@ -10068,6 +10190,7 @@ ${AGENT_HELP}
|
|
|
10068
10190
|
var updateAuditStateCommand = {
|
|
10069
10191
|
name: "update-audit-state",
|
|
10070
10192
|
summary: "Record the audit run in .dld-state.yaml",
|
|
10193
|
+
internal: true,
|
|
10071
10194
|
usage: "Usage: dld update-audit-state\n\nRecord the current time and HEAD commit as the last audit.\n",
|
|
10072
10195
|
run(args, io, ctx) {
|
|
10073
10196
|
parseCommandArgs({ args: [...args], options: {} });
|
|
@@ -10082,6 +10205,7 @@ var updateAuditStateCommand = {
|
|
|
10082
10205
|
var updateSnapshotStateCommand = {
|
|
10083
10206
|
name: "update-snapshot-state",
|
|
10084
10207
|
summary: "Record the snapshot run in .dld-state.yaml",
|
|
10208
|
+
internal: true,
|
|
10085
10209
|
usage: `Usage: dld update-snapshot-state [artifact ...]
|
|
10086
10210
|
|
|
10087
10211
|
Record the snapshot time, HEAD commit and highest accepted decision, with timestamps for
|
|
@@ -10104,6 +10228,7 @@ SNAPSHOT.md, OVERVIEW.md and any custom artifacts named.
|
|
|
10104
10228
|
var updateStatusCommand = {
|
|
10105
10229
|
name: "update-status",
|
|
10106
10230
|
summary: "Set a decision's status",
|
|
10231
|
+
internal: true,
|
|
10107
10232
|
usage: `Usage: dld update-status <DL-NNN> <${STATUSES.join("|")}>
|
|
10108
10233
|
|
|
10109
10234
|
Change only the status line of a decision record.
|
|
@@ -10135,6 +10260,7 @@ var EXIT_MISSING = 1;
|
|
|
10135
10260
|
var verifyAnnotationsCommand = {
|
|
10136
10261
|
name: "verify-annotations",
|
|
10137
10262
|
summary: "Check that decisions have annotations in the code",
|
|
10263
|
+
internal: true,
|
|
10138
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",
|
|
10139
10265
|
run(args, io, ctx) {
|
|
10140
10266
|
const { positionals: ids } = parseCommandArgs({
|
|
@@ -10188,11 +10314,19 @@ var COMMANDS = [
|
|
|
10188
10314
|
findStaleMentionsCommand,
|
|
10189
10315
|
commitReindexCommand
|
|
10190
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";
|
|
10191
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)}`);
|
|
10192
10327
|
return `Usage: dld <command> [options]
|
|
10193
10328
|
|
|
10194
|
-
|
|
10195
|
-
${commands.map((c) => ` ${c.name.padEnd(20)} ${c.summary}`).join("\n")}
|
|
10329
|
+
${sections.join("\n\n")}
|
|
10196
10330
|
|
|
10197
10331
|
Options:
|
|
10198
10332
|
-h, --help Show this help, or a command's help after its name
|
|
@@ -10231,7 +10365,8 @@ ${usage(commands)}`);
|
|
|
10231
10365
|
}
|
|
10232
10366
|
function report(error, command, io) {
|
|
10233
10367
|
if (error instanceof HelpRequested) {
|
|
10234
|
-
io.stdout(command.usage
|
|
10368
|
+
io.stdout(command.internal ? `${command.usage}
|
|
10369
|
+
${INTERNAL_NOTE}` : command.usage);
|
|
10235
10370
|
return EXIT_OK;
|
|
10236
10371
|
}
|
|
10237
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.
|
|
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.
|
|
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
|
|
package/skills/dld-init/SKILL.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
package/skills/dld-plan/SKILL.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|