triad-plus 1.6.0 → 1.8.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +26 -0
  3. package/adapters/antigravity/.agents/agents/triad-evaluator/agent.md +5 -0
  4. package/adapters/claude-code/.claude/agents/triad-evaluator.md +5 -0
  5. package/adapters/copilot/.github/agents/triad-evaluator.agent.md +5 -0
  6. package/adapters/hermes/skills/triad/SKILL.md +5 -0
  7. package/adapters/opencode/.opencode/agents/triad-evaluator.md +6 -0
  8. package/bin/triad-plus.js +28 -3
  9. package/docs/architecture.md +7 -0
  10. package/docs/bmad-integration.md +110 -0
  11. package/docs/configuration.md +19 -0
  12. package/docs/evaluator-plus.md +9 -0
  13. package/docs/operating-guide.it.md +10 -0
  14. package/docs/operating-guide.md +10 -0
  15. package/docs/quality-contract.md +119 -0
  16. package/docs/verification.md +17 -0
  17. package/integrations/bmad/README.md +12 -0
  18. package/integrations/bmad/story-importer.mjs +475 -0
  19. package/package.json +2 -2
  20. package/runtime/lib/quality-baseline.mjs +205 -0
  21. package/runtime/triad-evaluator-validate.mjs +261 -0
  22. package/runtime/triad-verify.mjs +19 -5
  23. package/schemas/evaluator-plus-result.schema.json +16 -0
  24. package/schemas/quality-baseline.schema.json +42 -0
  25. package/schemas/verification-evidence.schema.json +1 -1
  26. package/skills/triad-loop-bootstrap/SKILL.md +8 -0
  27. package/skills/triad-loop-bootstrap/assets/loop-template/handoff-report.template.md +13 -0
  28. package/skills/triad-loop-bootstrap/assets/loop-template/quality-baseline.json +15 -0
  29. package/skills/triad-loop-bootstrap/assets/loop-template/run-state.yaml +7 -0
  30. package/skills/triad-loop-bootstrap/assets/loop-template/runtime/assignments/assignment.template.json +2 -0
  31. package/skills/triad-loop-bootstrap/assets/project.yaml +5 -0
  32. package/skills/triad-loop-evaluator/SKILL.md +12 -2
  33. package/skills/triad-loop-orchestrator/SKILL.md +51 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.8.0 — 2026-09-12
4
+
5
+ - Add an optional Immutable Quality Contract with a deterministic Quality
6
+ Baseline fingerprint and fail-closed source-drift detection.
7
+ - Add phase-scoped `product_quality` and `delivery_closure` criteria,
8
+ criterion-level Evaluator+ validation, and a deterministic aggregate
9
+ verdict.
10
+ - Add deterministic Evaluator+/Delivery control-plane validation while
11
+ preserving legacy Triad+ 1.7 compatibility.
12
+
13
+ ## 1.7.0 — 2026-09-10
14
+
15
+ - Add an optional deterministic importer for one BMAD `ready-for-dev` Story at
16
+ a time, preserving the normal Triad Card contract and source provenance.
17
+
3
18
  ## 1.6.0 — 2026-09-05
4
19
 
5
20
  - Add card-declared repository `required_gates` with additive per-card gate
package/README.md CHANGED
@@ -107,6 +107,32 @@ The Orchestrator first shows the feature cards, then delegates the bounded work.
107
107
  If a tutorial step is unclear, see the [OpenCode guide](docs/runtimes.md#opencode)
108
108
  and [troubleshooting](docs/troubleshooting.md).
109
109
 
110
+ ## Optional immutable quality target
111
+
112
+ An initialized project may bind an immutable JSON Quality Baseline through
113
+ `project.quality_contract`. Triad fingerprints the canonical manifest, verifies
114
+ all declared sources before running costly gates, and fails closed on malformed
115
+ contracts or source drift. `product_quality` criteria are evaluated one by one
116
+ by a fresh Evaluator+; `delivery_closure` criteria remain in the delivery gate.
117
+ Projects without this opt-in continue to use the legacy PRD baseline path.
118
+
119
+ ## Optional BMAD Story import
120
+
121
+ When BMAD planning has already produced a Story with `status: ready-for-dev`,
122
+ you can convert one Story at a time into the normal Triad Card contract:
123
+
124
+ ```bash
125
+ npx triad-plus import-bmad-story \
126
+ --source /absolute/path/to/story.md \
127
+ --output /absolute/path/to/control/features/STORY-001.md
128
+ ```
129
+
130
+ The importer is read-only and fail-closed. It preserves the Story's executable
131
+ intent, acceptance criteria, technical context, and references, while any
132
+ required gates or dependencies remain explicit caller options. It does not run
133
+ BMAD workflows or add BMAD semantics to the Core. See the [BMAD integration
134
+ guide](docs/bmad-integration.md).
135
+
110
136
  ## Quick start for every runtime
111
137
 
112
138
  Requirements: Node.js 20+ and one supported coding-agent host.
@@ -16,3 +16,8 @@ Return `candidate_wins`, `bar_wins`, or `indeterminate`; a `bar_wins` decision
16
16
  has exactly one evidence-based largest gap. Do not edit source, change loop
17
17
  state, commit, push, or review delivery, request repair, or reopen the Triad
18
18
  run.
19
+
20
+ For an immutable Quality Contract, use the supplied baseline fingerprint and
21
+ evaluate each `product_quality` criterion exactly once. `delivery_closure`
22
+ criteria are excluded from this fresh packet and belong to delivery closure;
23
+ the control plane validates the final aggregate.
@@ -23,3 +23,8 @@ Return `candidate_wins`, `bar_wins`, or `indeterminate`. A `bar_wins` result has
23
23
  exactly one evidence-based largest gap with one bounded repair scope. Do not edit
24
24
  product source, change state, commit, push, or approve delivery; do not request
25
25
  repair or reopen the Triad run.
26
+
27
+ For an immutable Quality Contract, report the supplied baseline fingerprint and
28
+ evaluate every supplied `product_quality` criterion once. `delivery_closure`
29
+ criteria are not part of this packet and are checked by delivery closure. The
30
+ control plane, not the model, determines the deterministic aggregate verdict.
@@ -24,3 +24,8 @@ out-of-packet control record to justify a verdict. Return `PASS`, `FAIL`, or
24
24
  Do not edit source, change Triad queue/state, assign work, commit, push, publish,
25
25
  or start repair. Your verdict is post-run information. It never reopens Triad,
26
26
  requests repair, or changes an already closed result.
27
+
28
+ When an immutable Quality Contract is present, report its fingerprint and one
29
+ result for every supplied `product_quality` criterion. Do not receive or judge
30
+ `delivery_closure` criteria; the control plane validates the deterministic
31
+ aggregate rather than trusting a model-selected overall verdict.
@@ -51,3 +51,8 @@ post-run report at the absolute `<control-workspace>/artifacts/evaluator-plus/`
51
51
  path supplied by the Orchestrator; never infer it relative to a product worktree.
52
52
  `FAIL` or `INDETERMINATE` is information for a new
53
53
  owner-requested run, never an automatic repair and never reopens Triad.
54
+
55
+ If an immutable Quality Contract is configured, pass only its fingerprint and
56
+ `product_quality` criteria to the fresh Evaluator+. Exclude `delivery_closure`
57
+ criteria, which are recorded by delivery closure. Return one result per product
58
+ criterion; the shared control plane validates coverage and aggregate verdict.
@@ -36,3 +36,9 @@ result being assessed.
36
36
  Return `PASS`, `FAIL`, or `INDETERMINATE` with concise evidence. Do not edit
37
37
  source, make delivery decisions, commit, push, or change workflow state; do not
38
38
  request repair. A verdict never reopens Triad or starts repair.
39
+
40
+ If the packet includes an immutable Quality Contract, use its fingerprint and
41
+ evaluate exactly the supplied `product_quality` criteria once each. Never
42
+ evaluate `delivery_closure` criteria; delivery closure records those separately.
43
+ The control plane validates the deterministic aggregate, so do not invent a
44
+ different overall verdict.
package/bin/triad-plus.js CHANGED
@@ -8,6 +8,7 @@ import { dirname, join, resolve } from 'node:path';
8
8
  import process from 'node:process';
9
9
  import { createInterface } from 'node:readline/promises';
10
10
  import { getAdapter, listAdapters, roleDefinitions, sharedSkillNames } from '../adapters/registry.mjs';
11
+ import { writeImportedCard } from '../integrations/bmad/story-importer.mjs';
11
12
 
12
13
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
13
14
 
@@ -20,6 +21,7 @@ Usage:
20
21
  npx triad-plus init --host <adapter-id> --control <path> [--global] [--team-config <path>] [--allow-product-repo]
21
22
  npx triad-plus doctor --host <adapter-id> --control <path> [--hook-config <path>]
22
23
  npx triad-plus upgrade --host <adapter-id> --control <path> [--global] [--apply]
24
+ npx triad-plus import-bmad-story --source <story.md> --output <card.md> [--target-repository <id>] [--provenance <record.json>] [--required-gate <id>] [--depends-on <card-id>]
23
25
 
24
26
  Adapters: ${listAdapters().map((adapter) => adapter.id).join(', ')}
25
27
 
@@ -31,13 +33,20 @@ Installation refuses every asset overwrite. Upgrade is a dry run unless --apply
31
33
 
32
34
  function parseArgs(args) {
33
35
  const [command, ...rest] = args;
34
- const options = { command, global: false, allowProductRepo: false, apply: false };
36
+ const options = { command, global: false, allowProductRepo: false, apply: false, requiredGates: [], dependsOn: [] };
35
37
  for (let index = 0; index < rest.length; index += 1) {
36
38
  const argument = rest[index];
37
39
  if (argument === '--global') options.global = true;
38
40
  else if (argument === '--allow-product-repo') options.allowProductRepo = true;
39
41
  else if (argument === '--apply') options.apply = true;
40
- else if (['--host', '--control', '--team-config', '--hook-config'].includes(argument)) {
42
+ else if (argument === '--required-gate' || argument === '--depends-on') {
43
+ const value = rest[index + 1];
44
+ if (!value || value.startsWith('--')) throw new Error(`${argument} requires a value.`);
45
+ const target = argument === '--required-gate' ? options.requiredGates : options.dependsOn;
46
+ target.push(value);
47
+ index += 1;
48
+ }
49
+ else if (['--host', '--control', '--team-config', '--hook-config', '--source', '--output', '--target-repository', '--provenance'].includes(argument)) {
41
50
  const value = rest[index + 1];
42
51
  if (!value || value.startsWith('--')) throw new Error(`${argument} requires a value.`);
43
52
  options[argument.slice(2).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())] = value;
@@ -395,6 +404,21 @@ async function upgrade(options) {
395
404
  if (!options.apply) process.stdout.write('Dry run only. Re-run with --apply to update managed assets.\n');
396
405
  }
397
406
 
407
+ async function importBmadStory(options) {
408
+ if (!options.source) throw new Error('Provide --source <bmad-story.md>.');
409
+ if (!options.output) throw new Error('Provide --output <triad-card.md>.');
410
+ const result = await writeImportedCard({
411
+ sourcePath: options.source,
412
+ outputPath: options.output,
413
+ targetRepository: options.targetRepository,
414
+ provenancePath: options.provenance,
415
+ requiredGates: options.requiredGates,
416
+ dependsOn: options.dependsOn
417
+ });
418
+ process.stdout.write(`Imported BMAD Story ${result.story.id} as Triad Card ${result.outputPath}\n`);
419
+ process.stdout.write(`Provenance ${result.provenancePath}\n`);
420
+ }
421
+
398
422
  async function doctor(options) {
399
423
  if (!options.control) throw new Error('Provide --control <project-control-path>.');
400
424
  const controlRoot = resolve(options.control);
@@ -468,10 +492,11 @@ try {
468
492
  if (options.command === 'init') await init(options);
469
493
  else if (options.command === 'doctor') await doctor(options);
470
494
  else if (options.command === 'upgrade') await upgrade(options);
495
+ else if (options.command === 'import-bmad-story') await importBmadStory(options);
471
496
  else if (!options.command) await interactiveInit();
472
497
  else if (options.command === '--help' || options.command === '-h') usage(0);
473
498
  else throw new Error(`Unknown command: ${options.command}`);
474
499
  } catch (error) {
475
- process.stderr.write(`${error.message}\n`);
500
+ process.stderr.write(`${error.code ? `${error.code}: ` : ''}${error.message}\n`);
476
501
  usage(2);
477
502
  }
@@ -22,3 +22,10 @@ runtime-specific branches to Core installer or verification logic.
22
22
 
23
23
  Evaluator+ is outside the Core: it runs after a Reviewer-approved result, receives
24
24
  a deliberately limited fresh packet, and cannot reopen the completed run.
25
+
26
+ An optional immutable Quality Contract extends this existing control-plane
27
+ baseline without creating a second control plane. Its JSON manifest binds
28
+ approved intent sources and phase-scoped criteria to one canonical fingerprint.
29
+ `product_quality` criteria travel in the fresh Evaluator+ packet;
30
+ `delivery_closure` criteria are checked only during delivery closure. Projects
31
+ without the contract keep the legacy PRD baseline behavior.
@@ -0,0 +1,110 @@
1
+ # Optional BMAD Story integration
2
+
3
+ Triad+ can consume one already-produced BMAD Story and turn it into a normal
4
+ Triad feature Card. This is an integration boundary, not a BMAD execution
5
+ adapter: BMAD remains the planning authority and Triad remains responsible for
6
+ implementation, verification, review, and delivery.
7
+
8
+ ## Contract
9
+
10
+ The source is a read-only Markdown Story. It must contain:
11
+
12
+ - a unique Story `id` and `title` (frontmatter, metadata labels, or a Story
13
+ heading);
14
+ - `status: ready-for-dev` (frontmatter or a `Status` field);
15
+ - a target repository (or an explicit `--target-repository` importer option);
16
+ - an intent/outcome; and
17
+ - acceptance criteria.
18
+
19
+ The importer also carries through the Story's `Tasks & Acceptance`, Code Map,
20
+ Design Notes/constraints, verification expectations, and source references
21
+ when they are present. It does not interpret prose with an LLM, re-decompose a
22
+ Story, or invoke BMAD Build, Build Auto, or `bmad-loop`.
23
+
24
+ ## CLI
25
+
26
+ ```bash
27
+ npx triad-plus import-bmad-story \
28
+ --source /absolute/path/to/story.md \
29
+ --output /absolute/path/to/control/features/JFR-001.md \
30
+ --target-repository webup \
31
+ --required-gate cypress-jfr \
32
+ --depends-on JFR-000
33
+ ```
34
+
35
+ `--required-gate` and `--depends-on` may be repeated. They are explicit caller
36
+ options: gate IDs are additive to the repository's globally required gates,
37
+ and dependencies are never inferred from `stories.yaml` order. Omit both when
38
+ the Card should use the repository's normal/baseline behavior.
39
+
40
+ The command writes the Card and a sidecar provenance record (by default
41
+ `<card>.bmad-provenance.json`). The provenance records `source_kind:
42
+ bmad-story`, the resolved source path, source SHA-256, BMAD Story ID, target
43
+ repository, Card SHA-256, and the explicit options used for the import.
44
+
45
+ The same operation is available to Node consumers:
46
+
47
+ ```js
48
+ import { importBmadStory, writeImportedCard } from
49
+ 'triad-plus/integrations/bmad/story-importer.mjs';
50
+
51
+ const result = await importBmadStory({
52
+ sourcePath: '/absolute/path/to/story.md',
53
+ targetRepository: 'webup',
54
+ requiredGates: ['cypress-jfr']
55
+ });
56
+
57
+ await writeImportedCard({
58
+ sourcePath: '/absolute/path/to/story.md',
59
+ outputPath: '/absolute/path/to/control/features/JFR-001.md',
60
+ targetRepository: 'webup',
61
+ requiredGates: ['cypress-jfr']
62
+ });
63
+ ```
64
+
65
+ ## Example mapping
66
+
67
+ Input (abridged):
68
+
69
+ ```md
70
+ ---
71
+ id: JFR-001
72
+ title: Route the provider document
73
+ status: ready-for-dev
74
+ target_repository: webup
75
+ ---
76
+
77
+ # Story JFR-001: Route the provider document
78
+
79
+ ## Intent
80
+
81
+ Webup renders the provider-owned document at the existing boundary.
82
+
83
+ ## Acceptance Criteria
84
+
85
+ - Given a valid document, when the route is requested, then it renders.
86
+
87
+ ## Code Map
88
+
89
+ - `src/components/jfr/`
90
+ ```
91
+
92
+ The generated Card keeps that intent, acceptance criterion, Code Map, and the
93
+ target repository, then adds the normal Triad sections and the integration
94
+ boundary note. The caller-supplied `cypress-jfr` (if any) is recorded as an
95
+ additive required gate; it is not inferred from the word "render" or from a
96
+ file extension.
97
+
98
+ ## Fail-closed behavior
99
+
100
+ No executable Card is produced for a missing source, missing/non-`ready-for-dev`
101
+ status, malformed or ambiguous ID/title, missing indispensable Card fields,
102
+ conflicting target repository, invalid caller options, or a source that changes
103
+ while it is being read. The API exposes integration-level error codes such as
104
+ `bmad_story_not_ready`, `bmad_story_ambiguous`, `bmad_story_unmappable`, and
105
+ `bmad_story_source_mutated` so a planning gap can return upstream rather than
106
+ become a Developer decision.
107
+
108
+ After import, the generated Card goes through the existing assignment and
109
+ `triad-verify` path. No Core schema, lifecycle, Reviewer, retry, or gate
110
+ execution semantics are changed by this integration.
@@ -47,3 +47,22 @@ optional gate becomes required for that card, and an absent or empty list keeps
47
47
  the legacy gate behavior. Selected IDs are validated before Developer dispatch
48
48
  and are bound to the assignment; Triad does not attach visual or other
49
49
  domain-specific meaning to a gate ID.
50
+
51
+ ## Optional immutable Quality Contract
52
+
53
+ New or upgraded control workspaces may set:
54
+
55
+ ```yaml
56
+ project:
57
+ quality_contract:
58
+ baseline: artifacts/quality-baseline.json
59
+ fingerprint: <sha256 of the canonical manifest>
60
+ ```
61
+
62
+ The JSON manifest must contain at least one hashed, project-relative source and
63
+ may contain criteria scoped only to `product_quality` or `delivery_closure`.
64
+ The fingerprint is deterministic and independent of JSON whitespace or object
65
+ key order. A configured contract is validated before expensive verification
66
+ gates; malformed manifests and source drift fail closed. Omitting the section
67
+ preserves the 1.7 legacy PRD baseline behavior. A rebaseline is a new manifest
68
+ revision and explicit owner event, never an in-place edit.
@@ -12,3 +12,12 @@ attempt history unless the owner explicitly requires it. It reports `PASS`,
12
12
 
13
13
  An Evaluator+ `FAIL` does not reopen Triad, change the approved state, or start
14
14
  repair. An owner or a later Orchestrator may use it as input to a new run.
15
+
16
+ When a project declares an immutable Quality Contract, the packet also carries
17
+ the approved Quality Baseline fingerprint and exactly one result for each
18
+ `product_quality` criterion. `delivery_closure` criteria are deliberately
19
+ excluded and are evaluated by delivery closure. The control plane validates
20
+ fingerprint, candidate binding, criterion coverage, and the deterministic
21
+ aggregate (`FAIL` over `INDETERMINATE` over `PASS`) before recording the report.
22
+ Formatting a manifest or changing a bound source invalidates the contract; it
23
+ never starts a product retry.
@@ -51,6 +51,16 @@ selezionati possono essere saltati. Lista assente o vuota mantiene il
51
51
  comportamento legacy. Triad collega e registra gli ID senza attribuire loro un
52
52
  significato visuale o di altro dominio.
53
53
 
54
+ ## Target di qualità immutabile
55
+
56
+ Un progetto può optare per `project.quality_contract`, che collega al run un
57
+ fingerprint canonico del Quality Baseline JSON e verifica le source hashate prima
58
+ del dispatch e dei gate costosi. Drift o manifest invalido fermano il run come
59
+ contesto invalido senza consumare il budget di retry del prodotto. I criteri
60
+ `product_quality` vengono inviati al nuovo Evaluator+; quelli
61
+ `delivery_closure` sono verificati separatamente prima di `delivered`. Senza
62
+ questo opt-in i progetti legacy restano invariati.
63
+
54
64
  ## Retry e scope del candidato
55
65
 
56
66
  Gli attempt sono record storici di esecuzione. I nuovi workspace separano e
@@ -51,6 +51,16 @@ become required for that card, and unselected optional gates may be skipped. An
51
51
  absent or empty list preserves the legacy behavior. Triad binds and records the
52
52
  IDs but does not attach visual or other domain-specific meaning to them.
53
53
 
54
+ ## Immutable quality target
55
+
56
+ When a project opts in with `project.quality_contract`, the control plane binds a
57
+ canonical JSON Quality Baseline fingerprint and verifies its hashed sources before
58
+ dispatch or expensive gates. A source drift or invalid manifest stops the run as
59
+ context invalid, without consuming product retry budget. Product-quality criteria
60
+ are sent to the fresh Evaluator+ one at a time; delivery-closure criteria are
61
+ checked separately before `delivered`. The contract is optional, and legacy
62
+ projects without it are unchanged.
63
+
54
64
  ## Retry accounting and candidate scope
55
65
 
56
66
  Attempts are historical execution records. New workspaces separately bound
@@ -0,0 +1,119 @@
1
+ # Immutable Quality Contract
2
+
3
+ Triad+ 1.8 optionally binds a run to an owner-approved, machine-readable
4
+ Quality Baseline. It is an additional target record, not a second control
5
+ plane and not a replacement for the PRD, card baseline, or candidate
6
+ fingerprint.
7
+
8
+ ```text
9
+ Quality Baseline fingerprint = what the run was meant to satisfy
10
+ Repository/card baseline = where implementation started
11
+ Candidate fingerprint = what implementation produced
12
+ ```
13
+
14
+ ## Manifest
15
+
16
+ The manifest is JSON and is normally stored at
17
+ `artifacts/quality-baseline.json`:
18
+
19
+ ```json
20
+ {
21
+ "schema_version": 1,
22
+ "id": "my-project-quality-baseline",
23
+ "revision": 1,
24
+ "sources": [
25
+ {
26
+ "id": "prd",
27
+ "role": "intent",
28
+ "path": "artifacts/prd.md",
29
+ "sha256": "<64-hex-digest>"
30
+ }
31
+ ],
32
+ "criteria": [
33
+ { "id": "QB-001", "scope": "product_quality", "requirement": "..." },
34
+ { "id": "QB-010", "scope": "delivery_closure", "requirement": "..." }
35
+ ],
36
+ "fingerprint": "<sha256-of-canonical-manifest-without-fingerprint>"
37
+ }
38
+ ```
39
+
40
+ The manifest requires a positive revision, at least one source, unique source
41
+ and criterion IDs, project-relative source paths, SHA-256 for every source, and
42
+ non-empty requirements. Version 1 has exactly two criterion scopes:
43
+ `product_quality` and `delivery_closure`.
44
+
45
+ The fingerprint is SHA-256 over canonical JSON with object keys sorted
46
+ recursively, array order preserved, and the `fingerprint` field excluded from
47
+ the payload. Whitespace and object formatting therefore do not change it.
48
+
49
+ ## Binding and drift
50
+
51
+ When `project.quality_contract` is present, the Orchestrator binds both the
52
+ manifest path and its fingerprint in every active Developer assignment:
53
+
54
+ ```json
55
+ {
56
+ "quality_baseline_path": "artifacts/quality-baseline.json",
57
+ "expected_quality_baseline_fingerprint": "<sha256>"
58
+ }
59
+ ```
60
+
61
+ `triad-verify` validates the manifest and all source hashes before expensive
62
+ gates. A malformed manifest or fingerprint mismatch is
63
+ `quality_baseline_invalid`; a valid manifest whose declared sources or
64
+ assignment fingerprint no longer match is `quality_baseline_drift`. Both are
65
+ `invalid_context`: no Developer dispatch, expensive gates, or retry budget.
66
+
67
+ Projects without `quality_contract` retain the legacy PRD-only path. A
68
+ rebaseline is never an in-place edit: create a new revision/fingerprint and
69
+ record an explicit lineage event. Historical evidence is not silently
70
+ reinterpreted.
71
+
72
+ ## Phase ownership
73
+
74
+ `product_quality` criteria are included in the fresh, blind Evaluator+ packet.
75
+ The packet carries the baseline fingerprint, final candidate fingerprint,
76
+ criteria, approved source material, and bounded verifier evidence. It never
77
+ includes delivery criteria, queue state, handoff state, or attempt history.
78
+
79
+ Evaluator+ returns one result per product criterion. The control plane validates
80
+ coverage, uniqueness, scope, candidate/baseline fingerprints, and the overall
81
+ verdict. Aggregation is deterministic:
82
+
83
+ ```text
84
+ any FAIL -> FAIL
85
+ else any INDETERMINATE -> INDETERMINATE
86
+ else -> PASS
87
+ ```
88
+
89
+ `delivery_closure` criteria are evaluated separately during delivery closure
90
+ and recorded with criterion ID, verdict, and evidence references. A run is not
91
+ `delivered` when any configured delivery criterion is `FAIL` or
92
+ `INDETERMINATE`. Quality Bar evaluation is not a required-gate replacement,
93
+ and an Evaluator+ failure never repairs or reopens Triad automatically.
94
+
95
+ ## Explicit control-plane validation
96
+
97
+ The installed runtime exposes deterministic commands for the two phase
98
+ boundaries. Run the baseline preflight before dispatch, then run the phase
99
+ validators from the control workspace so the baseline is reloaded from disk:
100
+
101
+ ```bash
102
+ node .triad-runtime/triad-evaluator-validate.mjs --mode baseline \
103
+ --project /absolute/path/to/control \
104
+ --baseline artifacts/quality-baseline.json
105
+
106
+ node .triad-runtime/triad-evaluator-validate.mjs --mode evaluator \
107
+ --project /absolute/path/to/control \
108
+ --baseline artifacts/quality-baseline.json \
109
+ --result artifacts/evaluator-plus/evaluation.json \
110
+ --expected-candidate-fingerprint <final-candidate-fingerprint>
111
+
112
+ node .triad-runtime/triad-evaluator-validate.mjs --mode delivery \
113
+ --project /absolute/path/to/control \
114
+ --baseline artifacts/quality-baseline.json \
115
+ --result artifacts/delivery-closure.json
116
+ ```
117
+
118
+ Each command emits one machine-readable JSON result and exits non-zero for an
119
+ invalid contract, source drift, stale candidate binding, or invalid result.
@@ -17,6 +17,23 @@ An agent-reported claim is not the same as verification evidence. A Developer ca
17
17
  report the commands it ran; `triad-verify` independently observes declared
18
18
  required `control-plane` gates and writes atomic evidence.
19
19
 
20
+ ## Immutable Quality Contract
21
+
22
+ Projects may opt into `project.quality_contract` with a project-relative JSON
23
+ manifest and its expected SHA-256 fingerprint. The shared
24
+ `runtime/lib/quality-baseline.mjs` loader canonicalizes the manifest (excluding
25
+ its self-declared `fingerprint`), validates source paths, IDs, scopes, and hashes,
26
+ then verifies every bound source before any expensive gate runs. A malformed,
27
+ missing, or mismatched contract is `invalid_context`; a valid manifest whose
28
+ bound source content has changed is `quality_baseline_drift`. Both fail closed:
29
+ no Developer dispatch, gate execution, or retry budget consumption is allowed.
30
+
31
+ The verifier records `baseline.quality_baseline_fingerprint` when configured and
32
+ records `null` for legacy projects. The Quality Contract is distinct from the
33
+ repository/card baseline and the candidate fingerprint: it says what the run is
34
+ trying to satisfy, not which Git commit was checked out or what the candidate
35
+ changed.
36
+
20
37
  Before gates run, the verifier validates the active assignment, PRD/card/gate
21
38
  hashes, worktree, expected branch, and candidate fingerprint. It records the
22
39
  assignment ID/hash and run ID, executes deterministic commands with a bounded
@@ -0,0 +1,12 @@
1
+ # BMAD Story importer
2
+
3
+ This optional integration converts one BMAD Markdown Story with
4
+ `status: ready-for-dev` into a normal Triad feature Card. It is intentionally
5
+ small and deterministic: the source is read-only, caller options are explicit,
6
+ and BMAD workflows are never invoked.
7
+
8
+ See [the public BMAD integration guide](../../docs/bmad-integration.md) for the
9
+ mapping contract, CLI/API examples, provenance sidecar, and fail-closed rules.
10
+
11
+ The implementation is in `story-importer.mjs`. It does not add BMAD-specific
12
+ branches to the Triad Core or infer gates/dependencies from planning order.