@prodshape/integration-openspec 0.5.3 → 0.6.0-alpha.0
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 +64 -0
- package/assets/openspec-product-schema/schema.yaml +120 -0
- package/assets/openspec-product-schema/scripts/product-apply.mjs +105 -0
- package/assets/openspec-product-schema/scripts/product-validate.mjs +77 -0
- package/assets/openspec-product-schema/templates/change.md +49 -0
- package/assets/openspec-product-schema/templates/proposal.md +29 -0
- package/dist/index.d.ts +165 -4
- package/dist/index.js +647 -34
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/index.ts +416 -17
- package/src/product-schema.ts +73 -0
- package/src/product-workflow.ts +557 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
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/`) 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: metadata paths are validated and confined to `openspec/schemas/product/`; `prodshape integration add openspec` collides on every pre-existing unrecorded target, even when byte-identical; `update` replaces or removes only content whose recorded digest still proves ownership, including obsolete assets; and `remove` deletes only recorded, proven-managed files while preserving and reporting unrecorded or hand-edited ones. With no recorded product schema it touches nothing under `openspec/schemas/`. 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` at `openspec/schemas/product/`. An OpenSpec change created with that schema hosts a PDaC Product Change: the normative semantic delta (`product/change.md` plus `product/proposed/**`) against the accepted model in `docs/product/model`, which stays the only source of product truth.
|
|
14
|
+
|
|
15
|
+
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`. 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.
|
|
16
|
+
|
|
17
|
+
The primary workflow is OpenSpec's own:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
/opsx:new <change> --schema product
|
|
21
|
+
|
|
|
22
|
+
/opsx:continue (or /opsx:ff) authors proposal.md, then product/change.md + product/proposed/**
|
|
23
|
+
|
|
|
24
|
+
/opsx:apply <change> follows the schema's apply instruction
|
|
25
|
+
|
|
|
26
|
+
/opsx:archive <change> a separate action after successful apply and verification
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
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:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
node openspec/schemas/product/scripts/product-apply.mjs --change <name> [--dry-run]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
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.
|
|
38
|
+
|
|
39
|
+
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.md` and `product/proposed/**` carry no citations or scope declarations, and only the proposed artifacts can be materialized into the accepted model.
|
|
40
|
+
|
|
41
|
+
### Requirements
|
|
42
|
+
|
|
43
|
+
- 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.
|
|
44
|
+
- `@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.
|
|
45
|
+
- `@prodshape/cli` available locally for the authoring capabilities the schema instructions name (`prodshape inspect`, `prodshape impact`, `prodshape template <kind>`, `prodshape validate`).
|
|
46
|
+
|
|
47
|
+
## Library API
|
|
48
|
+
|
|
49
|
+
The deterministic rails are exported for wrappers, tests and custom callers; the bridge scripts are thin argument parsers over them.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import {
|
|
53
|
+
inspectProductModel, // accepted model + in-memory graph + diagnostics
|
|
54
|
+
listOpenSpecProductChanges, // OpenSpec changes hosting product/change.md
|
|
55
|
+
loadOpenSpecProductChange,
|
|
56
|
+
validateOpenSpecProductChange, // overlay validation; concurrency spans both containers
|
|
57
|
+
applyOpenSpecProductChange, // revalidates at apply time; fail closed; never archives
|
|
58
|
+
deriveDeliveryContext, // fresh post-apply context for a future delivery workflow
|
|
59
|
+
} from '@prodshape/integration-openspec';
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
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.
|
|
63
|
+
|
|
64
|
+
The citation-lane surface (`addOpenSpecIntegration`, `updateOpenSpecIntegration`, `checkOpenSpecIntegration`, `removeOpenSpecIntegration`, `bootstrapOpenSpecWorkspace`, the population provider) is unchanged and documented in the module source.
|
|
@@ -0,0 +1,120 @@
|
|
|
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
|
|
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
|
+
Ground the intent in the accepted model:
|
|
17
|
+
1. Read the accepted Product Definition (docs/product/model unless .product/config.yaml moves product-root).
|
|
18
|
+
Never treat proposals, archived changes or OpenSpec documents as product truth.
|
|
19
|
+
2. Compare the request with the whole definition, then widen through the product graph:
|
|
20
|
+
for every artifact the request depends on, alters or contradicts, inspect its neighbours and impact.
|
|
21
|
+
The repository's ProductShape tooling provides this deterministically: `prodshape inspect <ID>` and
|
|
22
|
+
`prodshape impact <ID>` resolve the locally installed @prodshape/cli. Install the CLI locally
|
|
23
|
+
(a devDependency counts) rather than relying on network resolution.
|
|
24
|
+
3. List every impacted artifact in the proposal and name any neighbour you checked and left out.
|
|
25
|
+
4. Cite the artifacts the intent stands on: read the digest with `prodshape inspect <ID>`, emit the
|
|
26
|
+
payload with `prodshape cite --id <ID> --digest <digest>`, and wrap it in an HTML comment on its
|
|
27
|
+
own line. Declare the document scope: `pdac-scope: cited` when the proposal carries citations, or
|
|
28
|
+
`pdac-scope: none` with a non-empty human reason when no product text is involved.
|
|
29
|
+
|
|
30
|
+
Before declaring the intent ready, actively look for wording that permits materially different
|
|
31
|
+
product outcomes. Compare those alternatives in product-observable terms. When the accepted model
|
|
32
|
+
and the request do not select one outcome, put the alternatives under "Open Questions for the
|
|
33
|
+
Product Owner", ask the user for the missing decision, and STOP before authoring the delta. Never
|
|
34
|
+
silently select, combine or narrow product meaning on the user's behalf. `None.` is appropriate only
|
|
35
|
+
after this ambiguity pass finds no unresolved decision that would change product behaviour, scope,
|
|
36
|
+
policy or acceptance.
|
|
37
|
+
|
|
38
|
+
Decide the outcome honestly. When the request changes no product meaning (tooling, refactoring,
|
|
39
|
+
delivery-only work), record that verdict under the "No product delta" heading and STOP: do not
|
|
40
|
+
author a delta artifact and do not invent product change to satisfy the workflow. Otherwise state
|
|
41
|
+
the intended product outcome in product language, not implementation language.
|
|
42
|
+
requires: []
|
|
43
|
+
|
|
44
|
+
- id: delta
|
|
45
|
+
generates: 'product/**/*.md'
|
|
46
|
+
description: The PDaC Product Change delta, change.md plus the complete proposed future state
|
|
47
|
+
template: change.md
|
|
48
|
+
instruction: |
|
|
49
|
+
Author the PDaC Product Change under product/ in this change directory: product/change.md plus
|
|
50
|
+
product/proposed/** holding the COMPLETE future state of every artifact the change adds or
|
|
51
|
+
modifies, laid out exactly as it will live in the model (actors/, journeys/, use-cases/,
|
|
52
|
+
business-rules/, behaviours/, domain/terms/, domain/bounded-contexts/, requirements/functional/,
|
|
53
|
+
requirements/quality/, requirements/constraints/), each file named <lowercase-id>.md.
|
|
54
|
+
|
|
55
|
+
Rules that make the delta valid:
|
|
56
|
+
- Re-read the intent's "Open Questions for the Product Owner" before writing. Do not author or
|
|
57
|
+
complete the delta while any product-semantic question remains unresolved: ask the user and stop.
|
|
58
|
+
Never choose a product outcome on the user's behalf. Carry the user's answer into one unambiguous
|
|
59
|
+
intended outcome; do not merely delete the question.
|
|
60
|
+
- change.md frontmatter carries id (CHG-...), type: product-change, title, status: draft,
|
|
61
|
+
base-revision (the commit the change was authored against, `git rev-parse --short HEAD`, quoted),
|
|
62
|
+
and operations with all three keys add, modify and remove. The body carries seven sections in
|
|
63
|
+
order: Problem, Intended Product Outcome, Rationale, Affected Product Areas, Open Questions,
|
|
64
|
+
Product Acceptance, Out of Scope.
|
|
65
|
+
- product/change.md is a change manifest, not a consumer document. Never add PDaC citations or a
|
|
66
|
+
pdac-scope declaration to it or to product/proposed/**. Citations belong in proposal.md and other
|
|
67
|
+
documents that consume the accepted model; proposed artifacts use typed PDaC relationships.
|
|
68
|
+
- Preserve stable IDs: evolve an existing artifact by listing its EXISTING id under
|
|
69
|
+
operations.modify, never by adding a near duplicate under a new id. Create only the artifacts
|
|
70
|
+
the intent requires.
|
|
71
|
+
- Every id in operations.add and operations.modify has a complete proposed artifact under
|
|
72
|
+
product/proposed/; nothing else appears there. Removals list existing ids and ship no files.
|
|
73
|
+
- Use the exact current PDaC formats. `prodshape template <kind>` prints the authoring template
|
|
74
|
+
for every kind (actor, journey, use-case, business-rule, domain-term, bounded-context,
|
|
75
|
+
functional-requirement, quality-requirement, constraint, structured-behaviour); it resolves the
|
|
76
|
+
locally installed @prodshape/cli.
|
|
77
|
+
- Keep typed relationships resolvable: primary-actor, steps[].use-case, bounded-context,
|
|
78
|
+
governed-by, uses-terms, applies-to, defined-in, derived-from, illustrates and
|
|
79
|
+
verification[].scenario-ref must point at ids that exist in the baseline or in this delta.
|
|
80
|
+
- Stay in product semantics: no file paths, package names or implementation detail inside product
|
|
81
|
+
artifacts. Add a structured-behaviour only where it clarifies observable product behaviour
|
|
82
|
+
(given/when/then in frontmatter, Intent and Boundaries body sections).
|
|
83
|
+
- Leave status: draft while authoring. Status approved records human product approval under
|
|
84
|
+
ProductShape's accepted policy; that decision happens outside this artifact, and neither
|
|
85
|
+
this workflow nor the integration grants it.
|
|
86
|
+
- Never edit docs/product/model directly; the accepted model changes only through the
|
|
87
|
+
deterministic apply of this change.
|
|
88
|
+
|
|
89
|
+
Preflight is available at any time:
|
|
90
|
+
`node openspec/schemas/product/scripts/product-validate.mjs --change <change-name>` validates the
|
|
91
|
+
accepted baseline plus this delta as an overlay without touching either.
|
|
92
|
+
requires:
|
|
93
|
+
- intent
|
|
94
|
+
|
|
95
|
+
apply:
|
|
96
|
+
requires:
|
|
97
|
+
- delta
|
|
98
|
+
instruction: |
|
|
99
|
+
Apply materializes the approved delta into docs/product/model. It is never implicit, never a
|
|
100
|
+
commit and never an archive.
|
|
101
|
+
|
|
102
|
+
1. Confirm the change is in the apply-authorised state: product/change.md carries
|
|
103
|
+
status: approved. That protocol state is required for apply (PRODUCT028 otherwise); the
|
|
104
|
+
integration checks only that protocol state. ProductShape's accepted policy requires human
|
|
105
|
+
product approval; neither this workflow nor the integration grants, infers or judges it.
|
|
106
|
+
Before asking for or recording that state, confirm there are no unresolved product-semantic questions
|
|
107
|
+
in either the intent or the delta. `status: approved` must not be recorded merely to make apply proceed.
|
|
108
|
+
2. Run the deterministic apply through the PDaC integration, dry run first:
|
|
109
|
+
`node openspec/schemas/product/scripts/product-apply.mjs --change <change-name> --dry-run`
|
|
110
|
+
then without --dry-run. The script resolves the locally installed
|
|
111
|
+
@prodshape/integration-openspec and calls its apply rail, which re-reads the accepted model,
|
|
112
|
+
revalidates this delta as an overlay at apply time, refuses on any blocking diagnostic with
|
|
113
|
+
docs/product/model untouched, enforces base-revision drift (PRODUCT027) and the
|
|
114
|
+
apply-authorised state (PRODUCT028), writes the delta into the model, reports the product diff
|
|
115
|
+
computed from the result and flips this change's status to applied in place.
|
|
116
|
+
3. Re-read docs/product/model and validate the resulting accepted model: `prodshape validate` must
|
|
117
|
+
report zero errors. Refresh any citations the reported diff made stale.
|
|
118
|
+
4. STOP. Do not archive this change as part of apply: archiving is a separate OpenSpec action
|
|
119
|
+
performed after successful Product apply and verification. Do not implement delivery work, do
|
|
120
|
+
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/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/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/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/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,49 @@
|
|
|
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.md` inside the OpenSpec change directory, and the proposed future state lives beside it under `product/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/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/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
|
+
## Affected Product Areas
|
|
36
|
+
|
|
37
|
+
Which parts of the product this change touches, in product language rather than file paths.
|
|
38
|
+
|
|
39
|
+
## Open Questions
|
|
40
|
+
|
|
41
|
+
Unresolved questions that need a decision before the change can be authorised. If answering a question could produce materially different product behaviour, scope, policy or acceptance, ask the product owner 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.
|
|
42
|
+
|
|
43
|
+
## Product Acceptance
|
|
44
|
+
|
|
45
|
+
How a reviewer recognises that the accepted definition expresses the intended outcome.
|
|
46
|
+
|
|
47
|
+
## Out of Scope
|
|
48
|
+
|
|
49
|
+
What this change explicitly does not touch, including delivery, technical design and implementation.
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
## No Product Delta
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
## Open Questions for the Product Owner
|
|
28
|
+
|
|
29
|
+
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.
|