opensdd 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/opensdd/cli.md +81 -53
- package/opensdd/skills/sdd-generate.md +5 -5
- package/opensdd/skills/sdd-manager.md +58 -7
- package/opensdd/spec-format.md +68 -28
- package/package.json +2 -2
- package/src/commands/init.js +113 -52
- package/src/commands/install.js +15 -6
- package/src/commands/publish.js +6 -6
- package/src/commands/status.js +1 -1
- package/src/commands/update.js +4 -4
- package/src/commands/updateApply.js +5 -5
- package/src/lib/manifest.js +6 -2
- package/src/lib/skills.js +54 -37
- package/src/lib/validation.js +3 -3
package/opensdd/cli.md
CHANGED
|
@@ -24,41 +24,68 @@ Prints usage information including the current version, available commands, and
|
|
|
24
24
|
|
|
25
25
|
### `opensdd init`
|
|
26
26
|
|
|
27
|
-
Initializes the OpenSDD protocol in the current project.
|
|
27
|
+
Initializes the OpenSDD protocol in the current project. Supports two modes:
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
1. Verify the current directory is a project root (contains `package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `.git`, `opensdd.json`, or similar project markers). If no project marker is found, warn the user and ask for confirmation to proceed.
|
|
32
|
-
2. Install both skills (sdd-manager and sdd-generate) into all supported agent configuration directories. If already present, overwrite — skills are always spec-owned. The full installation mapping is defined in the **Skill Installation Mapping** section below.
|
|
33
|
-
3. If `opensdd.json` does not exist at the project root, create it with default contents: `{ "opensdd": "0.1.0", "specs_dir": "opensdd", "deps_dir": ".opensdd.deps" }`. The `registry`, `publish`, and `dependencies` fields are intentionally omitted from the default — they are optional. When `registry` is absent, the CLI defaults to `https://github.com/deepagents-ai/opensdd` per the registry source resolution logic. If `opensdd.json` already exists, leave it untouched.
|
|
34
|
-
4. Create the `opensdd/` directory (or the directory specified by `specs_dir` in an existing `opensdd.json`) if it does not exist.
|
|
35
|
-
5. If `<specs_dir>/spec.md` does not exist, create a skeleton `spec.md` with placeholder sections:
|
|
36
|
-
```markdown
|
|
37
|
-
# {project-name}
|
|
38
|
-
|
|
39
|
-
> TODO: One-line description of what this software does.
|
|
40
|
-
|
|
41
|
-
## Behavioral Contract
|
|
29
|
+
- **Consumer-only**: Install and implement dependency specs. Minimal footprint — only `sdd-manager` skill installed, no specs directory or skeleton `spec.md`.
|
|
30
|
+
- **OpenSDD-driven**: Full SDD methodology adoption. Both skills installed, specs directory and skeleton `spec.md` created.
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
Mode detection: presence of `specsDir` in `opensdd.json` = OpenSDD-driven. Absence = consumer-only.
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<!-- List aspects left to the implementer's discretion. -->
|
|
48
|
-
|
|
49
|
-
## Invariants
|
|
50
|
-
|
|
51
|
-
<!-- List properties that must hold true across all inputs and states. -->
|
|
52
|
-
```
|
|
53
|
-
The `{project-name}` placeholder SHOULD be inferred from the nearest project manifest (e.g., `name` in `package.json`) or default to the directory name. If `spec.md` already exists, leave it untouched.
|
|
54
|
-
6. Create the `.opensdd.deps/` directory (or the directory specified by `deps_dir` in an existing `opensdd.json`) if it does not exist.
|
|
55
|
-
7. Print a success message.
|
|
34
|
+
#### Behavior
|
|
56
35
|
|
|
57
|
-
|
|
58
|
-
|
|
36
|
+
1. Verify the current directory is a project root (contains `package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `.git`, `opensdd.json`, or similar project markers). If no project marker is found, warn the user and ask for confirmation to proceed.
|
|
37
|
+
2. Read existing `opensdd.json` if present.
|
|
38
|
+
3. Determine mode:
|
|
39
|
+
- **Existing manifest with `specsDir`** (OpenSDD-driven re-init): `mode = 'full'`. No prompt.
|
|
40
|
+
- **Existing manifest without `specsDir`** (consumer re-init): prompt "Upgrade to OpenSDD-driven? (y/n)". If yes, add `specsDir` to manifest and `mode = 'full'`. If no, `mode = 'consumer'`.
|
|
41
|
+
- **No manifest** (fresh init): prompt "How will this project use OpenSDD?" with numbered choices:
|
|
42
|
+
1. Consumer only — install and implement dependency specs
|
|
43
|
+
2. OpenSDD-driven — full SDD methodology (author specs, both skills)
|
|
44
|
+
Create manifest accordingly.
|
|
45
|
+
4. Install skills with the determined mode. In consumer mode, only `sdd-manager` is installed. In full mode, both `sdd-manager` and `sdd-generate` are installed. The full installation mapping is defined in the **Skill Installation Mapping** section below. If already present, overwrite — skills are always spec-owned.
|
|
46
|
+
5. Create the `.opensdd.deps/` directory (or the directory specified by `depsDir` in an existing `opensdd.json`) if it does not exist. (Both modes.)
|
|
47
|
+
6. If `mode === 'full'`:
|
|
48
|
+
a. If `opensdd.json` does not exist at the project root, create it with default contents: `{ "opensdd": "0.1.0", "specsDir": "opensdd", "depsDir": ".opensdd.deps" }`. The `registry`, `publish`, and `dependencies` fields are intentionally omitted from the default — they are optional. When `registry` is absent, the CLI defaults to `https://github.com/deepagents-ai/opensdd` per the registry source resolution logic. If `opensdd.json` already exists, leave it untouched.
|
|
49
|
+
b. Create the `opensdd/` directory (or the directory specified by `specsDir` in the `opensdd.json`) if it does not exist.
|
|
50
|
+
c. If `<specsDir>/spec.md` does not exist, create a skeleton `spec.md` with placeholder sections:
|
|
51
|
+
```markdown
|
|
52
|
+
# {project-name}
|
|
53
|
+
|
|
54
|
+
> TODO: One-line description of what this software does.
|
|
55
|
+
|
|
56
|
+
## Behavioral Contract
|
|
57
|
+
|
|
58
|
+
<!-- Define behaviors here. -->
|
|
59
|
+
|
|
60
|
+
## NOT Specified (Implementation Freedom)
|
|
61
|
+
|
|
62
|
+
<!-- List aspects left to the implementer's discretion. -->
|
|
63
|
+
|
|
64
|
+
## Invariants
|
|
65
|
+
|
|
66
|
+
<!-- List properties that must hold true across all inputs and states. -->
|
|
67
|
+
```
|
|
68
|
+
The `{project-name}` placeholder SHOULD be inferred from the nearest project manifest (e.g., `name` in `package.json`) or default to the directory name. If `spec.md` already exists, leave it untouched.
|
|
69
|
+
7. If `mode === 'consumer'`: create `opensdd.json` with consumer-only contents: `{ "opensdd": "0.1.0", "depsDir": ".opensdd.deps" }` (no `specsDir`). If `opensdd.json` already exists, leave it untouched.
|
|
70
|
+
8. Print a success message.
|
|
71
|
+
|
|
72
|
+
- `opensdd init` selecting OpenSDD-driven in a fresh project MUST produce the full output below
|
|
73
|
+
- `opensdd init` selecting consumer-only in a fresh project MUST produce the consumer output below
|
|
74
|
+
- `opensdd init` in a project that already has OpenSDD initialized (with `specsDir`) MUST overwrite all skill installation files across all agent formats but MUST NOT overwrite `opensdd.json`
|
|
75
|
+
- `opensdd init` in a consumer project MUST prompt to upgrade to OpenSDD-driven
|
|
59
76
|
|
|
60
77
|
#### Output
|
|
61
78
|
|
|
79
|
+
Consumer-only (fresh):
|
|
80
|
+
```
|
|
81
|
+
Initialized OpenSDD (consumer):
|
|
82
|
+
Skills installed for: Claude Code, Codex CLI, Cursor, GitHub Copilot, Gemini CLI, Amp
|
|
83
|
+
sdd-manager installed (6 agent formats)
|
|
84
|
+
opensdd.json created
|
|
85
|
+
.opensdd.deps/ created
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
OpenSDD-driven (fresh):
|
|
62
89
|
```
|
|
63
90
|
Initialized OpenSDD:
|
|
64
91
|
Skills installed for: Claude Code, Codex CLI, Cursor, GitHub Copilot, Gemini CLI, Amp
|
|
@@ -70,7 +97,7 @@ Initialized OpenSDD:
|
|
|
70
97
|
.opensdd.deps/ created
|
|
71
98
|
```
|
|
72
99
|
|
|
73
|
-
|
|
100
|
+
OpenSDD-driven (re-init):
|
|
74
101
|
```
|
|
75
102
|
Initialized OpenSDD:
|
|
76
103
|
Skills installed for: Claude Code, Codex CLI, Cursor, GitHub Copilot, Gemini CLI, Amp
|
|
@@ -89,7 +116,7 @@ Initialized OpenSDD:
|
|
|
89
116
|
|
|
90
117
|
### Skill Installation Mapping
|
|
91
118
|
|
|
92
|
-
`opensdd init` installs
|
|
119
|
+
`opensdd init` installs skills into the native configuration format of each supported coding agent. In **consumer mode**, only `sdd-manager` is installed. In **full (OpenSDD-driven) mode**, both `sdd-manager` and `sdd-generate` are installed. The canonical skill content is authored as markdown source files in `opensdd/skills/` (`skills/sdd-manager.md`, `skills/sdd-generate.md`) and `opensdd/` (`spec-format.md`). Source skill files use Agent Skills frontmatter (`name`, `description`) which the CLI parses and transforms per-agent format during installation.
|
|
93
120
|
|
|
94
121
|
All installed skill files are **spec-owned** — they are overwritten on every `opensdd init` and MUST NOT be edited by the user.
|
|
95
122
|
|
|
@@ -269,13 +296,13 @@ Fetches a spec from the registry and installs it as a dependency.
|
|
|
269
296
|
|
|
270
297
|
#### Behavior
|
|
271
298
|
|
|
272
|
-
1. Verify `opensdd.json` exists at the project root. If not,
|
|
273
|
-
2. Check if the spec `<name>` already exists as a key in `opensdd.json`'s `dependencies` object. If it does AND the spec directory exists in `<
|
|
299
|
+
1. Verify `opensdd.json` exists at the project root. If not, auto-bootstrap as a consumer project: create a minimal `opensdd.json` (no `specsDir`), install consumer-mode skills, create the `.opensdd.deps/` directory, print "Auto-initialized OpenSDD (consumer).", and continue with the normal install flow.
|
|
300
|
+
2. Check if the spec `<name>` already exists as a key in `opensdd.json`'s `dependencies` object. If it does AND the spec directory exists in `<depsDir>`, print a message indicating the spec is already installed and suggest `opensdd update` instead. Exit with code 1. If the entry exists BUT the spec directory is missing, treat as a re-install: log a message noting the stale entry, then continue to step 4 using the version from the existing entry (unless `[version]` is explicitly provided, in which case use that).
|
|
274
301
|
3. Validate the spec name (lowercase alphanumeric and hyphens only).
|
|
275
302
|
4. Fetch `index.json` from `registry/<name>/` in the configured registry source. If `[version]` is provided, use that version; otherwise use `latest` from `index.json`.
|
|
276
|
-
5. Read `manifest.json` from `registry/<name>/<version>/` to get
|
|
277
|
-
6. Copy all files from `registry/<name>/<version>/` into `<
|
|
278
|
-
7. Add an entry to `opensdd.json` under `dependencies.<name>` with fields from `manifest.json` (`version`, `
|
|
303
|
+
5. Read `manifest.json` from `registry/<name>/<version>/` to get specFormat and dependencies.
|
|
304
|
+
6. Copy all files from `registry/<name>/<version>/` into `<depsDir>/<name>/` (including `manifest.json`, `spec.md`, and any supplementary files).
|
|
305
|
+
7. Add an entry to `opensdd.json` under `dependencies.<name>` with fields from `manifest.json` (`version`, `specFormat`), the resolved registry URL as `source`, and consumer-managed fields initialized to defaults: `implementation: null`, `tests: null`, `hasDeviations: false`.
|
|
279
306
|
8. If the spec has `dependencies`, check whether each dependency name exists as a key in `opensdd.json`'s `dependencies` object. If any are missing, print a warning listing the missing dependencies and suggesting `opensdd install` for each.
|
|
280
307
|
9. Print a success message.
|
|
281
308
|
|
|
@@ -300,7 +327,6 @@ Run "implement the slugify spec" in your agent to generate an implementation.
|
|
|
300
327
|
- Spec not found in registry: print error listing available specs and exit with code 1.
|
|
301
328
|
- Requested version not found: print error listing available versions and exit with code 1.
|
|
302
329
|
- Spec already installed (entry and directory both exist): print message suggesting `opensdd update` and exit with code 1.
|
|
303
|
-
- OpenSDD not initialized: print message suggesting `opensdd init` and exit with code 1.
|
|
304
330
|
|
|
305
331
|
### `opensdd update [name]`
|
|
306
332
|
|
|
@@ -310,15 +336,15 @@ Fetches the latest version of installed dependency specs from the registry, upda
|
|
|
310
336
|
|
|
311
337
|
1. If `<name>` is provided, update that single spec. If no name is provided, update all installed dependencies.
|
|
312
338
|
2. For each spec being updated:
|
|
313
|
-
a. Read the spec's entry in `opensdd.json` `dependencies` to get the installed version and `
|
|
339
|
+
a. Read the spec's entry in `opensdd.json` `dependencies` to get the installed version and `specFormat` version.
|
|
314
340
|
b. Fetch `index.json` from the registry to get the latest version. Read `manifest.json` from the latest version directory.
|
|
315
341
|
c. If the registry version matches the installed version, skip with a message "already up to date".
|
|
316
342
|
d. Before overwriting, compute unified diffs of all spec-owned files that will change.
|
|
317
|
-
e. Overwrite all spec-owned files in `<
|
|
343
|
+
e. Overwrite all spec-owned files in `<depsDir>/<name>/` with the new version from the registry (`manifest.json`, `spec.md`, and any supplementary files).
|
|
318
344
|
f. MUST NOT overwrite or delete `deviations.md`. The CLI MUST NOT create, modify, or delete `deviations.md` under any circumstances.
|
|
319
345
|
g. Create the staging directory `.opensdd.deps/.updates/<name>/` and write two files. If a pending update already exists for this spec, overwrite it and note the replacement in the output.
|
|
320
|
-
- `changeset.md` — contains previous and new version, `
|
|
321
|
-
- `manifest.json` — contains the metadata needed to finalize the update in `opensdd.json`: `name`, `
|
|
346
|
+
- `changeset.md` — contains previous and new version, `specFormat` version change (if any), and unified diffs from step (d).
|
|
347
|
+
- `manifest.json` — contains the metadata needed to finalize the update in `opensdd.json`: `name`, `previousVersion`, `version`, `source`, `specFormat`.
|
|
322
348
|
3. Print a summary of what was updated.
|
|
323
349
|
|
|
324
350
|
#### Input
|
|
@@ -376,7 +402,7 @@ Applies a staged update to `opensdd.json`, confirming that the migration is comp
|
|
|
376
402
|
3. Prompt the user for confirmation (y/n). If declined, exit with code 0.
|
|
377
403
|
4. For each pending update:
|
|
378
404
|
a. Read `.opensdd.deps/.updates/<name>/manifest.json` to get the update metadata.
|
|
379
|
-
b. Update the `opensdd.json` `dependencies.<name>` entry: set `version`, `source`, and `
|
|
405
|
+
b. Update the `opensdd.json` `dependencies.<name>` entry: set `version`, `source`, and `specFormat` from the manifest. Preserve all consumer-managed fields (`implementation`, `tests`, `hasDeviations`).
|
|
380
406
|
c. Delete the `.opensdd.deps/.updates/<name>/` directory.
|
|
381
407
|
5. If `.opensdd.deps/.updates/` is now empty, delete it.
|
|
382
408
|
6. Print a summary.
|
|
@@ -431,14 +457,14 @@ Publishes an authored spec to the registry.
|
|
|
431
457
|
|
|
432
458
|
1. Verify `opensdd.json` exists at the project root. If not, print a message suggesting `opensdd init` first and exit with code 1.
|
|
433
459
|
2. Verify `opensdd.json` has a `publish` object. If not, print an error suggesting the user add a `publish` section and exit with code 1.
|
|
434
|
-
3. Read the `publish` object to get `name`, `version`, `description`, `
|
|
435
|
-
4. Verify `<
|
|
436
|
-
5. Run validation on the `<
|
|
460
|
+
3. Read the `publish` object to get `name`, `version`, `description`, `specFormat`, `dependencies`.
|
|
461
|
+
4. Verify `<specsDir>/spec.md` exists. If not, print error and exit with code 1.
|
|
462
|
+
5. Run validation on the `<specsDir>/` directory (same logic as `opensdd validate`). If validation fails with errors, print them and exit with code 1.
|
|
437
463
|
6. Resolve the registry source. The registry MUST be a GitHub repository URL for publishing.
|
|
438
464
|
7. Fetch `index.json` from `registry/<name>/` if it exists. If the version being published already exists in `index.json`, print error suggesting a version bump and exit with code 1.
|
|
439
465
|
8. Construct the registry entry:
|
|
440
|
-
a. Build `manifest.json` from the `opensdd.json` `publish` fields (`name`, `version`, `description`, `
|
|
441
|
-
b. Collect all files from `<
|
|
466
|
+
a. Build `manifest.json` from the `opensdd.json` `publish` fields (`name`, `version`, `description`, `specFormat`, `dependencies`).
|
|
467
|
+
b. Collect all files from `<specsDir>/`.
|
|
442
468
|
9. If `--branch <name>` was provided, use that as the branch name. Otherwise, prompt the user for a branch name.
|
|
443
469
|
10. Clone the registry repo (shallow), create a new branch with the chosen name, and:
|
|
444
470
|
a. Create `registry/<name>/<version>/` with `manifest.json` and all spec files.
|
|
@@ -472,7 +498,7 @@ Published. Spec will be available after PR is merged.
|
|
|
472
498
|
|
|
473
499
|
- OpenSDD not initialized: print message suggesting `opensdd init` and exit with code 1.
|
|
474
500
|
- No `publish` section in `opensdd.json`: print error and exit with code 1.
|
|
475
|
-
- `<
|
|
501
|
+
- `<specsDir>/spec.md` missing: print error and exit with code 1.
|
|
476
502
|
- Spec validation fails: print validation errors and exit with code 1.
|
|
477
503
|
- Version already exists in registry: print error suggesting version bump and exit with code 1.
|
|
478
504
|
- Registry is not a GitHub URL: print error (publishing requires a GitHub registry) and exit with code 1.
|
|
@@ -487,8 +513,8 @@ Shows the status of the authored spec and all installed dependency specs in the
|
|
|
487
513
|
|
|
488
514
|
1. Read `opensdd.json`.
|
|
489
515
|
2. If `publish` exists, print authored spec section showing the spec's name, version, and directory.
|
|
490
|
-
3. If `dependencies` exists and has entries, iterate the `dependencies` object. For each entry, read its consumer-managed fields and check for the presence of `deviations.md` in `<
|
|
491
|
-
4. Check for untracked directories: spec directories in `<
|
|
516
|
+
3. If `dependencies` exists and has entries, iterate the `dependencies` object. For each entry, read its consumer-managed fields and check for the presence of `deviations.md` in `<depsDir>/<name>/`. Print a dependency status table.
|
|
517
|
+
4. Check for untracked directories: spec directories in `<depsDir>` that have no corresponding `opensdd.json` dependency entry. Warn about any found.
|
|
492
518
|
|
|
493
519
|
#### Output
|
|
494
520
|
|
|
@@ -520,7 +546,7 @@ Validates that a spec directory conforms to the OpenSDD spec-format.
|
|
|
520
546
|
3. Check for required files: `spec.md` MUST exist.
|
|
521
547
|
4. If `manifest.json` exists, validate it:
|
|
522
548
|
a. `name` MUST be present.
|
|
523
|
-
b. `
|
|
549
|
+
b. `specFormat` MUST be present and be a recognized version.
|
|
524
550
|
c. `version` MUST be present and be valid semver.
|
|
525
551
|
5. Validate `spec.md`:
|
|
526
552
|
a. MUST start with an H1 header followed by a blockquote summary.
|
|
@@ -607,7 +633,7 @@ For local paths, the CLI MUST read directly from the filesystem. This supports d
|
|
|
607
633
|
During `opensdd update apply`, the CLI MUST preserve these `opensdd.json` fields for each affected dependency entry:
|
|
608
634
|
- `implementation`
|
|
609
635
|
- `tests`
|
|
610
|
-
- `
|
|
636
|
+
- `hasDeviations`
|
|
611
637
|
|
|
612
638
|
The CLI reads the existing `opensdd.json` dependency entry, applies updated metadata from the staged manifest, then re-applies the consumer-managed field values. Note that `opensdd update` does NOT touch `opensdd.json` at all — it only stages the update. The `opensdd.json` entry continues to reflect the old version until `opensdd update apply` is called.
|
|
613
639
|
|
|
@@ -620,7 +646,9 @@ The CLI reads the existing `opensdd.json` dependency entry, applies updated meta
|
|
|
620
646
|
- Running `opensdd update` when a pending update already exists for the spec: overwrite the existing staged update with the new one.
|
|
621
647
|
- Running `opensdd update apply` when no pending updates exist: print "No pending updates." and exit with code 0 (not an error).
|
|
622
648
|
- Running `opensdd update apply <name>` when the agent hasn't finished processing the changeset: the CLI has no way to verify this — it's the user's responsibility to confirm the migration is complete before applying.
|
|
623
|
-
- Running `opensdd init` in a project that already has OpenSDD initialized: overwrite all skill installation files across all agent formats, leave `opensdd.json` untouched.
|
|
649
|
+
- Running `opensdd init` in a project that already has OpenSDD initialized (with `specsDir`): overwrite all skill installation files across all agent formats, leave `opensdd.json` untouched.
|
|
650
|
+
- Running `opensdd init` in a consumer-only project: prompt to upgrade to OpenSDD-driven. If declined, re-install consumer skills only.
|
|
651
|
+
- Running `opensdd install` in an uninitialized project: auto-bootstrap as consumer, then continue with install.
|
|
624
652
|
- Running any command outside a project directory (no project markers found): warn but allow with confirmation, except `opensdd list` and `opensdd validate` which work anywhere.
|
|
625
653
|
- Spec name contains characters invalid for directory names: reject with an error listing allowed characters (lowercase alphanumeric and hyphens).
|
|
626
654
|
- Publishing a version that already exists in the registry: reject with an error suggesting a version bump.
|
|
@@ -651,7 +679,7 @@ The CLI reads the existing `opensdd.json` dependency entry, applies updated meta
|
|
|
651
679
|
- The Claude Code skill installation (`.claude/skills/`) MUST always be present since Gemini CLI and Amp reference it
|
|
652
680
|
- `opensdd.json` MUST be created by `opensdd init` if it does not exist, and MUST NOT be overwritten if it already exists
|
|
653
681
|
- Consumer-managed `opensdd.json` fields MUST survive all update operations
|
|
654
|
-
- Every installed dependency MUST have both a directory in `
|
|
682
|
+
- Every installed dependency MUST have both a directory in `depsDir` and an entry in `opensdd.json` `dependencies`
|
|
655
683
|
- All commands MUST exit with code 0 on success and code 1 on error
|
|
656
684
|
- The CLI MUST NOT invoke an AI model or coding agent
|
|
657
685
|
- `opensdd publish` MUST NOT allow overwriting an existing version in the registry
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sdd-generate
|
|
3
|
-
description: "
|
|
3
|
+
description: "Extract an OpenSDD behavioral spec from an existing codebase. Use when the user asks to generate, create, or extract a spec from a repository or codebase."
|
|
4
4
|
---
|
|
5
5
|
# SDD Generate
|
|
6
6
|
|
|
7
|
-
> Guides an AI agent through analyzing
|
|
7
|
+
> Guides an AI agent through analyzing an existing codebase and extracting a behavioral spec in the OpenSDD format.
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
@@ -17,7 +17,7 @@ Before starting, you need:
|
|
|
17
17
|
1. A **target repository** — a GitHub URL or local path provided by the user.
|
|
18
18
|
2. A **scope** — which capability, module, or function to spec. If the user provides a whole-repo URL without scoping, ask them to narrow it before proceeding. A spec for "the entire lodash library" is not useful. A spec for "lodash's string slugification" is.
|
|
19
19
|
3. The **spec-format reference** — read [spec-format.md](../spec-format.md) to understand the required output structure.
|
|
20
|
-
4. A **working directory** — confirm with the user where the generated spec should be written. If the project has `opensdd.json`, use the directory specified by `
|
|
20
|
+
4. A **working directory** — confirm with the user where the generated spec should be written. If the project has `opensdd.json`, use the directory specified by `specsDir` (default: `opensdd/`). If the project is not yet initialized, ask the user where to output the spec — a common default is `opensdd/` in the current project root. The agent does not need `opensdd.json` to exist before generating a spec.
|
|
21
21
|
|
|
22
22
|
## Output
|
|
23
23
|
|
|
@@ -38,7 +38,7 @@ If `opensdd.json` exists, add or update the `publish` object:
|
|
|
38
38
|
"name": "{name}",
|
|
39
39
|
"version": "{semver}",
|
|
40
40
|
"description": "{one-line description}",
|
|
41
|
-
"
|
|
41
|
+
"specFormat": "0.1.0",
|
|
42
42
|
"dependencies": []
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -164,7 +164,7 @@ Always update `_notes/gaps.md` at the end of each pass with what still needs to
|
|
|
164
164
|
- `## NOT Specified (Implementation Freedom)` — list implementation choices observed in the source that the spec intentionally leaves open (data structures, algorithms, caching, internal architecture)
|
|
165
165
|
- `## Invariants` — universal properties extracted from property tests, type constraints, or behavioral patterns (e.g., idempotency, commutativity, output format guarantees)
|
|
166
166
|
- `## Implementation Hints` (optional) — guidance on performance, concurrency, or common pitfalls observed in the source, only if genuinely useful. If the spec targets a specific platform, hints MAY be platform-specific; otherwise they SHOULD be language-agnostic.
|
|
167
|
-
- If `opensdd.json` exists, add or update the `publish` object with name, version, description,
|
|
167
|
+
- If `opensdd.json` exists, add or update the `publish` object with name, version, description, specFormat, and dependencies from `_notes/scope.md`.
|
|
168
168
|
|
|
169
169
|
**Validate the output:**
|
|
170
170
|
- MUST have H1 header with blockquote summary and `## Behavioral Contract`
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sdd-manager
|
|
3
|
-
description: "
|
|
3
|
+
description: "Manage OpenSDD specs — revise authored specs, implement dependency specs, process updates, check conformance, and create deviations. Use when the user asks to revise a spec, implement a spec, process a spec update, check conformance, or create a deviation."
|
|
4
4
|
---
|
|
5
5
|
# SDD Manager
|
|
6
6
|
|
|
7
|
-
> Teaches agents how to implement, update, and verify
|
|
7
|
+
> Teaches agents how to revise authored specs, and implement, update, and verify dependency specs in an OpenSDD-compliant project.
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
|
-
The sdd-manager skill is installed once per project via `opensdd init` alongside the sdd-generate skill, into each supported coding agent's configuration directory. It teaches agents
|
|
11
|
+
The sdd-manager skill is installed once per project via `opensdd init` alongside the sdd-generate skill, into each supported coding agent's configuration directory. It teaches agents five workflows: revising an authored spec, implementing a dependency spec, processing a dependency spec update, checking conformance, and creating deviations. It also defines universal implementation defaults, the project conventions check, and the verification protocol that apply to all spec implementations.
|
|
12
12
|
|
|
13
|
-
This skill is the required entry point whenever an agent
|
|
13
|
+
This skill is the required entry point whenever an agent works with OpenSDD specs — whether revising the project's authored spec, implementing a dependency spec, processing a dependency update, checking conformance, or creating a deviation. The agent MUST NOT implement or modify code based on an OpenSDD spec outside of the workflows defined here.
|
|
14
14
|
|
|
15
15
|
## Spec as Source of Truth
|
|
16
16
|
|
|
17
|
-
The
|
|
17
|
+
The spec (`spec.md`) — whether an authored spec or an installed dependency — is the authoritative description of what to build. It is already a carefully structured behavioral contract with precise language, edge cases, and constraints. The agent MUST treat it as the primary reference throughout all workflows and MUST NOT replace it with a self-generated substitute.
|
|
18
18
|
|
|
19
19
|
**Do not rewrite the spec into a plan.** The agent MUST NOT translate spec requirements into its own planning format (todo lists, step-by-step plans, internal summaries, etc.) as a substitute for the spec itself. Such translations are inherently lossy — they flatten nuance, drop edge cases, and shift intent. The spec's behavioral contract already defines what to build; duplicating it in another format adds no value and introduces drift.
|
|
20
20
|
|
|
@@ -22,10 +22,61 @@ The dependency spec (`spec.md`) is the authoritative description of what to buil
|
|
|
22
22
|
|
|
23
23
|
**Plans are for additive context only.** If the agent uses planning tools (todo lists, scratchpads, plan mode, etc.), those plans MUST be limited to information that is _not_ in the spec: project-specific decisions (file paths, module structure, integration points), target language and framework details, implementation ordering, and deviations. Plans SHOULD reference spec sections by name rather than restating their content.
|
|
24
24
|
|
|
25
|
+
## Workflow Identification
|
|
26
|
+
|
|
27
|
+
When entering any sdd-manager workflow, the agent MUST announce the active workflow to the user before proceeding. The announcement MUST include the workflow name, the target spec, and a one-line description of what will happen next.
|
|
28
|
+
|
|
29
|
+
Format:
|
|
30
|
+
|
|
31
|
+
> **[sdd-manager: {Workflow}]** {Target spec}
|
|
32
|
+
> {What the user should expect}
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
|
|
36
|
+
> **[sdd-manager: Revise]** opensdd/cli.md
|
|
37
|
+
> I'll draft a changeset for your review before modifying the spec.
|
|
38
|
+
|
|
39
|
+
> **[sdd-manager: Implement]** .opensdd.deps/slugify/spec.md
|
|
40
|
+
> I'll walk you through the spec, then implement and verify.
|
|
41
|
+
|
|
42
|
+
> **[sdd-manager: Update]** .opensdd.deps/slugify
|
|
43
|
+
> Reading the staged changeset to identify what changed.
|
|
44
|
+
|
|
45
|
+
If the agent determines the wrong workflow was triggered, it MUST stop and clarify with the user before proceeding.
|
|
46
|
+
|
|
25
47
|
## Workflows
|
|
26
48
|
|
|
49
|
+
### Revise
|
|
50
|
+
|
|
51
|
+
For incremental changes to the project's authored spec. The agent drafts a changeset for the user to review before modifying the spec or implementation.
|
|
52
|
+
|
|
53
|
+
1. **Understand the request.** Read the user's description of the desired behavior change. Read the current spec from `<specsDir>/` (`spec.md` and any supplementary files).
|
|
54
|
+
|
|
55
|
+
2. **Draft changeset.** Write a changeset to `<specsDir>/.changes/changeset.md` containing:
|
|
56
|
+
- **Rationale:** Why this change is being made — the user's request, the problem it solves, and any design decisions.
|
|
57
|
+
- **Changed Files:** For each spec file being modified, a unified diff showing the proposed changes. For new sections being added, show the full proposed content as an addition.
|
|
58
|
+
|
|
59
|
+
The changeset MUST be persisted to disk (not kept in conversation context) so it survives context window clears. The agent MUST NOT modify spec files or implementation code during this step.
|
|
60
|
+
|
|
61
|
+
3. **Review.** Present the changeset to the user. Summarize what's changing and why. The user may:
|
|
62
|
+
- **Approve** — proceed to step 4.
|
|
63
|
+
- **Request modifications** — the agent updates the changeset and re-presents. The agent MUST re-read the changeset from disk before modifying it.
|
|
64
|
+
- **Reject** — delete `<specsDir>/.changes/` and stop.
|
|
65
|
+
|
|
66
|
+
The agent MUST NOT proceed past this step without explicit user approval.
|
|
67
|
+
|
|
68
|
+
4. **Apply to spec.** Apply the approved diffs to the spec files. Delete `<specsDir>/.changes/` after successful application.
|
|
69
|
+
|
|
70
|
+
5. **Implement.** Update the implementation to match the revised spec. Use the changeset to identify which behavioral sections changed — only modify code affected by the changes. The agent MUST re-read the updated spec sections directly (not work from the changeset diffs) when implementing.
|
|
71
|
+
|
|
72
|
+
6. **Verify.** Execute the verification protocol (see Verification Protocol section below) scoped to the changed sections: regenerate affected tests, run until all pass, dispatch subagent for spec compliance audit scoped to the changed sections, fix any findings, re-run tests.
|
|
73
|
+
|
|
74
|
+
7. **Report.** Summarize what changed in the spec and implementation. If the project has `publish` configured in `opensdd.json`, remind the user to bump the version before publishing.
|
|
75
|
+
|
|
27
76
|
### Implement
|
|
28
77
|
|
|
78
|
+
For first-time implementation of a dependency spec.
|
|
79
|
+
|
|
29
80
|
1. **Read:** Read the dependency spec from `.opensdd.deps/<name>/` (`spec.md` and any supplementary files), `deviations.md` (if it exists), and other dependency specs it depends on.
|
|
30
81
|
|
|
31
82
|
2. **Clarify (pre-implementation Q&A):** Before writing any code, the agent MUST walk the user through the spec's scope and structure and solicit clarifications. The agent MUST:
|
|
@@ -40,7 +91,7 @@ The dependency spec (`spec.md`) is the authoritative description of what to buil
|
|
|
40
91
|
|
|
41
92
|
5. **Verify:** Execute the full verification protocol (see Verification Protocol section below): generate test suite → run tests until all pass (or SHOULD bail after 50 attempts) → dispatch subagent for spec compliance audit → fix any findings → re-run tests.
|
|
42
93
|
|
|
43
|
-
6. **Record:** Update `opensdd.json` `dependencies` entry with `implementation` path, `tests` path, and `
|
|
94
|
+
6. **Record:** Update `opensdd.json` `dependencies` entry with `implementation` path, `tests` path, and `hasDeviations` if applicable.
|
|
44
95
|
|
|
45
96
|
7. **Report:** Report results with spec coverage summary.
|
|
46
97
|
|
|
@@ -59,7 +110,7 @@ Run existing test suite → report pass/fail. If test suite is missing or stale,
|
|
|
59
110
|
|
|
60
111
|
### Create Deviation
|
|
61
112
|
|
|
62
|
-
Determine affected spec section → classify type → create/append to `deviations.md` in `.opensdd.deps/<name>/` → update test suite to skip affected tests → update `
|
|
113
|
+
Determine affected spec section → classify type → create/append to `deviations.md` in `.opensdd.deps/<name>/` → update test suite to skip affected tests → update `hasDeviations` in `opensdd.json`.
|
|
63
114
|
|
|
64
115
|
## Universal Implementation Defaults
|
|
65
116
|
|
package/opensdd/spec-format.md
CHANGED
|
@@ -21,7 +21,7 @@ A project MAY be both an author (has `opensdd/spec.md`) and a consumer (has `.op
|
|
|
21
21
|
|
|
22
22
|
In a monorepo, each sub-project that needs its own spec maintains its own `opensdd.json`, `opensdd/`, and `.opensdd.deps/` at its sub-project root — the same way each package in an npm workspace has its own `package.json`. The CLI always operates relative to the nearest `opensdd.json` in the directory hierarchy.
|
|
23
23
|
|
|
24
|
-
The OpenSDD protocol installs two skills into the project: **sdd-manager** teaches agents how to implement, update, and verify
|
|
24
|
+
The OpenSDD protocol installs two skills into the project: **sdd-manager** teaches agents how to revise authored specs and implement, update, and verify dependency specs; **sdd-generate** teaches agents how to extract a spec from an existing codebase. Individual specs are not skills — they are data that the skills operate on.
|
|
25
25
|
|
|
26
26
|
Skills are installed into the native configuration format of each supported coding agent so they are automatically discovered. The canonical skill content follows the Agent Skills standard (agentskills.io) with `SKILL.md` files; adapter files are generated for agents with different configuration systems. See the CLI spec for the full installation mapping. Supported agents:
|
|
27
27
|
|
|
@@ -48,7 +48,7 @@ A spec is a directory containing spec files. The directory name is the bare spec
|
|
|
48
48
|
|
|
49
49
|
In the registry and in installed dependency specs (`.opensdd.deps/`), every spec directory MUST contain:
|
|
50
50
|
|
|
51
|
-
- `manifest.json` — Metadata about the spec (name, version,
|
|
51
|
+
- `manifest.json` — Metadata about the spec (name, version, specFormat, description, dependencies).
|
|
52
52
|
- `spec.md` — The behavioral contract and acceptance criteria. The spec IS the acceptance criteria — a well-written spec contains everything needed to both implement and verify the software.
|
|
53
53
|
|
|
54
54
|
For the authored spec (`opensdd/`), the directory contains `spec.md` and any supplementary files. The metadata that would be in `manifest.json` lives in the `opensdd.json` `publish` entry instead; a separate `manifest.json` file is constructed during publishing.
|
|
@@ -227,11 +227,12 @@ The CLI resolves `opensdd.json` by searching upward from the current working dir
|
|
|
227
227
|
|
|
228
228
|
The development methodology for an OpenSDD-compliant repo:
|
|
229
229
|
|
|
230
|
-
1. **
|
|
231
|
-
2. **
|
|
232
|
-
3. **
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
1. **Propose** — behavior changes are drafted as a changeset (rationale + unified diffs) staged in `<specsDir>/.changes/changeset.md`. The sdd-manager skill's Revise workflow guides this process.
|
|
231
|
+
2. **Review** — the user reviews the proposed changeset, iterates if needed, and approves.
|
|
232
|
+
3. **Apply** — the agent applies the approved changeset to the spec files and deletes the staging directory.
|
|
233
|
+
4. **Implement** — the agent updates the implementation to match the revised spec, using the changeset to scope the changes.
|
|
234
|
+
5. **Verify** — the agent runs the verification protocol (tests + spec compliance audit) scoped to the changed sections.
|
|
235
|
+
6. **Publish** — when the spec and implementation are in sync, the developer publishes the spec version to the registry via `opensdd publish`.
|
|
235
236
|
|
|
236
237
|
#### Publishing
|
|
237
238
|
|
|
@@ -255,23 +256,23 @@ The `opensdd.json` file is the project-level manifest. It lives at the project r
|
|
|
255
256
|
{
|
|
256
257
|
"opensdd": "0.1.0",
|
|
257
258
|
"registry": "https://github.com/deepagents-ai/opensdd",
|
|
258
|
-
"
|
|
259
|
-
"
|
|
259
|
+
"specsDir": "opensdd",
|
|
260
|
+
"depsDir": ".opensdd.deps",
|
|
260
261
|
"publish": {
|
|
261
262
|
"name": "auth",
|
|
262
263
|
"version": "1.0.0",
|
|
263
264
|
"description": "Authentication with multiple provider support",
|
|
264
|
-
"
|
|
265
|
+
"specFormat": "0.1.0",
|
|
265
266
|
"dependencies": []
|
|
266
267
|
},
|
|
267
268
|
"dependencies": {
|
|
268
269
|
"slugify": {
|
|
269
270
|
"version": "2.1.0",
|
|
270
271
|
"source": "https://github.com/deepagents-ai/opensdd",
|
|
271
|
-
"
|
|
272
|
+
"specFormat": "0.1.0",
|
|
272
273
|
"implementation": null,
|
|
273
274
|
"tests": null,
|
|
274
|
-
"
|
|
275
|
+
"hasDeviations": false
|
|
275
276
|
}
|
|
276
277
|
}
|
|
277
278
|
}
|
|
@@ -281,8 +282,8 @@ The `opensdd.json` file is the project-level manifest. It lives at the project r
|
|
|
281
282
|
|
|
282
283
|
- `opensdd` (required): Protocol version string. Agents and the CLI MUST use this to determine how to interpret the manifest.
|
|
283
284
|
- `registry` (optional): URL of the default registry. Overridden by the CLI's `--registry` flag. Default: `"https://github.com/deepagents-ai/opensdd"`.
|
|
284
|
-
- `
|
|
285
|
-
- `
|
|
285
|
+
- `specsDir` (optional): Relative path from the project root to the directory containing the authored spec. Default: `"opensdd"`.
|
|
286
|
+
- `depsDir` (optional): Relative path from the project root to the directory containing installed dependency specs. Default: `".opensdd.deps"`.
|
|
286
287
|
- `publish` (optional): Object defining the spec this project publishes. Omit if the project only consumes specs.
|
|
287
288
|
- `dependencies` (optional): Object keyed by spec name. Each entry tracks an installed dependency spec. Omit if the project only publishes specs.
|
|
288
289
|
|
|
@@ -291,17 +292,17 @@ The `opensdd.json` file is the project-level manifest. It lives at the project r
|
|
|
291
292
|
- `name` (required): Bare spec name — lowercase alphanumeric and hyphens only.
|
|
292
293
|
- `version` (required): Semver version of the spec being developed.
|
|
293
294
|
- `description` (required): One-line description for registry display.
|
|
294
|
-
- `
|
|
295
|
+
- `specFormat` (required): Which version of the OpenSDD protocol this spec targets.
|
|
295
296
|
- `dependencies` (optional): Array of bare spec names that this spec references for shared types or behavioral contracts.
|
|
296
297
|
|
|
297
298
|
#### Dependency entry fields
|
|
298
299
|
|
|
299
300
|
- `version` (required): Semver version of the installed spec.
|
|
300
301
|
- `source` (required): URL of the registry this spec was installed from.
|
|
301
|
-
- `
|
|
302
|
+
- `specFormat` (required): OpenSDD protocol version of the installed spec.
|
|
302
303
|
- `implementation` (consumer-managed): Path to the generated implementation file, `null` until implemented.
|
|
303
304
|
- `tests` (consumer-managed): Path to the generated test file, `null` until implemented.
|
|
304
|
-
- `
|
|
305
|
+
- `hasDeviations` (consumer-managed): Boolean, `false` until a deviation is created.
|
|
305
306
|
|
|
306
307
|
Consumer-managed fields MUST be present with explicit `null` or `false` values rather than omitted. Fields MUST survive all update operations.
|
|
307
308
|
|
|
@@ -338,8 +339,8 @@ Each spec in the registry MUST have an `index.json` at its root:
|
|
|
338
339
|
"description": "String to URL-friendly slug",
|
|
339
340
|
"latest": "2.2.0",
|
|
340
341
|
"versions": {
|
|
341
|
-
"2.1.0": { "
|
|
342
|
-
"2.2.0": { "
|
|
342
|
+
"2.1.0": { "specFormat": "0.1.0" },
|
|
343
|
+
"2.2.0": { "specFormat": "0.1.0" }
|
|
343
344
|
}
|
|
344
345
|
}
|
|
345
346
|
```
|
|
@@ -347,7 +348,7 @@ Each spec in the registry MUST have an `index.json` at its root:
|
|
|
347
348
|
- `name` (required): Bare spec name.
|
|
348
349
|
- `description` (required): One-line description.
|
|
349
350
|
- `latest` (required): The most recent published version.
|
|
350
|
-
- `versions` (required): Object keyed by semver version string. Each entry MAY include summary metadata (e.g., `
|
|
351
|
+
- `versions` (required): Object keyed by semver version string. Each entry MAY include summary metadata (e.g., `specFormat`).
|
|
351
352
|
|
|
352
353
|
#### manifest.json (per version)
|
|
353
354
|
|
|
@@ -357,7 +358,7 @@ Each version directory MUST contain a `manifest.json`:
|
|
|
357
358
|
{
|
|
358
359
|
"name": "slugify",
|
|
359
360
|
"version": "2.2.0",
|
|
360
|
-
"
|
|
361
|
+
"specFormat": "0.1.0",
|
|
361
362
|
"description": "String to URL-friendly slug",
|
|
362
363
|
"dependencies": []
|
|
363
364
|
}
|
|
@@ -365,7 +366,7 @@ Each version directory MUST contain a `manifest.json`:
|
|
|
365
366
|
|
|
366
367
|
- `name` (required): Bare spec name.
|
|
367
368
|
- `version` (required): Semver version of this entry.
|
|
368
|
-
- `
|
|
369
|
+
- `specFormat` (required): OpenSDD protocol version.
|
|
369
370
|
- `description` (required): One-line description.
|
|
370
371
|
- `dependencies` (optional): Array of bare spec names.
|
|
371
372
|
|
|
@@ -405,6 +406,10 @@ Captures everything the agent needs to understand and process the update without
|
|
|
405
406
|
**Spec-format:** {old version} → {new version} (or "unchanged")
|
|
406
407
|
**Date:** {ISO 8601 date}
|
|
407
408
|
|
|
409
|
+
## Rationale (optional)
|
|
410
|
+
|
|
411
|
+
{Context for why this version was published. Omitted if not available from the registry.}
|
|
412
|
+
|
|
408
413
|
## Changed Files
|
|
409
414
|
|
|
410
415
|
### spec.md
|
|
@@ -429,22 +434,57 @@ Contains the metadata needed to finalize the `opensdd.json` dependency entry whe
|
|
|
429
434
|
```json
|
|
430
435
|
{
|
|
431
436
|
"name": "slugify",
|
|
432
|
-
"
|
|
437
|
+
"previousVersion": "2.1.0",
|
|
433
438
|
"version": "2.2.0",
|
|
434
439
|
"source": "https://github.com/deepagents-ai/opensdd",
|
|
435
|
-
"
|
|
440
|
+
"specFormat": "0.1.0"
|
|
436
441
|
}
|
|
437
442
|
```
|
|
438
443
|
|
|
439
444
|
This is a transient artifact. `opensdd update apply` reads this file, applies the metadata to `opensdd.json`, and deletes the staging directory.
|
|
440
445
|
|
|
446
|
+
### Authored Spec Staging
|
|
447
|
+
|
|
448
|
+
When a behavior change is proposed to the project's authored spec via the sdd-manager Revise workflow, the agent stages the changeset in `<specsDir>/.changes/`:
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
opensdd/
|
|
452
|
+
.changes/
|
|
453
|
+
changeset.md # Rationale + unified diffs of proposed spec changes
|
|
454
|
+
spec.md # Unchanged until the changeset is approved and applied
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
The staging directory is persisted to disk so it survives context window clears. It is deleted after the changeset is applied (or rejected).
|
|
458
|
+
|
|
459
|
+
#### changeset.md (authored spec)
|
|
460
|
+
|
|
461
|
+
```markdown
|
|
462
|
+
# Changeset
|
|
463
|
+
|
|
464
|
+
**Date:** {ISO 8601 date}
|
|
465
|
+
|
|
466
|
+
## Rationale
|
|
467
|
+
|
|
468
|
+
{Why this change is being made — the user's request, the problem it solves, and any design decisions made.}
|
|
469
|
+
|
|
470
|
+
## Changed Files
|
|
471
|
+
|
|
472
|
+
### {filename}
|
|
473
|
+
|
|
474
|
+
\`\`\`diff
|
|
475
|
+
{unified diff of proposed changes}
|
|
476
|
+
\`\`\`
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
The rationale section distinguishes authored spec changesets from dependency update changesets. For dependency updates, the "why" is implicit in the version change. For authored spec changes, the "why" is the design decision being made and MUST be captured explicitly.
|
|
480
|
+
|
|
441
481
|
### SDD-Manager Skill
|
|
442
482
|
|
|
443
|
-
The sdd-manager skill teaches agents how to implement, update, and verify
|
|
483
|
+
The sdd-manager skill teaches agents how to revise authored specs and implement, update, and verify dependency specs. It is installed once per project via `opensdd init` alongside the sdd-generate skill, into each supported agent's configuration directory. See [sdd-manager.md](skills/sdd-manager.md) for the full skill workflow, including implementation defaults, the project conventions check, and the verification protocol.
|
|
444
484
|
|
|
445
485
|
### SDD-Generate Skill
|
|
446
486
|
|
|
447
|
-
The sdd-generate skill teaches agents how to
|
|
487
|
+
The sdd-generate skill teaches agents how to extract a behavioral spec from an existing codebase. See [sdd-generate.md](skills/sdd-generate.md) for the full skill workflow.
|
|
448
488
|
|
|
449
489
|
### Versioning
|
|
450
490
|
|
|
@@ -475,7 +515,7 @@ Specs use semantic versioning:
|
|
|
475
515
|
- How spec dependencies are resolved when circular (not currently supported)
|
|
476
516
|
- The sdd-generate skill's internal workflow (separate concern)
|
|
477
517
|
- File encoding (assumed UTF-8)
|
|
478
|
-
-
|
|
518
|
+
- The exact wording of the sdd-manager workflow identification announcement
|
|
479
519
|
- The exact mechanism for authenticating with the registry during publish (deferred to local git/gh credentials)
|
|
480
520
|
|
|
481
521
|
## Invariants
|
|
@@ -484,7 +524,7 @@ Specs use semantic versioning:
|
|
|
484
524
|
- Spec-owned files in `.opensdd.deps/` MUST NOT be modified by the consumer or their agent
|
|
485
525
|
- `deviations.md` MUST NOT be created, modified, or deleted by the CLI or any automated tooling
|
|
486
526
|
- Consumer-managed fields in `opensdd.json` MUST survive all update operations
|
|
487
|
-
- Every installed dependency spec MUST have both a directory in `
|
|
527
|
+
- Every installed dependency spec MUST have both a directory in `depsDir` and an entry in `opensdd.json` `dependencies`
|
|
488
528
|
- All behaviors described in the spec MUST be thoroughly tested by the implementing agent
|
|
489
529
|
- A `spec.md` MUST contain an H1 header with blockquote summary and a `## Behavioral Contract` section
|
|
490
530
|
- `deviations.md` MUST only be created when a deviation actually exists
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opensdd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "The official OpenSDD (Open Spec-Driven Development) CLI, spec, and spec registry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"node": ">=18.0.0"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
|
-
"test": "node --test test/**/*.test.js"
|
|
18
|
+
"test": "echo '\\n========== TESTS BEGIN ==========\\n' && node --test test/**/*.test.js; EXIT=$?; echo '\\n========== TESTS END ==========\\n'; exit $EXIT"
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
|
21
21
|
"opensdd",
|
package/src/commands/init.js
CHANGED
|
@@ -75,6 +75,18 @@ function promptYN(question) {
|
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
function promptChoice(question, choices) {
|
|
79
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
const lines = choices.map((c, i) => ` ${i + 1}. ${c}`).join('\n');
|
|
82
|
+
rl.question(`${question}\n${lines}\nChoice: `, (answer) => {
|
|
83
|
+
rl.close();
|
|
84
|
+
const idx = parseInt(answer, 10);
|
|
85
|
+
resolve(idx >= 1 && idx <= choices.length ? idx : null);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
export async function initCommand() {
|
|
79
91
|
const cwd = process.cwd();
|
|
80
92
|
|
|
@@ -88,23 +100,9 @@ export async function initCommand() {
|
|
|
88
100
|
}
|
|
89
101
|
}
|
|
90
102
|
|
|
91
|
-
// Step 2:
|
|
92
|
-
let warnings;
|
|
93
|
-
try {
|
|
94
|
-
warnings = installSkills(cwd);
|
|
95
|
-
} catch (err) {
|
|
96
|
-
console.error(`Error: Could not install skills: ${err.message}`);
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
for (const w of warnings) {
|
|
101
|
-
console.warn(`Warning: ${w}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Step 3: Read or create opensdd.json
|
|
103
|
+
// Step 2: Read existing opensdd.json if present
|
|
105
104
|
const manifestPath = path.join(cwd, 'opensdd.json');
|
|
106
105
|
let manifest = null;
|
|
107
|
-
let manifestCreated = false;
|
|
108
106
|
|
|
109
107
|
if (fs.existsSync(manifestPath)) {
|
|
110
108
|
try {
|
|
@@ -115,31 +113,84 @@ export async function initCommand() {
|
|
|
115
113
|
}
|
|
116
114
|
}
|
|
117
115
|
|
|
116
|
+
// Step 3: Determine mode
|
|
117
|
+
let mode;
|
|
118
|
+
let manifestCreated = false;
|
|
119
|
+
|
|
120
|
+
if (manifest && manifest.specsDir) {
|
|
121
|
+
// OpenSDD-driven re-init — no prompt needed
|
|
122
|
+
mode = 'full';
|
|
123
|
+
} else if (manifest && !manifest.specsDir) {
|
|
124
|
+
// Consumer re-init — offer upgrade
|
|
125
|
+
const upgrade = await promptYN('Upgrade to OpenSDD-driven? (y/n) ');
|
|
126
|
+
if (upgrade) {
|
|
127
|
+
mode = 'full';
|
|
128
|
+
manifest.specsDir = manifest.specsDir || 'opensdd';
|
|
129
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
130
|
+
} else {
|
|
131
|
+
mode = 'consumer';
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
// Fresh init — prompt for mode
|
|
135
|
+
const choice = await promptChoice('How will this project use OpenSDD?', [
|
|
136
|
+
'Consumer only \u2014 install and implement dependency specs',
|
|
137
|
+
'OpenSDD-driven \u2014 full SDD methodology (author specs, both skills)',
|
|
138
|
+
]);
|
|
139
|
+
mode = choice === 2 ? 'full' : 'consumer';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Step 4: Install skills
|
|
143
|
+
let warnings;
|
|
144
|
+
try {
|
|
145
|
+
warnings = installSkills(cwd, { mode });
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error(`Error: Could not install skills: ${err.message}`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const w of warnings) {
|
|
152
|
+
console.warn(`Warning: ${w}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Step 5: Create or preserve opensdd.json
|
|
118
156
|
if (!manifest) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
157
|
+
if (mode === 'full') {
|
|
158
|
+
manifest = {
|
|
159
|
+
opensdd: '0.1.0',
|
|
160
|
+
specsDir: 'opensdd',
|
|
161
|
+
depsDir: '.opensdd.deps',
|
|
162
|
+
};
|
|
163
|
+
} else {
|
|
164
|
+
manifest = {
|
|
165
|
+
opensdd: '0.1.0',
|
|
166
|
+
depsDir: '.opensdd.deps',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
124
169
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
125
170
|
manifestCreated = true;
|
|
126
171
|
}
|
|
127
172
|
|
|
128
|
-
const
|
|
129
|
-
const depsDir = manifest.deps_dir || '.opensdd.deps';
|
|
130
|
-
const specsDirPath = path.join(cwd, specsDir);
|
|
173
|
+
const depsDir = manifest.depsDir || '.opensdd.deps';
|
|
131
174
|
const depsDirPath = path.join(cwd, depsDir);
|
|
132
175
|
|
|
133
|
-
// Step
|
|
134
|
-
const
|
|
135
|
-
fs.mkdirSync(
|
|
176
|
+
// Step 6: Create deps directory (both modes)
|
|
177
|
+
const depsDirCreated = !fs.existsSync(depsDirPath);
|
|
178
|
+
fs.mkdirSync(depsDirPath, { recursive: true });
|
|
136
179
|
|
|
137
|
-
// Step
|
|
138
|
-
|
|
180
|
+
// Step 7: Full mode — create specs directory and skeleton spec.md
|
|
181
|
+
let specsDirCreated = false;
|
|
139
182
|
let specMdCreated = false;
|
|
140
|
-
if (
|
|
141
|
-
const
|
|
142
|
-
const
|
|
183
|
+
if (mode === 'full') {
|
|
184
|
+
const specsDir = manifest.specsDir || 'opensdd';
|
|
185
|
+
const specsDirPath = path.join(cwd, specsDir);
|
|
186
|
+
|
|
187
|
+
specsDirCreated = !fs.existsSync(specsDirPath);
|
|
188
|
+
fs.mkdirSync(specsDirPath, { recursive: true });
|
|
189
|
+
|
|
190
|
+
const specMdPath = path.join(specsDirPath, 'spec.md');
|
|
191
|
+
if (!fs.existsSync(specMdPath)) {
|
|
192
|
+
const projectName = getProjectName(cwd);
|
|
193
|
+
const skeleton = `# ${projectName}
|
|
143
194
|
|
|
144
195
|
> TODO: One-line description of what this software does.
|
|
145
196
|
|
|
@@ -155,30 +206,40 @@ export async function initCommand() {
|
|
|
155
206
|
|
|
156
207
|
<!-- List properties that must hold true across all inputs and states. -->
|
|
157
208
|
`;
|
|
158
|
-
|
|
159
|
-
|
|
209
|
+
fs.writeFileSync(specMdPath, skeleton);
|
|
210
|
+
specMdCreated = true;
|
|
211
|
+
}
|
|
160
212
|
}
|
|
161
213
|
|
|
162
|
-
// Step
|
|
163
|
-
const depsDirCreated = !fs.existsSync(depsDirPath);
|
|
164
|
-
fs.mkdirSync(depsDirPath, { recursive: true });
|
|
165
|
-
|
|
166
|
-
// Step 7: Print output
|
|
214
|
+
// Step 8: Print output
|
|
167
215
|
const isReInit = !manifestCreated;
|
|
168
216
|
const skillVerb = isReInit ? 'updated' : 'installed';
|
|
169
217
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
218
|
+
if (mode === 'consumer') {
|
|
219
|
+
console.log('Initialized OpenSDD (consumer):');
|
|
220
|
+
console.log(
|
|
221
|
+
' Skills installed for: Claude Code, Codex CLI, Cursor, GitHub Copilot, Gemini CLI, Amp'
|
|
222
|
+
);
|
|
223
|
+
console.log(` sdd-manager ${skillVerb} (6 agent formats)`);
|
|
224
|
+
console.log(
|
|
225
|
+
` opensdd.json ${manifestCreated ? 'created' : 'already exists (preserved)'}`
|
|
226
|
+
);
|
|
227
|
+
console.log(` ${depsDir}/ ${depsDirCreated ? 'created' : 'already exists'}`);
|
|
228
|
+
} else {
|
|
229
|
+
const specsDir = manifest.specsDir || 'opensdd';
|
|
230
|
+
console.log('Initialized OpenSDD:');
|
|
231
|
+
console.log(
|
|
232
|
+
' Skills installed for: Claude Code, Codex CLI, Cursor, GitHub Copilot, Gemini CLI, Amp'
|
|
233
|
+
);
|
|
234
|
+
console.log(` sdd-manager ${skillVerb} (6 agent formats)`);
|
|
235
|
+
console.log(` sdd-generate ${skillVerb} (6 agent formats)`);
|
|
236
|
+
console.log(
|
|
237
|
+
` opensdd.json ${manifestCreated ? 'created' : 'already exists (preserved)'}`
|
|
238
|
+
);
|
|
239
|
+
console.log(` ${specsDir}/ ${specsDirCreated ? 'created' : 'already exists'}`);
|
|
240
|
+
console.log(
|
|
241
|
+
` ${specsDir}/spec.md ${specMdCreated ? 'created (skeleton)' : 'already exists (preserved)'}`
|
|
242
|
+
);
|
|
243
|
+
console.log(` ${depsDir}/ ${depsDirCreated ? 'created' : 'already exists'}`);
|
|
244
|
+
}
|
|
184
245
|
}
|
package/src/commands/install.js
CHANGED
|
@@ -13,17 +13,26 @@ import {
|
|
|
13
13
|
fetchSpecFiles,
|
|
14
14
|
listRegistrySpecs,
|
|
15
15
|
} from '../lib/registry.js';
|
|
16
|
+
import { installSkills } from '../lib/skills.js';
|
|
16
17
|
|
|
17
18
|
function isValidSpecName(name) {
|
|
18
19
|
return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(name);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export async function installCommand(name, version, options) {
|
|
22
|
-
// Step 1: Verify opensdd.json exists
|
|
23
|
-
|
|
23
|
+
// Step 1: Verify opensdd.json exists, auto-bootstrap if not
|
|
24
|
+
let manifestPath = findManifestPath(process.cwd());
|
|
24
25
|
if (!manifestPath) {
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
manifestPath = path.join(cwd, 'opensdd.json');
|
|
28
|
+
const consumerManifest = {
|
|
29
|
+
opensdd: '0.1.0',
|
|
30
|
+
depsDir: '.opensdd.deps',
|
|
31
|
+
};
|
|
32
|
+
fs.writeFileSync(manifestPath, JSON.stringify(consumerManifest, null, 2) + '\n');
|
|
33
|
+
installSkills(cwd, { mode: 'consumer' });
|
|
34
|
+
fs.mkdirSync(path.join(cwd, '.opensdd.deps'), { recursive: true });
|
|
35
|
+
console.log('Auto-initialized OpenSDD (consumer).');
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
const manifest = readManifest(manifestPath);
|
|
@@ -132,10 +141,10 @@ export async function installCommand(name, version, options) {
|
|
|
132
141
|
manifest.dependencies[name] = {
|
|
133
142
|
version: resolvedVersion,
|
|
134
143
|
source: registrySource,
|
|
135
|
-
|
|
144
|
+
specFormat: specManifest.specFormat || '0.1.0',
|
|
136
145
|
implementation: existingEntry?.implementation ?? null,
|
|
137
146
|
tests: existingEntry?.tests ?? null,
|
|
138
|
-
|
|
147
|
+
hasDeviations: existingEntry?.hasDeviations ?? false,
|
|
139
148
|
};
|
|
140
149
|
|
|
141
150
|
writeManifest(manifestPath, manifest);
|
package/src/commands/publish.js
CHANGED
|
@@ -65,16 +65,16 @@ export async function publishCommand(options) {
|
|
|
65
65
|
if (!manifest.publish) {
|
|
66
66
|
console.error('Error: No `publish` section in opensdd.json.');
|
|
67
67
|
console.error(
|
|
68
|
-
'Add a `publish` object with name, version, description, and
|
|
68
|
+
'Add a `publish` object with name, version, description, and specFormat to publish your spec.'
|
|
69
69
|
);
|
|
70
70
|
process.exit(1);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const { name, version, description,
|
|
73
|
+
const { name, version, description, specFormat, dependencies } = manifest.publish;
|
|
74
74
|
|
|
75
|
-
if (!name || !version || !description || !
|
|
75
|
+
if (!name || !version || !description || !specFormat) {
|
|
76
76
|
console.error(
|
|
77
|
-
'Error: `publish` section must include name, version, description, and
|
|
77
|
+
'Error: `publish` section must include name, version, description, and specFormat.'
|
|
78
78
|
);
|
|
79
79
|
process.exit(1);
|
|
80
80
|
}
|
|
@@ -169,7 +169,7 @@ export async function publishCommand(options) {
|
|
|
169
169
|
const publishManifest = {
|
|
170
170
|
name,
|
|
171
171
|
version,
|
|
172
|
-
|
|
172
|
+
specFormat,
|
|
173
173
|
description,
|
|
174
174
|
dependencies: dependencies || [],
|
|
175
175
|
};
|
|
@@ -192,7 +192,7 @@ export async function publishCommand(options) {
|
|
|
192
192
|
indexData = { name, description, latest: version, versions: {} };
|
|
193
193
|
}
|
|
194
194
|
indexData.latest = version;
|
|
195
|
-
indexData.versions[version] = {
|
|
195
|
+
indexData.versions[version] = { specFormat };
|
|
196
196
|
fs.writeFileSync(indexPath, JSON.stringify(indexData, null, 2) + '\n');
|
|
197
197
|
console.log(` Updated index.json latest: ${version}`);
|
|
198
198
|
|
package/src/commands/status.js
CHANGED
|
@@ -53,7 +53,7 @@ export async function statusCommand() {
|
|
|
53
53
|
// Check for deviations
|
|
54
54
|
let deviationInfo = '';
|
|
55
55
|
const deviationsPath = path.join(projectRoot, depsDir, depName, 'deviations.md');
|
|
56
|
-
if (entry.
|
|
56
|
+
if (entry.hasDeviations || fs.existsSync(deviationsPath)) {
|
|
57
57
|
if (fs.existsSync(deviationsPath)) {
|
|
58
58
|
const content = fs.readFileSync(deviationsPath, 'utf-8');
|
|
59
59
|
const deviationCount = (content.match(/^## /gm) || []).length;
|
package/src/commands/update.js
CHANGED
|
@@ -115,8 +115,8 @@ export async function updateCommand(name, options) {
|
|
|
115
115
|
fs.mkdirSync(updatesDir, { recursive: true });
|
|
116
116
|
|
|
117
117
|
// Write changeset.md
|
|
118
|
-
const specFormatOld = entry.
|
|
119
|
-
const specFormatNew = newManifest.
|
|
118
|
+
const specFormatOld = entry.specFormat || '0.1.0';
|
|
119
|
+
const specFormatNew = newManifest.specFormat || '0.1.0';
|
|
120
120
|
const specFormatChange =
|
|
121
121
|
specFormatOld === specFormatNew
|
|
122
122
|
? 'unchanged'
|
|
@@ -142,10 +142,10 @@ export async function updateCommand(name, options) {
|
|
|
142
142
|
// Write manifest.json for staging
|
|
143
143
|
const stageManifest = {
|
|
144
144
|
name: specName,
|
|
145
|
-
|
|
145
|
+
previousVersion: entry.version,
|
|
146
146
|
version: latestVersion,
|
|
147
147
|
source: registrySource,
|
|
148
|
-
|
|
148
|
+
specFormat: newManifest.specFormat || '0.1.0',
|
|
149
149
|
};
|
|
150
150
|
|
|
151
151
|
fs.writeFileSync(
|
|
@@ -64,7 +64,7 @@ export async function updateApplyCommand(name) {
|
|
|
64
64
|
if (pendingNames.length === 1) {
|
|
65
65
|
const updateManifestPath = path.join(updatesDir, pendingNames[0], 'manifest.json');
|
|
66
66
|
const updateManifest = JSON.parse(fs.readFileSync(updateManifestPath, 'utf-8'));
|
|
67
|
-
confirmMsg = `Apply update for ${pendingNames[0]} v${updateManifest.
|
|
67
|
+
confirmMsg = `Apply update for ${pendingNames[0]} v${updateManifest.previousVersion} -> v${updateManifest.version}? (y/n) `;
|
|
68
68
|
} else {
|
|
69
69
|
confirmMsg = `Apply ${pendingNames.length} pending updates? (y/n) `;
|
|
70
70
|
}
|
|
@@ -103,13 +103,13 @@ export async function updateApplyCommand(name) {
|
|
|
103
103
|
...existing,
|
|
104
104
|
version: updateManifest.version,
|
|
105
105
|
source: updateManifest.source,
|
|
106
|
-
|
|
106
|
+
specFormat: updateManifest.specFormat,
|
|
107
107
|
// Preserve consumer-managed fields
|
|
108
108
|
implementation:
|
|
109
109
|
existing.implementation !== undefined ? existing.implementation : null,
|
|
110
110
|
tests: existing.tests !== undefined ? existing.tests : null,
|
|
111
|
-
|
|
112
|
-
existing.
|
|
111
|
+
hasDeviations:
|
|
112
|
+
existing.hasDeviations !== undefined ? existing.hasDeviations : false,
|
|
113
113
|
};
|
|
114
114
|
|
|
115
115
|
// Delete staging directory
|
|
@@ -117,7 +117,7 @@ export async function updateApplyCommand(name) {
|
|
|
117
117
|
|
|
118
118
|
applied.push({
|
|
119
119
|
name: specName,
|
|
120
|
-
oldVersion: updateManifest.
|
|
120
|
+
oldVersion: updateManifest.previousVersion,
|
|
121
121
|
newVersion: updateManifest.version,
|
|
122
122
|
});
|
|
123
123
|
}
|
package/src/lib/manifest.js
CHANGED
|
@@ -29,10 +29,14 @@ export function writeManifest(manifestPath, data) {
|
|
|
29
29
|
fs.writeFileSync(manifestPath, JSON.stringify(data, null, 2) + '\n');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export function isConsumerOnly(manifest) {
|
|
33
|
+
return !manifest.specsDir;
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
export function getSpecsDir(manifest) {
|
|
33
|
-
return manifest.
|
|
37
|
+
return manifest.specsDir || 'opensdd';
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export function getDepsDir(manifest) {
|
|
37
|
-
return manifest.
|
|
41
|
+
return manifest.depsDir || '.opensdd.deps';
|
|
38
42
|
}
|
package/src/lib/skills.js
CHANGED
|
@@ -89,9 +89,10 @@ function updateManagedSection(filePath, sectionBody) {
|
|
|
89
89
|
* Returns an array of warnings for non-critical failures.
|
|
90
90
|
* Throws on critical failures (e.g., Claude Code installation fails).
|
|
91
91
|
*/
|
|
92
|
-
export function installSkills(projectRoot) {
|
|
92
|
+
export function installSkills(projectRoot, { mode = 'full' } = {}) {
|
|
93
93
|
const skills = getSkillContent();
|
|
94
94
|
const warnings = [];
|
|
95
|
+
const isFull = mode === 'full';
|
|
95
96
|
|
|
96
97
|
// 1. Claude Code (critical — Gemini and Amp depend on this)
|
|
97
98
|
const claudeBase = path.join(projectRoot, '.claude', 'skills');
|
|
@@ -103,14 +104,16 @@ export function installSkills(projectRoot) {
|
|
|
103
104
|
path.join(claudeBase, 'sdd-manager', 'references', 'spec-format.md'),
|
|
104
105
|
skills.specFormat
|
|
105
106
|
);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
if (isFull) {
|
|
108
|
+
writeFileSync(
|
|
109
|
+
path.join(claudeBase, 'sdd-generate', 'SKILL.md'),
|
|
110
|
+
skills.sddGenerate
|
|
111
|
+
);
|
|
112
|
+
writeFileSync(
|
|
113
|
+
path.join(claudeBase, 'sdd-generate', 'references', 'spec-format.md'),
|
|
114
|
+
skills.specFormat
|
|
115
|
+
);
|
|
116
|
+
}
|
|
114
117
|
|
|
115
118
|
// 2. Codex CLI
|
|
116
119
|
try {
|
|
@@ -123,14 +126,16 @@ export function installSkills(projectRoot) {
|
|
|
123
126
|
path.join(codexBase, 'sdd-manager', 'references', 'spec-format.md'),
|
|
124
127
|
skills.specFormat
|
|
125
128
|
);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
if (isFull) {
|
|
130
|
+
writeFileSync(
|
|
131
|
+
path.join(codexBase, 'sdd-generate', 'SKILL.md'),
|
|
132
|
+
skills.sddGenerate
|
|
133
|
+
);
|
|
134
|
+
writeFileSync(
|
|
135
|
+
path.join(codexBase, 'sdd-generate', 'references', 'spec-format.md'),
|
|
136
|
+
skills.specFormat
|
|
137
|
+
);
|
|
138
|
+
}
|
|
134
139
|
} catch (err) {
|
|
135
140
|
warnings.push(`Could not install Codex CLI skills: ${err.message}`);
|
|
136
141
|
}
|
|
@@ -141,7 +146,6 @@ export function installSkills(projectRoot) {
|
|
|
141
146
|
ensureDir(cursorBase);
|
|
142
147
|
|
|
143
148
|
const { frontmatter: managerFm, body: managerBody } = parseFrontmatter(skills.sddManager);
|
|
144
|
-
const { frontmatter: generateFm, body: generateBody } = parseFrontmatter(skills.sddGenerate);
|
|
145
149
|
|
|
146
150
|
const sddManagerCursor = `---
|
|
147
151
|
description: "${managerFm.description}"
|
|
@@ -150,13 +154,6 @@ alwaysApply: false
|
|
|
150
154
|
|
|
151
155
|
${managerBody}`;
|
|
152
156
|
|
|
153
|
-
const sddGenerateCursor = `---
|
|
154
|
-
description: "${generateFm.description}"
|
|
155
|
-
alwaysApply: false
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
${generateBody}`;
|
|
159
|
-
|
|
160
157
|
const specFormatCursor = `---
|
|
161
158
|
description: "OpenSDD spec format reference. Defines the structure and rules for behavioral specifications. Referenced by sdd-manager and sdd-generate skills."
|
|
162
159
|
alwaysApply: false
|
|
@@ -165,8 +162,20 @@ alwaysApply: false
|
|
|
165
162
|
${skills.specFormat}`;
|
|
166
163
|
|
|
167
164
|
writeFileSync(path.join(cursorBase, 'sdd-manager.md'), sddManagerCursor);
|
|
168
|
-
writeFileSync(path.join(cursorBase, 'sdd-generate.md'), sddGenerateCursor);
|
|
169
165
|
writeFileSync(path.join(cursorBase, 'opensdd-spec-format.md'), specFormatCursor);
|
|
166
|
+
|
|
167
|
+
if (isFull) {
|
|
168
|
+
const { frontmatter: generateFm, body: generateBody } = parseFrontmatter(skills.sddGenerate);
|
|
169
|
+
|
|
170
|
+
const sddGenerateCursor = `---
|
|
171
|
+
description: "${generateFm.description}"
|
|
172
|
+
alwaysApply: false
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
${generateBody}`;
|
|
176
|
+
|
|
177
|
+
writeFileSync(path.join(cursorBase, 'sdd-generate.md'), sddGenerateCursor);
|
|
178
|
+
}
|
|
170
179
|
} catch (err) {
|
|
171
180
|
warnings.push(`Could not install Cursor skills: ${err.message}`);
|
|
172
181
|
}
|
|
@@ -177,20 +186,24 @@ ${skills.specFormat}`;
|
|
|
177
186
|
ensureDir(copilotBase);
|
|
178
187
|
|
|
179
188
|
const { frontmatter: managerFmCp, body: managerBodyCp } = parseFrontmatter(skills.sddManager);
|
|
180
|
-
const { frontmatter: generateFmCp, body: generateBodyCp } = parseFrontmatter(skills.sddGenerate);
|
|
181
189
|
|
|
182
190
|
writeFileSync(
|
|
183
191
|
path.join(copilotBase, 'sdd-manager.instructions.md'),
|
|
184
192
|
`---\napplyTo: "**"\ndescription: "${managerFmCp.description}"\n---\n\n${managerBodyCp}`
|
|
185
193
|
);
|
|
186
|
-
writeFileSync(
|
|
187
|
-
path.join(copilotBase, 'sdd-generate.instructions.md'),
|
|
188
|
-
`---\napplyTo: "**"\ndescription: "${generateFmCp.description}"\n---\n\n${generateBodyCp}`
|
|
189
|
-
);
|
|
190
194
|
writeFileSync(
|
|
191
195
|
path.join(copilotBase, 'opensdd-spec-format.instructions.md'),
|
|
192
196
|
`---\napplyTo: "**"\ndescription: "OpenSDD spec format reference. Defines the structure and rules for behavioral specifications. Referenced by sdd-manager and sdd-generate skills."\n---\n\n${skills.specFormat}`
|
|
193
197
|
);
|
|
198
|
+
|
|
199
|
+
if (isFull) {
|
|
200
|
+
const { frontmatter: generateFmCp, body: generateBodyCp } = parseFrontmatter(skills.sddGenerate);
|
|
201
|
+
|
|
202
|
+
writeFileSync(
|
|
203
|
+
path.join(copilotBase, 'sdd-generate.instructions.md'),
|
|
204
|
+
`---\napplyTo: "**"\ndescription: "${generateFmCp.description}"\n---\n\n${generateBodyCp}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
194
207
|
} catch (err) {
|
|
195
208
|
warnings.push(`Could not install GitHub Copilot skills: ${err.message}`);
|
|
196
209
|
}
|
|
@@ -198,10 +211,12 @@ ${skills.specFormat}`;
|
|
|
198
211
|
// 5. Gemini CLI
|
|
199
212
|
try {
|
|
200
213
|
const geminiPath = path.join(projectRoot, 'GEMINI.md');
|
|
201
|
-
|
|
202
|
-
@.claude/skills/sdd-manager/references/spec-format.md
|
|
203
|
-
|
|
214
|
+
let geminiBody = `@.claude/skills/sdd-manager/SKILL.md
|
|
215
|
+
@.claude/skills/sdd-manager/references/spec-format.md`;
|
|
216
|
+
if (isFull) {
|
|
217
|
+
geminiBody += `\n@.claude/skills/sdd-generate/SKILL.md
|
|
204
218
|
@.claude/skills/sdd-generate/references/spec-format.md`;
|
|
219
|
+
}
|
|
205
220
|
updateManagedSection(geminiPath, geminiBody);
|
|
206
221
|
} catch (err) {
|
|
207
222
|
warnings.push(`Could not install Gemini CLI skills: ${err.message}`);
|
|
@@ -210,10 +225,12 @@ ${skills.specFormat}`;
|
|
|
210
225
|
// 6. Amp
|
|
211
226
|
try {
|
|
212
227
|
const ampPath = path.join(projectRoot, 'AGENTS.md');
|
|
213
|
-
|
|
214
|
-
@.claude/skills/sdd-manager/references/spec-format.md
|
|
215
|
-
|
|
228
|
+
let ampBody = `@.claude/skills/sdd-manager/SKILL.md
|
|
229
|
+
@.claude/skills/sdd-manager/references/spec-format.md`;
|
|
230
|
+
if (isFull) {
|
|
231
|
+
ampBody += `\n@.claude/skills/sdd-generate/SKILL.md
|
|
216
232
|
@.claude/skills/sdd-generate/references/spec-format.md`;
|
|
233
|
+
}
|
|
217
234
|
updateManagedSection(ampPath, ampBody);
|
|
218
235
|
} catch (err) {
|
|
219
236
|
warnings.push(`Could not install Amp skills: ${err.message}`);
|
package/src/lib/validation.js
CHANGED
|
@@ -105,9 +105,9 @@ function validateManifest(manifest, result) {
|
|
|
105
105
|
result.errors.push('manifest.json: Missing name');
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
if (!manifest.
|
|
109
|
-
result.manifestErrors.push('Missing required field:
|
|
110
|
-
result.errors.push('manifest.json: Missing
|
|
108
|
+
if (!manifest.specFormat) {
|
|
109
|
+
result.manifestErrors.push('Missing required field: specFormat');
|
|
110
|
+
result.errors.push('manifest.json: Missing specFormat');
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
if (!manifest.version) {
|