@prodshape/integration-openspec 0.5.3 → 0.6.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -0
- package/assets/openspec-product-change-schema/schema.yaml +151 -0
- package/assets/openspec-product-change-schema/scripts/product-apply.mjs +105 -0
- package/assets/openspec-product-change-schema/scripts/product-validate.mjs +77 -0
- package/assets/openspec-product-change-schema/templates/change.md +51 -0
- package/assets/openspec-product-change-schema/templates/proposal.md +41 -0
- package/assets/openspec-product-recovery-schema/schema.yaml +40 -0
- package/assets/openspec-product-recovery-schema/templates/brief.md +22 -0
- package/assets/openspec-product-recovery-schema/templates/report.md +15 -0
- package/dist/index.d.ts +175 -4
- package/dist/index.js +794 -35
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/index.ts +585 -19
- package/src/product-change-schema.ts +93 -0
- package/src/product-workflow.ts +552 -0
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @prodshape/integration-openspec
|
|
2
|
+
|
|
3
|
+
OpenSpec integration for Product Definition as Code (PDaC), in two lanes: a citation lane that grounds OpenSpec's spec-driven delivery workflow in the accepted product model, and a product lane that hosts the PDaC product workflow inside OpenSpec itself.
|
|
4
|
+
|
|
5
|
+
Both lanes write only official OpenSpec surfaces (`openspec/config.yaml`, `openspec/schemas/product-change/`, and `openspec/schemas/product-recovery/`) plus ProductShape's own integration metadata (`.product/integrations/openspec.json`). The integration never patches OpenSpec-generated commands or skills, never modifies OpenSpec's built-in schemas, and never writes into a native spec-driven change's documents. Every write is byte-gated and ownership-proven per file; `update` replaces or removes only content whose recorded digest still proves ownership, while `remove` preserves unrecorded or hand-edited files. Dry runs report exactly what the real operation would do.
|
|
6
|
+
|
|
7
|
+
## The citation lane (delivery)
|
|
8
|
+
|
|
9
|
+
The integration merges PDaC authority context and citation rules into `openspec/config.yaml` and installs a CI-ready verification example. Spec-driven OpenSpec documents cite canonical product artifacts by id and sha256 digest; `prodshape citations verify --provider openspec` resolves every citation against the model and enforces the scope-declaration population contract. A spec-driven delivery change never edits `docs/product/model`.
|
|
10
|
+
|
|
11
|
+
## The product lane (the hosted product workflow)
|
|
12
|
+
|
|
13
|
+
The integration installs a project-local OpenSpec schema named `product-change` at `openspec/schemas/product-change/`. An OpenSpec change created with that schema hosts a PDaC Product Change: the normative semantic delta (`product-change/change.md` plus `product-change/proposed/**`) against the accepted model in `docs/product/model`, which stays the only source of product truth.
|
|
14
|
+
|
|
15
|
+
For brownfield systems without an accepted baseline, the integration also installs the independent `product-recovery` schema at `openspec/schemas/product-recovery/`. Use `/opsx:new <name> --schema product-recovery`, confirm `recovery/brief.md`, and repeat `/opsx:continue` for bounded evidence rounds. Recovery state and the draft `CHG-INITIAL` remain inside the OpenSpec change container; they never write `.product/generated/recovery/` or `docs/product/changes/active/`. A completed recovery may produce a candidate baseline or an honest insufficient-evidence outcome. Only a human-approved candidate baseline can delegate to the existing Product Change apply rail, and archive remains separate.
|
|
16
|
+
|
|
17
|
+
The schema pin is a load-bearing, enforced invariant: the rail accepts only one folder-safe OpenSpec 1.11 change name and validates the complete OpenSpec 1.11 metadata shape in `.openspec.yaml`, not merely the presence of a schema string. It treats a change as a product change only when that valid metadata pins `schema: product-change`. A change pinned to another schema never lists, validates, applies or enters concurrency, however product-shaped its contents look. A product-shaped container with missing or malformed metadata stops listing and concurrency with a clear error instead of disappearing from the live-change set.
|
|
18
|
+
|
|
19
|
+
The primary workflow is OpenSpec's own:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
/opsx:new <change> --schema product-change
|
|
23
|
+
|
|
|
24
|
+
/opsx:continue (or /opsx:ff) authors proposal.md, then product-change/change.md + product-change/proposed/**
|
|
25
|
+
|
|
|
26
|
+
/opsx:apply <change> follows the schema's apply instruction
|
|
27
|
+
|
|
|
28
|
+
/opsx:archive <change> a separate action after successful apply and verification
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Apply and archive are separate lifecycle operations. The schema's apply instruction delegates the deterministic write to this package's apply rail through the installed bridge script:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
node openspec/schemas/product-change/scripts/product-apply.mjs --change <name> [--dry-run]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The bridge resolves the locally installed `@prodshape/integration-openspec` and calls `applyOpenSpecProductChange`, which re-reads the accepted model and revalidates everything at apply time: the configuration, the baseline itself (including per-document load diagnostics of artifacts the change never touches) and the hosted delta as an overlay; a prior validate call is never trusted. Any blocking diagnostic refuses before any write, with the model and the change container byte-identical. It enforces base-revision drift (PRODUCT027) and the apply-authorised state (PRODUCT028, whose hosted message states the protocol requirement without pretending to verify the actor), writes the delta into the model named by lowercase id, reports the product diff computed from the result, and flips the hosted change.md status to applied in place. It never commits and never archives; `openspec archive` moves the change container afterwards and never touches the model. A preflight bridge exists beside it (`product-validate.mjs`) for explicit overlay validation at any point.
|
|
38
|
+
|
|
39
|
+
Validation is mandatory; authorisation policy is outside the deterministic rail. `status: approved` in the hosted change.md is the apply-authorised protocol state. ProductShape's accepted policy requires human product approval before that state is recorded; the integration verifies the state but cannot identify or judge the actor, and it never performs the transition. Merging the resulting baseline also remains a human decision.
|
|
40
|
+
|
|
41
|
+
Product-semantic clarification happens before the delta. While authoring the intent, the schema asks the agent to identify wording that admits materially different observable outcomes, present those alternatives to the user and stop. An unresolved decision must remain visible under `Open Questions for the Product Owner`; the agent must not choose an outcome, author the delta or seek approval merely to advance the artifact graph. The deterministic rail validates structure and semantics it can prove, but it cannot infer that a human-facing request was ambiguous. `proposal.md` cites the accepted model it consumes; `product-change/change.md` and `product-change/proposed/**` carry no citations or scope declarations, and only the proposed artifacts can be materialized into the accepted model.
|
|
42
|
+
|
|
43
|
+
### Requirements
|
|
44
|
+
|
|
45
|
+
- OpenSpec >= 1.7.0 for the product workflow (`skip_specs` change metadata, declaration-order artifacts, schema-agnostic skill instructions). The schema files install under any supported OpenSpec (>= 1.0.0) and are inert data below the floor: `prodshape doctor` then reports the product workflow UNAVAILABLE while the citation lane keeps working.
|
|
46
|
+
- `@prodshape/integration-openspec` installed locally (`npm install --save-dev @prodshape/integration-openspec`). The ProductShape CLI bundles this package for its own commands, so a CLI-only installation does not put it in `node_modules`; the bridge scripts need the package resolvable from the repository and say so when it is not. This documented installation is proven end to end by the packed-consumer suite, which installs the packed tarballs into a fresh repository and runs the bridge scripts by bare specifier with no override.
|
|
47
|
+
- `@prodshape/cli` available locally for the authoring capabilities the schema instructions name (`prodshape inspect`, `prodshape impact`, `prodshape template <kind>`, `prodshape validate`).
|
|
48
|
+
|
|
49
|
+
## Library API
|
|
50
|
+
|
|
51
|
+
The deterministic rails are exported for wrappers, tests and custom callers; the bridge scripts are thin argument parsers over them.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import {
|
|
55
|
+
inspectProductModel, // accepted model + in-memory graph + diagnostics
|
|
56
|
+
listOpenSpecProductChanges, // OpenSpec changes hosting product-change/change.md
|
|
57
|
+
loadOpenSpecProductChange,
|
|
58
|
+
validateOpenSpecProductChange, // overlay validation; concurrency spans both containers
|
|
59
|
+
applyOpenSpecProductChange, // revalidates at apply time; fail closed; never archives
|
|
60
|
+
deriveDeliveryContext, // fresh post-apply context for a future delivery workflow
|
|
61
|
+
} from '@prodshape/integration-openspec';
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Concurrency (PRODUCT025) spans both change containers: a hosted OpenSpec product change and a native change under `docs/product/changes/active` touching the same artifact report against each other.
|
|
65
|
+
|
|
66
|
+
The citation-lane surface (`addOpenSpecIntegration`, `updateOpenSpecIntegration`, `checkOpenSpecIntegration`, `removeOpenSpecIntegration`, `bootstrapOpenSpecWorkspace`, the population provider) is unchanged and documented in the module source.
|
|
67
|
+
|
|
68
|
+
`addOpenSpecIntegration` and `checkOpenSpecIntegration` detect the OpenSpec CLI because they establish or verify the recorded compatibility verdict. `updateOpenSpecIntegration` uses that verdict from `.product/integrations/openspec.json`, so managed configuration and schema ownership reconciliation remains available when OpenSpec is not on `PATH`; an update without existing integration metadata follows the same detection requirement as a first installation.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# MANAGED FILE. Installed by `prodshape integration add openspec` and regenerated by
|
|
2
|
+
# `prodshape integration update`. Hand edits are reported by `prodshape doctor` and replaced
|
|
3
|
+
# on the next update; remove the integration to take the schema out.
|
|
4
|
+
name: product-change
|
|
5
|
+
version: 1
|
|
6
|
+
description: >-
|
|
7
|
+
PDaC product workflow hosted in OpenSpec: requested product intent becomes a Product Change delta against the accepted model in docs/product/model, deterministically validated as an overlay, then applied by the PDaC integration only for a change in the apply-authorised state. Requires OpenSpec >= 1.7.0. This schema changes product meaning only; implementation work belongs to a separate spec-driven change.
|
|
8
|
+
artifacts:
|
|
9
|
+
- id: intent
|
|
10
|
+
generates: proposal.md
|
|
11
|
+
description: Requested product intent, grounded in the accepted product model
|
|
12
|
+
template: proposal.md
|
|
13
|
+
instruction: |
|
|
14
|
+
Capture WHY the product definition should change, before any delta exists.
|
|
15
|
+
|
|
16
|
+
Run a proportional integrity pass before authoring the first delta. Show a small prioritised
|
|
17
|
+
report: for each changed or proposed node, name the neighbouring candidate, relationship,
|
|
18
|
+
impact polarity and the mechanical reason it merits review. Classify each candidate as
|
|
19
|
+
affected, checked and unaffected, or uncertain. This graph evidence is assistance, never
|
|
20
|
+
authority: it must not expand scope or modify an artifact. Explain why relevant categories
|
|
21
|
+
and neighbours were left unchanged.
|
|
22
|
+
|
|
23
|
+
Ground the intent in the accepted model:
|
|
24
|
+
1. Read the accepted Product Definition (docs/product/model unless .product/config.yaml moves product-root).
|
|
25
|
+
Never treat proposals, archived changes or OpenSpec documents as product truth.
|
|
26
|
+
2. Compare the request with the whole definition, then widen through the product graph:
|
|
27
|
+
for every artifact the request depends on, alters or contradicts, inspect its neighbours and impact.
|
|
28
|
+
The repository's ProductShape tooling provides this deterministically: `prodshape inspect <ID>` and
|
|
29
|
+
`prodshape impact <ID>` resolve the locally installed @prodshape/cli. Install the CLI locally
|
|
30
|
+
(a devDependency counts) rather than relying on network resolution.
|
|
31
|
+
3. List every impacted artifact in the proposal and name any neighbour you checked and left out.
|
|
32
|
+
4. Cite the artifacts the intent stands on: read the digest with `prodshape inspect <ID>`, emit the
|
|
33
|
+
payload with `prodshape cite --id <ID> --digest <digest>`, and wrap it in an HTML comment on its
|
|
34
|
+
own line. Declare the document scope: `pdac-scope: cited` when the proposal carries citations, or
|
|
35
|
+
`pdac-scope: none` with a non-empty human reason when no product text is involved.
|
|
36
|
+
|
|
37
|
+
Before declaring the intent ready, actively look for wording that permits materially different
|
|
38
|
+
product outcomes. Compare those alternatives in product-observable terms. When the accepted model
|
|
39
|
+
and the request do not select one outcome, put the alternatives under "Open Questions for the
|
|
40
|
+
Product Owner", ask the user for the missing decision, and STOP before authoring the delta. Never
|
|
41
|
+
silently select, combine or narrow product meaning on the user's behalf. `None.` is appropriate only
|
|
42
|
+
after this ambiguity pass finds no unresolved decision that would change product behaviour, scope,
|
|
43
|
+
policy or acceptance.
|
|
44
|
+
|
|
45
|
+
Decide the outcome honestly. When the request changes no product meaning (tooling, refactoring,
|
|
46
|
+
delivery-only work), record that verdict under the "No product delta" heading and STOP: do not
|
|
47
|
+
author a delta artifact and do not invent product change to satisfy the workflow. Otherwise state
|
|
48
|
+
the intended product outcome in product language, not implementation language.
|
|
49
|
+
|
|
50
|
+
Keep proposal.md as resumable working memory: record the current queue, checked-and-excluded
|
|
51
|
+
candidates, recommendations, parked items, parked questions and verbatim human answers. Ask exactly one
|
|
52
|
+
question at a time, highest product risk first. A user may stop, park a question, or place a
|
|
53
|
+
possible gap explicitly out of scope. The readiness conclusion must say whether no material
|
|
54
|
+
product decisions remain unresolved, named decisions are required, or named possible gaps
|
|
55
|
+
remain and may be refined now or consciously placed out of scope.
|
|
56
|
+
Record checked and excluded candidates in the proposal even when they need no artifact.
|
|
57
|
+
requires: []
|
|
58
|
+
|
|
59
|
+
- id: delta
|
|
60
|
+
generates: 'product-change/**/*.md'
|
|
61
|
+
description: The PDaC Product Change delta, change.md plus the complete proposed future state
|
|
62
|
+
template: change.md
|
|
63
|
+
instruction: |
|
|
64
|
+
Author the PDaC Product Change under product/ in this change directory: product-change/change.md plus
|
|
65
|
+
product-change/proposed/** holding the COMPLETE future state of every artifact the change adds or
|
|
66
|
+
modifies, laid out exactly as it will live in the model (actors/, journeys/, use-cases/,
|
|
67
|
+
business-rules/, behaviours/, domain/terms/, domain/bounded-contexts/, requirements/functional/,
|
|
68
|
+
requirements/quality/, requirements/constraints/), each file named <lowercase-id>.md.
|
|
69
|
+
|
|
70
|
+
Rules that make the delta valid:
|
|
71
|
+
- Perform the proportional integrity pass again before the first delta, after a material
|
|
72
|
+
widening of intent, when the delta begins to modify additional graph nodes, whenever the
|
|
73
|
+
user explicitly requests another refinement pass, and immediately before approval.
|
|
74
|
+
Editorial corrections do not require a new whole pass. Recompute the explained graph
|
|
75
|
+
candidates; do not expand scope or modify a neighbour automatically.
|
|
76
|
+
- Re-read the intent's "Open Questions for the Product Owner" before writing. Do not author or
|
|
77
|
+
complete the delta while any product-semantic question remains unresolved: ask the user and stop.
|
|
78
|
+
Never choose a product outcome on the user's behalf. Carry the user's answer into one unambiguous
|
|
79
|
+
intended outcome; do not merely delete the question.
|
|
80
|
+
- change.md frontmatter carries id (CHG-...), type: product-change, title, status: draft,
|
|
81
|
+
base-revision (the commit the change was authored against, `git rev-parse --short HEAD`, quoted),
|
|
82
|
+
and operations with all three keys add, modify and remove. The body carries seven sections in
|
|
83
|
+
order: Problem, Intended Product Outcome, Rationale, Affected Product Areas, Open Questions,
|
|
84
|
+
Product Acceptance, Out of Scope.
|
|
85
|
+
- product-change/change.md is a change manifest, not a consumer document. Never add PDaC citations or a
|
|
86
|
+
pdac-scope declaration to it or to product-change/proposed/**. Citations belong in proposal.md and other
|
|
87
|
+
documents that consume the accepted model; proposed artifacts use typed PDaC relationships.
|
|
88
|
+
- Preserve stable IDs: evolve an existing artifact by listing its EXISTING id under
|
|
89
|
+
operations.modify, never by adding a near duplicate under a new id. Create only the artifacts
|
|
90
|
+
the intent requires.
|
|
91
|
+
- Every id in operations.add and operations.modify has a complete proposed artifact under
|
|
92
|
+
product-change/proposed/; nothing else appears there. Removals list existing ids and ship no files.
|
|
93
|
+
- Use the exact current PDaC formats. `prodshape template <kind>` prints the authoring template
|
|
94
|
+
for every kind (actor, journey, use-case, business-rule, domain-term, bounded-context,
|
|
95
|
+
functional-requirement, quality-requirement, constraint, structured-behaviour); it resolves the
|
|
96
|
+
locally installed @prodshape/cli.
|
|
97
|
+
- Keep typed relationships resolvable: primary-actor, steps[].use-case, bounded-context,
|
|
98
|
+
governed-by, uses-terms, applies-to, defined-in, derived-from, illustrates and
|
|
99
|
+
verification[].scenario-ref must point at ids that exist in the baseline or in this delta.
|
|
100
|
+
- Stay in product semantics: no file paths, package names or implementation detail inside product
|
|
101
|
+
artifacts. Add a structured-behaviour only where it clarifies observable product behaviour
|
|
102
|
+
(given/when/then in frontmatter, Intent and Boundaries body sections).
|
|
103
|
+
- Leave status: draft while authoring. Status approved records human product approval under
|
|
104
|
+
ProductShape's accepted policy; that decision happens outside this artifact, and neither
|
|
105
|
+
this workflow nor the integration grants it.
|
|
106
|
+
- Never edit docs/product/model directly; the accepted model changes only through the
|
|
107
|
+
deterministic apply of this change.
|
|
108
|
+
|
|
109
|
+
Preflight is available at any time:
|
|
110
|
+
`node openspec/schemas/product-change/scripts/product-validate.mjs --change <change-name>` validates the
|
|
111
|
+
accepted baseline plus this delta as an overlay without touching either.
|
|
112
|
+
|
|
113
|
+
The semantic requests "refine", "review" and "challenge this product change", and the
|
|
114
|
+
explicit `/product:refine <change>` command, resume this same OpenSpec product-change
|
|
115
|
+
container. The refine adapter may update proposal.md, change.md and proposed artifacts, but
|
|
116
|
+
it must never approve, apply or archive. If no change is identifiable, ask the user to select
|
|
117
|
+
one or create one through the normal OpenSpec workflow; do not create a second lifecycle.
|
|
118
|
+
This is the same OpenSpec change, not a new refinement workflow.
|
|
119
|
+
This is the same OpenSpec change, not a new refinement workflow.
|
|
120
|
+
requires:
|
|
121
|
+
- intent
|
|
122
|
+
|
|
123
|
+
apply:
|
|
124
|
+
requires:
|
|
125
|
+
- delta
|
|
126
|
+
instruction: |
|
|
127
|
+
Apply materializes the approved delta into docs/product/model. It is never implicit, never a
|
|
128
|
+
commit and never an archive.
|
|
129
|
+
|
|
130
|
+
1. Confirm the change is in the apply-authorised state: product-change/change.md carries
|
|
131
|
+
status: approved. That protocol state is required for apply (PRODUCT028 otherwise); the
|
|
132
|
+
integration checks only that protocol state. ProductShape's accepted policy requires human
|
|
133
|
+
product approval; neither this workflow nor the integration grants, infers or judges it.
|
|
134
|
+
Before asking for or recording that state, confirm there are no unresolved product-semantic questions
|
|
135
|
+
in either the intent or the delta. `status: approved` must not be recorded merely to make apply proceed.
|
|
136
|
+
Immediately before asking for approval, run the proportional integrity pass again, record
|
|
137
|
+
the report and distinguish unresolved decisions from optional further depth. Parked decisions
|
|
138
|
+
keep the change unready; consciously excluded gaps must be named under Out of Scope.
|
|
139
|
+
2. Run the deterministic apply through the PDaC integration, dry run first:
|
|
140
|
+
`node openspec/schemas/product-change/scripts/product-apply.mjs --change <change-name> --dry-run`
|
|
141
|
+
then without --dry-run. The script resolves the locally installed
|
|
142
|
+
@prodshape/integration-openspec and calls its apply rail, which re-reads the accepted model,
|
|
143
|
+
revalidates this delta as an overlay at apply time, refuses on any blocking diagnostic with
|
|
144
|
+
docs/product/model untouched, enforces base-revision drift (PRODUCT027) and the
|
|
145
|
+
apply-authorised state (PRODUCT028), writes the delta into the model, reports the product diff
|
|
146
|
+
computed from the result and flips this change's status to applied in place.
|
|
147
|
+
3. Re-read docs/product/model and validate the resulting accepted model: `prodshape validate` must
|
|
148
|
+
report zero errors. Refresh any citations the reported diff made stale.
|
|
149
|
+
4. STOP. Do not archive this change as part of apply: archiving is a separate OpenSpec action
|
|
150
|
+
performed after successful Product apply and verification. Do not implement delivery work, do
|
|
151
|
+
not modify implementation code and never edit docs/product/model by hand.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// MANAGED FILE. Installed by `prodshape integration add openspec` as part of the OpenSpec product
|
|
3
|
+
// schema. The executable bridge from the schema's apply instruction to the deterministic apply
|
|
4
|
+
// rail: the apply logic lives in @prodshape/integration-openspec (applyOpenSpecProductChange);
|
|
5
|
+
// this script only resolves the local installation, invokes it and reports. It revalidates the
|
|
6
|
+
// overlay at apply time, refuses with the model untouched on any blocking diagnostic, requires
|
|
7
|
+
// the apply-authorised state (status: approved) and never archives the change.
|
|
8
|
+
import { dirname, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
|
+
import process from 'node:process';
|
|
11
|
+
|
|
12
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
// openspec/schemas/product-change/scripts sits four levels below the repository root.
|
|
14
|
+
const defaultRoot = resolve(scriptDir, '..', '..', '..', '..');
|
|
15
|
+
|
|
16
|
+
function usage(message) {
|
|
17
|
+
console.error(message);
|
|
18
|
+
console.error(
|
|
19
|
+
'Usage: node openspec/schemas/product-change/scripts/product-apply.mjs --change <name> [--dry-run] [--root <dir>]',
|
|
20
|
+
);
|
|
21
|
+
process.exit(2);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseArgs(argv) {
|
|
25
|
+
const args = { change: undefined, root: undefined, dryRun: false };
|
|
26
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
27
|
+
const arg = argv[i];
|
|
28
|
+
if (arg === '--change') {
|
|
29
|
+
args.change = argv[(i += 1)];
|
|
30
|
+
} else if (arg === '--root') {
|
|
31
|
+
args.root = argv[(i += 1)];
|
|
32
|
+
} else if (arg === '--dry-run') {
|
|
33
|
+
args.dryRun = true;
|
|
34
|
+
} else {
|
|
35
|
+
usage(`Unknown argument: ${arg}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!args.change) usage('Missing --change <name>.');
|
|
39
|
+
return args;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function loadIntegration() {
|
|
43
|
+
const override = process.env.PRODSHAPE_INTEGRATION_OPENSPEC_ENTRY;
|
|
44
|
+
if (override) {
|
|
45
|
+
const url = override.startsWith('file:') ? override : pathToFileURL(resolve(override)).href;
|
|
46
|
+
return import(url);
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return await import('@prodshape/integration-openspec');
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Could not resolve @prodshape/integration-openspec from this repository.');
|
|
52
|
+
console.error('The product workflow bridge needs the integration package installed locally:');
|
|
53
|
+
console.error(' npm install --save-dev @prodshape/integration-openspec');
|
|
54
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
55
|
+
process.exit(2);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function printDiagnostics(diagnostics) {
|
|
60
|
+
for (const diagnostic of diagnostics) {
|
|
61
|
+
const attrs = ['artifact', 'change', 'field', 'target', 'line', 'entry']
|
|
62
|
+
.filter((key) => diagnostic[key] !== undefined)
|
|
63
|
+
.map((key) => `${key}=${diagnostic[key]}`)
|
|
64
|
+
.join(' ');
|
|
65
|
+
console.log(
|
|
66
|
+
`${diagnostic.severity} ${diagnostic.code} ${diagnostic.file}${attrs ? ` ${attrs}` : ''}: ${diagnostic.message}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const args = parseArgs(process.argv.slice(2));
|
|
72
|
+
const repoRoot = args.root ? resolve(args.root) : defaultRoot;
|
|
73
|
+
const integration = await loadIntegration();
|
|
74
|
+
const result = await integration.applyOpenSpecProductChange(repoRoot, args.change, {
|
|
75
|
+
dryRun: args.dryRun,
|
|
76
|
+
});
|
|
77
|
+
printDiagnostics(result.plan.diagnostics);
|
|
78
|
+
if (result.outcome === 'refused') {
|
|
79
|
+
console.log('Apply refused; docs/product/model untouched.');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
for (const action of result.plan.actions) {
|
|
83
|
+
console.log(`${action.kind}: ${action.description}`);
|
|
84
|
+
}
|
|
85
|
+
const { added, modified, removed } = result.plan.diff;
|
|
86
|
+
console.log(
|
|
87
|
+
`Product diff: ${added.length} added, ${modified.length} modified, ${removed.length} removed.`,
|
|
88
|
+
);
|
|
89
|
+
if (result.outcome === 'dry-run') {
|
|
90
|
+
console.log('Dry run; nothing was written.');
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
const resultingErrors = (result.resultingModel?.diagnostics ?? []).filter(
|
|
94
|
+
(diagnostic) => diagnostic.severity === 'error',
|
|
95
|
+
);
|
|
96
|
+
if (resultingErrors.length > 0) {
|
|
97
|
+
printDiagnostics(resultingErrors);
|
|
98
|
+
console.log(
|
|
99
|
+
'Applied, but the resulting accepted model reports errors. Investigate before archiving.',
|
|
100
|
+
);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
console.log(
|
|
104
|
+
'Applied. Nothing was committed and the change was not archived: verify the model, then archive separately.',
|
|
105
|
+
);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// MANAGED FILE. Installed by `prodshape integration add openspec` as part of the OpenSpec product
|
|
3
|
+
// schema. Deterministic overlay preflight for a hosted Product Change: the validation logic lives
|
|
4
|
+
// in @prodshape/integration-openspec; this script only resolves the local installation, invokes it
|
|
5
|
+
// and reports. It writes nothing.
|
|
6
|
+
import { dirname, resolve } from 'node:path';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
+
import process from 'node:process';
|
|
9
|
+
|
|
10
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
// openspec/schemas/product-change/scripts sits four levels below the repository root.
|
|
12
|
+
const defaultRoot = resolve(scriptDir, '..', '..', '..', '..');
|
|
13
|
+
|
|
14
|
+
function usage(message) {
|
|
15
|
+
console.error(message);
|
|
16
|
+
console.error(
|
|
17
|
+
'Usage: node openspec/schemas/product-change/scripts/product-validate.mjs --change <name> [--root <dir>]',
|
|
18
|
+
);
|
|
19
|
+
process.exit(2);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseArgs(argv) {
|
|
23
|
+
const args = { change: undefined, root: undefined };
|
|
24
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
25
|
+
const arg = argv[i];
|
|
26
|
+
if (arg === '--change') {
|
|
27
|
+
args.change = argv[(i += 1)];
|
|
28
|
+
} else if (arg === '--root') {
|
|
29
|
+
args.root = argv[(i += 1)];
|
|
30
|
+
} else {
|
|
31
|
+
usage(`Unknown argument: ${arg}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (!args.change) usage('Missing --change <name>.');
|
|
35
|
+
return args;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function loadIntegration() {
|
|
39
|
+
const override = process.env.PRODSHAPE_INTEGRATION_OPENSPEC_ENTRY;
|
|
40
|
+
if (override) {
|
|
41
|
+
const url = override.startsWith('file:') ? override : pathToFileURL(resolve(override)).href;
|
|
42
|
+
return import(url);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return await import('@prodshape/integration-openspec');
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Could not resolve @prodshape/integration-openspec from this repository.');
|
|
48
|
+
console.error('The product workflow bridge needs the integration package installed locally:');
|
|
49
|
+
console.error(' npm install --save-dev @prodshape/integration-openspec');
|
|
50
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function printDiagnostics(diagnostics) {
|
|
56
|
+
for (const diagnostic of diagnostics) {
|
|
57
|
+
const attrs = ['artifact', 'change', 'field', 'target', 'line', 'entry']
|
|
58
|
+
.filter((key) => diagnostic[key] !== undefined)
|
|
59
|
+
.map((key) => `${key}=${diagnostic[key]}`)
|
|
60
|
+
.join(' ');
|
|
61
|
+
console.log(
|
|
62
|
+
`${diagnostic.severity} ${diagnostic.code} ${diagnostic.file}${attrs ? ` ${attrs}` : ''}: ${diagnostic.message}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const args = parseArgs(process.argv.slice(2));
|
|
68
|
+
const repoRoot = args.root ? resolve(args.root) : defaultRoot;
|
|
69
|
+
const integration = await loadIntegration();
|
|
70
|
+
const result = await integration.validateOpenSpecProductChange(repoRoot, args.change);
|
|
71
|
+
printDiagnostics(result.diagnostics);
|
|
72
|
+
if (result.blocking.length > 0) {
|
|
73
|
+
const count = result.blocking.length;
|
|
74
|
+
console.log(`Overlay validation: FAIL (${count} blocking diagnostic${count === 1 ? '' : 's'}).`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
console.log('Overlay validation: PASS.');
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: CHG-EXAMPLE-001
|
|
3
|
+
type: product-change
|
|
4
|
+
title: Example product change
|
|
5
|
+
status: draft
|
|
6
|
+
base-revision: '3f2a91c'
|
|
7
|
+
operations:
|
|
8
|
+
add:
|
|
9
|
+
- FR-EXAMPLE-001
|
|
10
|
+
modify:
|
|
11
|
+
- UC-EXAMPLE-001
|
|
12
|
+
remove: []
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
This file lives at `product-change/change.md` inside the OpenSpec change directory, and the proposed future state lives beside it under `product-change/proposed/**`, laid out exactly as it will live in `docs/product/model` with files named by lowercase id.
|
|
16
|
+
|
|
17
|
+
This manifest and every artifact under `product-change/proposed/**` are Product Definition content, not consumer documents. Do not add `pdac-scope` declarations or PDaC citations here. Proposed artifacts express their internal connections through typed PDaC relationships; citations remain in `proposal.md` and other documents that consume the accepted model.
|
|
18
|
+
|
|
19
|
+
Every ID under `operations.add` and `operations.modify` needs a complete proposed artifact under `product-change/proposed/`. Author each artifact from its current PDaC template (`prodshape template <kind>`). Set `base-revision` to the baseline commit this change was authored against, quoted so YAML reads an all-digit revision as a string.
|
|
20
|
+
|
|
21
|
+
Leave `status: draft` while authoring; `status: approved` is the apply-authorised protocol state and setting it is the caller's authorisation act, outside this workflow. Apply materializes the proposal into the accepted model on a working branch; it never archives this change, and a pull-request merge accepts the resulting baseline. Product Change status never records implementation, verification, release or deployment.
|
|
22
|
+
|
|
23
|
+
## Problem
|
|
24
|
+
|
|
25
|
+
What is wrong or missing in the current Product Definition? State the problem, not the solution.
|
|
26
|
+
|
|
27
|
+
## Intended Product Outcome
|
|
28
|
+
|
|
29
|
+
What the Product Definition says once this change is accepted. Describe the destination, not the steps.
|
|
30
|
+
|
|
31
|
+
## Rationale
|
|
32
|
+
|
|
33
|
+
Why this outcome, and why now. Record the reasoning a future reader would otherwise have to reconstruct.
|
|
34
|
+
|
|
35
|
+
Distil each accepted decision that explains the delta here, next to the relevant edit. Preserve the human's answer verbatim; do not replace it with an interpretation.
|
|
36
|
+
|
|
37
|
+
## Affected Product Areas
|
|
38
|
+
|
|
39
|
+
Which parts of the product this change touches, in product language rather than file paths.
|
|
40
|
+
|
|
41
|
+
## Open Questions
|
|
42
|
+
|
|
43
|
+
Only genuinely unresolved decisions remain here. Include parked questions and why they are parked. If answering a question could produce materially different product behaviour, ask exactly one question at a time and stop: do not complete this delta or select an answer on the user's behalf. Write `None.` only after every product-semantic question in both the intent and this manifest has been resolved.
|
|
44
|
+
|
|
45
|
+
## Product Acceptance
|
|
46
|
+
|
|
47
|
+
How a reviewer recognises that the accepted definition expresses the intended outcome.
|
|
48
|
+
|
|
49
|
+
## Out of Scope
|
|
50
|
+
|
|
51
|
+
What this change explicitly does not touch, including consciously excluded gaps, delivery, technical design and implementation. A graph neighbour checked and found unaffected belongs here as an explained exclusion, not as a silent omission.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!-- pdac-scope: cited -->
|
|
2
|
+
|
|
3
|
+
# <Requested product change title>
|
|
4
|
+
|
|
5
|
+
Declare the document scope on the first line: keep `pdac-scope: cited` when this proposal carries citations (the normal case), or replace it with `<!-- pdac-scope: none reason="<why no product text is involved>" -->`.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
The request as it arrived: the Issue, ask or observation that started this change, in one or two sentences.
|
|
10
|
+
|
|
11
|
+
## Requested Product Intent
|
|
12
|
+
|
|
13
|
+
What the product should mean once this change is accepted, in product language. Destination, not steps; no implementation detail.
|
|
14
|
+
|
|
15
|
+
## Impacted Product Areas
|
|
16
|
+
|
|
17
|
+
Every accepted artifact this intent depends on, alters or contradicts. One line per artifact: the id, why it is impacted, and a citation payload on its own line directly under it (emit with `prodshape cite --id <ID> --digest <digest>`, never write one by hand).
|
|
18
|
+
|
|
19
|
+
## Neighbours Checked and Left Out
|
|
20
|
+
|
|
21
|
+
Artifacts inspected through the graph (impact, relationships) and judged out of scope, each with the one-line reason. Write `None.` when the impact list already covers everything inspected.
|
|
22
|
+
|
|
23
|
+
## Clarification Report
|
|
24
|
+
|
|
25
|
+
Before interviewing, record a small, prioritised integrity pass. For each changed or proposed node, list the neighbouring candidate, relationship, impact polarity and the mechanical reason it merits review. Classify each candidate as affected, checked and unaffected, or uncertain. The graph is evidence for review, never authority: it must not expand scope or modify an artifact. Explain why relevant categories and neighbours were left unchanged.
|
|
26
|
+
|
|
27
|
+
Repeat the pass before the first delta, after a material widening, when new graph nodes enter the delta, after an explicit `/product:refine` request, and immediately before approval. Editorial corrections do not require a new whole pass. A small clear change may have no questions; a large coherent outcome may remain one change.
|
|
28
|
+
|
|
29
|
+
## Refinement pass
|
|
30
|
+
|
|
31
|
+
Read the existing OpenSpec change before each sitting and reconstruct its state without chat history. Keep this proposal as working memory across sittings. Record the current queue, evidence, checked and excluded candidates, recommendations, parked questions and verbatim human answers. Ask exactly one question at a time, highest product risk first. A user may stop, park a question, or explicitly place a gap out of scope. Do not present the change as ready while a parked decision remains unresolved.
|
|
32
|
+
|
|
33
|
+
The readiness conclusion must distinguish one of: no material product decisions remain unresolved; named decisions are required before completion or approval; or named possible gaps remain and may be refined now or consciously placed out of scope.
|
|
34
|
+
|
|
35
|
+
## No Product Delta
|
|
36
|
+
|
|
37
|
+
Only when the honest verdict is that this request changes no product meaning: state that verdict and the reason, and stop the workflow here (no delta artifact follows). Delete this section otherwise.
|
|
38
|
+
|
|
39
|
+
## Open Questions for the Product Owner
|
|
40
|
+
|
|
41
|
+
Questions that must be answered before the delta can be authored or authorised. First identify any wording that permits materially different observable product outcomes and list the alternatives here. Ask the product owner and stop before the delta until each such decision is answered. Never choose an outcome on the user's behalf. Write `None.` only after this ambiguity pass finds no unresolved decision that would change product behaviour, scope, policy or acceptance.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# MANAGED FILE. Installed by `prodshape integration add openspec`.
|
|
2
|
+
name: product-recovery
|
|
3
|
+
version: 1
|
|
4
|
+
description: >-
|
|
5
|
+
PDaC brownfield recovery hosted in OpenSpec. Evidence is processed in bounded, resumable rounds and converges on a draft CHG-INITIAL Product Change.
|
|
6
|
+
artifacts:
|
|
7
|
+
- id: brief
|
|
8
|
+
generates: recovery/brief.md
|
|
9
|
+
description: Confirmed recovery scope, evidence authority and batch policy
|
|
10
|
+
template: brief.md
|
|
11
|
+
instruction: |
|
|
12
|
+
Agree this brief with the human before reading evidence. The brief is the
|
|
13
|
+
authority for roots, include/exclude/forbidden patterns, ordered tiers,
|
|
14
|
+
batch size and explicitly authorised external sources.
|
|
15
|
+
requires: []
|
|
16
|
+
- id: recovery-outcome
|
|
17
|
+
generates: recovery/report.md
|
|
18
|
+
description: Iterative evidence recovery report and hosted CHG-INITIAL draft
|
|
19
|
+
template: report.md
|
|
20
|
+
instruction: |
|
|
21
|
+
Repeat `/opsx:continue` for one bounded round at a time. Re-inventory and
|
|
22
|
+
re-hash first, stop on drift, then process at most the configured batch.
|
|
23
|
+
Use the deterministic ProductShape recovery capabilities for state changes;
|
|
24
|
+
never edit state files manually. Keep recovery/** and product-change/**
|
|
25
|
+
inside this OpenSpec change container. Distinguish observed evidence from
|
|
26
|
+
inference, preserve provenance and confidence, record contradictions as
|
|
27
|
+
questions, and finish either with a defensible candidate baseline or an
|
|
28
|
+
explicit insufficient-evidence report. Do not approve or apply.
|
|
29
|
+
requires:
|
|
30
|
+
- brief
|
|
31
|
+
apply:
|
|
32
|
+
requires:
|
|
33
|
+
- recovery-outcome
|
|
34
|
+
instruction: |
|
|
35
|
+
Apply is available only for a complete candidate-baseline outcome with a
|
|
36
|
+
human-approved CHG-INITIAL in product-change/change.md. Revalidate fresh
|
|
37
|
+
evidence, recovery completion, model drift and the complete overlay, then
|
|
38
|
+
delegate to the existing Product Change apply rail. An insufficient-evidence
|
|
39
|
+
outcome reports that there is nothing to apply. Never commit or archive;
|
|
40
|
+
`/opsx:archive` remains a separate action.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Recovery brief
|
|
2
|
+
|
|
3
|
+
```yaml
|
|
4
|
+
schema: product-definition-as-code/recovery-brief/v1alpha1
|
|
5
|
+
roots: [.]
|
|
6
|
+
include: ['**/*']
|
|
7
|
+
exclude: []
|
|
8
|
+
forbidden: ['**/.env', '**/.env.*', '**/*.pem', '**/*.key', '**/secrets/**']
|
|
9
|
+
ignore: []
|
|
10
|
+
languages: []
|
|
11
|
+
tiers: []
|
|
12
|
+
batch-size: 10
|
|
13
|
+
external-sources: []
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Product scope
|
|
17
|
+
|
|
18
|
+
## Evidence authority and secondary-evidence policy
|
|
19
|
+
|
|
20
|
+
## Known vocabulary, actors and contradictions
|
|
21
|
+
|
|
22
|
+
## Areas requiring human confirmation
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Recovery outcome
|
|
2
|
+
|
|
3
|
+
This artifact is resumable working memory. It is not accepted product truth.
|
|
4
|
+
|
|
5
|
+
## Progress and next bounded batch
|
|
6
|
+
|
|
7
|
+
## Observed evidence and inferred intent
|
|
8
|
+
|
|
9
|
+
## Candidates and provenance
|
|
10
|
+
|
|
11
|
+
## Questions, contradictions and leads
|
|
12
|
+
|
|
13
|
+
## Outcome
|
|
14
|
+
|
|
15
|
+
State either `Candidate baseline produced` or `Insufficient evidence`. A candidate baseline must reference `product-change/change.md` and its proposed artifacts. An insufficient outcome must contain no invented or empty delta.
|