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