opensdd 0.1.2 → 0.1.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/opensdd/cli.md +24 -24
- package/opensdd/skills/sdd-generate.md +3 -3
- package/opensdd/skills/sdd-manager.md +2 -2
- package/opensdd/spec-format.md +19 -19
- package/package.json +2 -2
- package/src/commands/init.js +4 -4
- package/src/commands/install.js +2 -2
- 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 +2 -2
- package/src/lib/validation.js +3 -3
package/opensdd/cli.md
CHANGED
|
@@ -30,9 +30,9 @@ Initializes the OpenSDD protocol in the current project.
|
|
|
30
30
|
|
|
31
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
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", "
|
|
34
|
-
4. Create the `opensdd/` directory (or the directory specified by `
|
|
35
|
-
5. If `<
|
|
33
|
+
3. 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.
|
|
34
|
+
4. Create the `opensdd/` directory (or the directory specified by `specsDir` in an existing `opensdd.json`) if it does not exist.
|
|
35
|
+
5. If `<specsDir>/spec.md` does not exist, create a skeleton `spec.md` with placeholder sections:
|
|
36
36
|
```markdown
|
|
37
37
|
# {project-name}
|
|
38
38
|
|
|
@@ -51,7 +51,7 @@ Initializes the OpenSDD protocol in the current project.
|
|
|
51
51
|
<!-- List properties that must hold true across all inputs and states. -->
|
|
52
52
|
```
|
|
53
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 `
|
|
54
|
+
6. Create the `.opensdd.deps/` directory (or the directory specified by `depsDir` in an existing `opensdd.json`) if it does not exist.
|
|
55
55
|
7. Print a success message.
|
|
56
56
|
|
|
57
57
|
- `opensdd init` in a fresh project MUST produce the output below
|
|
@@ -270,12 +270,12 @@ Fetches a spec from the registry and installs it as a dependency.
|
|
|
270
270
|
#### Behavior
|
|
271
271
|
|
|
272
272
|
1. Verify `opensdd.json` exists at the project root. If not, print a message suggesting `opensdd init` first and exit with code 1.
|
|
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 `<
|
|
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 `<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
274
|
3. Validate the spec name (lowercase alphanumeric and hyphens only).
|
|
275
275
|
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`, `
|
|
276
|
+
5. Read `manifest.json` from `registry/<name>/<version>/` to get specFormat and dependencies.
|
|
277
|
+
6. Copy all files from `registry/<name>/<version>/` into `<depsDir>/<name>/` (including `manifest.json`, `spec.md`, and any supplementary files).
|
|
278
|
+
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
279
|
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
280
|
9. Print a success message.
|
|
281
281
|
|
|
@@ -310,15 +310,15 @@ Fetches the latest version of installed dependency specs from the registry, upda
|
|
|
310
310
|
|
|
311
311
|
1. If `<name>` is provided, update that single spec. If no name is provided, update all installed dependencies.
|
|
312
312
|
2. For each spec being updated:
|
|
313
|
-
a. Read the spec's entry in `opensdd.json` `dependencies` to get the installed version and `
|
|
313
|
+
a. Read the spec's entry in `opensdd.json` `dependencies` to get the installed version and `specFormat` version.
|
|
314
314
|
b. Fetch `index.json` from the registry to get the latest version. Read `manifest.json` from the latest version directory.
|
|
315
315
|
c. If the registry version matches the installed version, skip with a message "already up to date".
|
|
316
316
|
d. Before overwriting, compute unified diffs of all spec-owned files that will change.
|
|
317
|
-
e. Overwrite all spec-owned files in `<
|
|
317
|
+
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
318
|
f. MUST NOT overwrite or delete `deviations.md`. The CLI MUST NOT create, modify, or delete `deviations.md` under any circumstances.
|
|
319
319
|
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`, `
|
|
320
|
+
- `changeset.md` — contains previous and new version, `specFormat` version change (if any), and unified diffs from step (d).
|
|
321
|
+
- `manifest.json` — contains the metadata needed to finalize the update in `opensdd.json`: `name`, `previousVersion`, `version`, `source`, `specFormat`.
|
|
322
322
|
3. Print a summary of what was updated.
|
|
323
323
|
|
|
324
324
|
#### Input
|
|
@@ -376,7 +376,7 @@ Applies a staged update to `opensdd.json`, confirming that the migration is comp
|
|
|
376
376
|
3. Prompt the user for confirmation (y/n). If declined, exit with code 0.
|
|
377
377
|
4. For each pending update:
|
|
378
378
|
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 `
|
|
379
|
+
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
380
|
c. Delete the `.opensdd.deps/.updates/<name>/` directory.
|
|
381
381
|
5. If `.opensdd.deps/.updates/` is now empty, delete it.
|
|
382
382
|
6. Print a summary.
|
|
@@ -431,14 +431,14 @@ Publishes an authored spec to the registry.
|
|
|
431
431
|
|
|
432
432
|
1. Verify `opensdd.json` exists at the project root. If not, print a message suggesting `opensdd init` first and exit with code 1.
|
|
433
433
|
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 `<
|
|
434
|
+
3. Read the `publish` object to get `name`, `version`, `description`, `specFormat`, `dependencies`.
|
|
435
|
+
4. Verify `<specsDir>/spec.md` exists. If not, print error and exit with code 1.
|
|
436
|
+
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
437
|
6. Resolve the registry source. The registry MUST be a GitHub repository URL for publishing.
|
|
438
438
|
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
439
|
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 `<
|
|
440
|
+
a. Build `manifest.json` from the `opensdd.json` `publish` fields (`name`, `version`, `description`, `specFormat`, `dependencies`).
|
|
441
|
+
b. Collect all files from `<specsDir>/`.
|
|
442
442
|
9. If `--branch <name>` was provided, use that as the branch name. Otherwise, prompt the user for a branch name.
|
|
443
443
|
10. Clone the registry repo (shallow), create a new branch with the chosen name, and:
|
|
444
444
|
a. Create `registry/<name>/<version>/` with `manifest.json` and all spec files.
|
|
@@ -472,7 +472,7 @@ Published. Spec will be available after PR is merged.
|
|
|
472
472
|
|
|
473
473
|
- OpenSDD not initialized: print message suggesting `opensdd init` and exit with code 1.
|
|
474
474
|
- No `publish` section in `opensdd.json`: print error and exit with code 1.
|
|
475
|
-
- `<
|
|
475
|
+
- `<specsDir>/spec.md` missing: print error and exit with code 1.
|
|
476
476
|
- Spec validation fails: print validation errors and exit with code 1.
|
|
477
477
|
- Version already exists in registry: print error suggesting version bump and exit with code 1.
|
|
478
478
|
- Registry is not a GitHub URL: print error (publishing requires a GitHub registry) and exit with code 1.
|
|
@@ -487,8 +487,8 @@ Shows the status of the authored spec and all installed dependency specs in the
|
|
|
487
487
|
|
|
488
488
|
1. Read `opensdd.json`.
|
|
489
489
|
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 `<
|
|
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 `<depsDir>/<name>/`. Print a dependency status table.
|
|
491
|
+
4. Check for untracked directories: spec directories in `<depsDir>` that have no corresponding `opensdd.json` dependency entry. Warn about any found.
|
|
492
492
|
|
|
493
493
|
#### Output
|
|
494
494
|
|
|
@@ -520,7 +520,7 @@ Validates that a spec directory conforms to the OpenSDD spec-format.
|
|
|
520
520
|
3. Check for required files: `spec.md` MUST exist.
|
|
521
521
|
4. If `manifest.json` exists, validate it:
|
|
522
522
|
a. `name` MUST be present.
|
|
523
|
-
b. `
|
|
523
|
+
b. `specFormat` MUST be present and be a recognized version.
|
|
524
524
|
c. `version` MUST be present and be valid semver.
|
|
525
525
|
5. Validate `spec.md`:
|
|
526
526
|
a. MUST start with an H1 header followed by a blockquote summary.
|
|
@@ -607,7 +607,7 @@ For local paths, the CLI MUST read directly from the filesystem. This supports d
|
|
|
607
607
|
During `opensdd update apply`, the CLI MUST preserve these `opensdd.json` fields for each affected dependency entry:
|
|
608
608
|
- `implementation`
|
|
609
609
|
- `tests`
|
|
610
|
-
- `
|
|
610
|
+
- `hasDeviations`
|
|
611
611
|
|
|
612
612
|
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
613
|
|
|
@@ -651,7 +651,7 @@ The CLI reads the existing `opensdd.json` dependency entry, applies updated meta
|
|
|
651
651
|
- The Claude Code skill installation (`.claude/skills/`) MUST always be present since Gemini CLI and Amp reference it
|
|
652
652
|
- `opensdd.json` MUST be created by `opensdd init` if it does not exist, and MUST NOT be overwritten if it already exists
|
|
653
653
|
- Consumer-managed `opensdd.json` fields MUST survive all update operations
|
|
654
|
-
- Every installed dependency MUST have both a directory in `
|
|
654
|
+
- Every installed dependency MUST have both a directory in `depsDir` and an entry in `opensdd.json` `dependencies`
|
|
655
655
|
- All commands MUST exit with code 0 on success and code 1 on error
|
|
656
656
|
- The CLI MUST NOT invoke an AI model or coding agent
|
|
657
657
|
- `opensdd publish` MUST NOT allow overwriting an existing version in the registry
|
|
@@ -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`
|
|
@@ -40,7 +40,7 @@ The dependency spec (`spec.md`) is the authoritative description of what to buil
|
|
|
40
40
|
|
|
41
41
|
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
42
|
|
|
43
|
-
6. **Record:** Update `opensdd.json` `dependencies` entry with `implementation` path, `tests` path, and `
|
|
43
|
+
6. **Record:** Update `opensdd.json` `dependencies` entry with `implementation` path, `tests` path, and `hasDeviations` if applicable.
|
|
44
44
|
|
|
45
45
|
7. **Report:** Report results with spec coverage summary.
|
|
46
46
|
|
|
@@ -59,7 +59,7 @@ Run existing test suite → report pass/fail. If test suite is missing or stale,
|
|
|
59
59
|
|
|
60
60
|
### Create Deviation
|
|
61
61
|
|
|
62
|
-
Determine affected spec section → classify type → create/append to `deviations.md` in `.opensdd.deps/<name>/` → update test suite to skip affected tests → update `
|
|
62
|
+
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
63
|
|
|
64
64
|
## Universal Implementation Defaults
|
|
65
65
|
|
package/opensdd/spec-format.md
CHANGED
|
@@ -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.
|
|
@@ -255,23 +255,23 @@ The `opensdd.json` file is the project-level manifest. It lives at the project r
|
|
|
255
255
|
{
|
|
256
256
|
"opensdd": "0.1.0",
|
|
257
257
|
"registry": "https://github.com/deepagents-ai/opensdd",
|
|
258
|
-
"
|
|
259
|
-
"
|
|
258
|
+
"specsDir": "opensdd",
|
|
259
|
+
"depsDir": ".opensdd.deps",
|
|
260
260
|
"publish": {
|
|
261
261
|
"name": "auth",
|
|
262
262
|
"version": "1.0.0",
|
|
263
263
|
"description": "Authentication with multiple provider support",
|
|
264
|
-
"
|
|
264
|
+
"specFormat": "0.1.0",
|
|
265
265
|
"dependencies": []
|
|
266
266
|
},
|
|
267
267
|
"dependencies": {
|
|
268
268
|
"slugify": {
|
|
269
269
|
"version": "2.1.0",
|
|
270
270
|
"source": "https://github.com/deepagents-ai/opensdd",
|
|
271
|
-
"
|
|
271
|
+
"specFormat": "0.1.0",
|
|
272
272
|
"implementation": null,
|
|
273
273
|
"tests": null,
|
|
274
|
-
"
|
|
274
|
+
"hasDeviations": false
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
277
|
}
|
|
@@ -281,8 +281,8 @@ The `opensdd.json` file is the project-level manifest. It lives at the project r
|
|
|
281
281
|
|
|
282
282
|
- `opensdd` (required): Protocol version string. Agents and the CLI MUST use this to determine how to interpret the manifest.
|
|
283
283
|
- `registry` (optional): URL of the default registry. Overridden by the CLI's `--registry` flag. Default: `"https://github.com/deepagents-ai/opensdd"`.
|
|
284
|
-
- `
|
|
285
|
-
- `
|
|
284
|
+
- `specsDir` (optional): Relative path from the project root to the directory containing the authored spec. Default: `"opensdd"`.
|
|
285
|
+
- `depsDir` (optional): Relative path from the project root to the directory containing installed dependency specs. Default: `".opensdd.deps"`.
|
|
286
286
|
- `publish` (optional): Object defining the spec this project publishes. Omit if the project only consumes specs.
|
|
287
287
|
- `dependencies` (optional): Object keyed by spec name. Each entry tracks an installed dependency spec. Omit if the project only publishes specs.
|
|
288
288
|
|
|
@@ -291,17 +291,17 @@ The `opensdd.json` file is the project-level manifest. It lives at the project r
|
|
|
291
291
|
- `name` (required): Bare spec name — lowercase alphanumeric and hyphens only.
|
|
292
292
|
- `version` (required): Semver version of the spec being developed.
|
|
293
293
|
- `description` (required): One-line description for registry display.
|
|
294
|
-
- `
|
|
294
|
+
- `specFormat` (required): Which version of the OpenSDD protocol this spec targets.
|
|
295
295
|
- `dependencies` (optional): Array of bare spec names that this spec references for shared types or behavioral contracts.
|
|
296
296
|
|
|
297
297
|
#### Dependency entry fields
|
|
298
298
|
|
|
299
299
|
- `version` (required): Semver version of the installed spec.
|
|
300
300
|
- `source` (required): URL of the registry this spec was installed from.
|
|
301
|
-
- `
|
|
301
|
+
- `specFormat` (required): OpenSDD protocol version of the installed spec.
|
|
302
302
|
- `implementation` (consumer-managed): Path to the generated implementation file, `null` until implemented.
|
|
303
303
|
- `tests` (consumer-managed): Path to the generated test file, `null` until implemented.
|
|
304
|
-
- `
|
|
304
|
+
- `hasDeviations` (consumer-managed): Boolean, `false` until a deviation is created.
|
|
305
305
|
|
|
306
306
|
Consumer-managed fields MUST be present with explicit `null` or `false` values rather than omitted. Fields MUST survive all update operations.
|
|
307
307
|
|
|
@@ -338,8 +338,8 @@ Each spec in the registry MUST have an `index.json` at its root:
|
|
|
338
338
|
"description": "String to URL-friendly slug",
|
|
339
339
|
"latest": "2.2.0",
|
|
340
340
|
"versions": {
|
|
341
|
-
"2.1.0": { "
|
|
342
|
-
"2.2.0": { "
|
|
341
|
+
"2.1.0": { "specFormat": "0.1.0" },
|
|
342
|
+
"2.2.0": { "specFormat": "0.1.0" }
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
345
|
```
|
|
@@ -347,7 +347,7 @@ Each spec in the registry MUST have an `index.json` at its root:
|
|
|
347
347
|
- `name` (required): Bare spec name.
|
|
348
348
|
- `description` (required): One-line description.
|
|
349
349
|
- `latest` (required): The most recent published version.
|
|
350
|
-
- `versions` (required): Object keyed by semver version string. Each entry MAY include summary metadata (e.g., `
|
|
350
|
+
- `versions` (required): Object keyed by semver version string. Each entry MAY include summary metadata (e.g., `specFormat`).
|
|
351
351
|
|
|
352
352
|
#### manifest.json (per version)
|
|
353
353
|
|
|
@@ -357,7 +357,7 @@ Each version directory MUST contain a `manifest.json`:
|
|
|
357
357
|
{
|
|
358
358
|
"name": "slugify",
|
|
359
359
|
"version": "2.2.0",
|
|
360
|
-
"
|
|
360
|
+
"specFormat": "0.1.0",
|
|
361
361
|
"description": "String to URL-friendly slug",
|
|
362
362
|
"dependencies": []
|
|
363
363
|
}
|
|
@@ -365,7 +365,7 @@ Each version directory MUST contain a `manifest.json`:
|
|
|
365
365
|
|
|
366
366
|
- `name` (required): Bare spec name.
|
|
367
367
|
- `version` (required): Semver version of this entry.
|
|
368
|
-
- `
|
|
368
|
+
- `specFormat` (required): OpenSDD protocol version.
|
|
369
369
|
- `description` (required): One-line description.
|
|
370
370
|
- `dependencies` (optional): Array of bare spec names.
|
|
371
371
|
|
|
@@ -429,10 +429,10 @@ Contains the metadata needed to finalize the `opensdd.json` dependency entry whe
|
|
|
429
429
|
```json
|
|
430
430
|
{
|
|
431
431
|
"name": "slugify",
|
|
432
|
-
"
|
|
432
|
+
"previousVersion": "2.1.0",
|
|
433
433
|
"version": "2.2.0",
|
|
434
434
|
"source": "https://github.com/deepagents-ai/opensdd",
|
|
435
|
-
"
|
|
435
|
+
"specFormat": "0.1.0"
|
|
436
436
|
}
|
|
437
437
|
```
|
|
438
438
|
|
|
@@ -484,7 +484,7 @@ Specs use semantic versioning:
|
|
|
484
484
|
- Spec-owned files in `.opensdd.deps/` MUST NOT be modified by the consumer or their agent
|
|
485
485
|
- `deviations.md` MUST NOT be created, modified, or deleted by the CLI or any automated tooling
|
|
486
486
|
- Consumer-managed fields in `opensdd.json` MUST survive all update operations
|
|
487
|
-
- Every installed dependency spec MUST have both a directory in `
|
|
487
|
+
- Every installed dependency spec MUST have both a directory in `depsDir` and an entry in `opensdd.json` `dependencies`
|
|
488
488
|
- All behaviors described in the spec MUST be thoroughly tested by the implementing agent
|
|
489
489
|
- A `spec.md` MUST contain an H1 header with blockquote summary and a `## Behavioral Contract` section
|
|
490
490
|
- `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.3",
|
|
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
|
@@ -118,15 +118,15 @@ export async function initCommand() {
|
|
|
118
118
|
if (!manifest) {
|
|
119
119
|
manifest = {
|
|
120
120
|
opensdd: '0.1.0',
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
specsDir: 'opensdd',
|
|
122
|
+
depsDir: '.opensdd.deps',
|
|
123
123
|
};
|
|
124
124
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
125
125
|
manifestCreated = true;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
const specsDir = manifest.
|
|
129
|
-
const depsDir = manifest.
|
|
128
|
+
const specsDir = manifest.specsDir || 'opensdd';
|
|
129
|
+
const depsDir = manifest.depsDir || '.opensdd.deps';
|
|
130
130
|
const specsDirPath = path.join(cwd, specsDir);
|
|
131
131
|
const depsDirPath = path.join(cwd, depsDir);
|
|
132
132
|
|
package/src/commands/install.js
CHANGED
|
@@ -132,10 +132,10 @@ export async function installCommand(name, version, options) {
|
|
|
132
132
|
manifest.dependencies[name] = {
|
|
133
133
|
version: resolvedVersion,
|
|
134
134
|
source: registrySource,
|
|
135
|
-
|
|
135
|
+
specFormat: specManifest.specFormat || '0.1.0',
|
|
136
136
|
implementation: existingEntry?.implementation ?? null,
|
|
137
137
|
tests: existingEntry?.tests ?? null,
|
|
138
|
-
|
|
138
|
+
hasDeviations: existingEntry?.hasDeviations ?? false,
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
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
|
@@ -30,9 +30,9 @@ export function writeManifest(manifestPath, data) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function getSpecsDir(manifest) {
|
|
33
|
-
return manifest.
|
|
33
|
+
return manifest.specsDir || 'opensdd';
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export function getDepsDir(manifest) {
|
|
37
|
-
return manifest.
|
|
37
|
+
return manifest.depsDir || '.opensdd.deps';
|
|
38
38
|
}
|
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) {
|