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.
- package/CHANGELOG.md +15 -0
- package/README.md +26 -0
- package/adapters/antigravity/.agents/agents/triad-evaluator/agent.md +5 -0
- package/adapters/claude-code/.claude/agents/triad-evaluator.md +5 -0
- package/adapters/copilot/.github/agents/triad-evaluator.agent.md +5 -0
- package/adapters/hermes/skills/triad/SKILL.md +5 -0
- package/adapters/opencode/.opencode/agents/triad-evaluator.md +6 -0
- package/bin/triad-plus.js +28 -3
- package/docs/architecture.md +7 -0
- package/docs/bmad-integration.md +110 -0
- package/docs/configuration.md +19 -0
- package/docs/evaluator-plus.md +9 -0
- package/docs/operating-guide.it.md +10 -0
- package/docs/operating-guide.md +10 -0
- package/docs/quality-contract.md +119 -0
- package/docs/verification.md +17 -0
- package/integrations/bmad/README.md +12 -0
- package/integrations/bmad/story-importer.mjs +475 -0
- package/package.json +2 -2
- package/runtime/lib/quality-baseline.mjs +205 -0
- package/runtime/triad-evaluator-validate.mjs +261 -0
- package/runtime/triad-verify.mjs +19 -5
- package/schemas/evaluator-plus-result.schema.json +16 -0
- package/schemas/quality-baseline.schema.json +42 -0
- package/schemas/verification-evidence.schema.json +1 -1
- package/skills/triad-loop-bootstrap/SKILL.md +8 -0
- package/skills/triad-loop-bootstrap/assets/loop-template/handoff-report.template.md +13 -0
- package/skills/triad-loop-bootstrap/assets/loop-template/quality-baseline.json +15 -0
- package/skills/triad-loop-bootstrap/assets/loop-template/run-state.yaml +7 -0
- package/skills/triad-loop-bootstrap/assets/loop-template/runtime/assignments/assignment.template.json +2 -0
- package/skills/triad-loop-bootstrap/assets/project.yaml +5 -0
- package/skills/triad-loop-evaluator/SKILL.md +12 -2
- 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 (
|
|
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
|
}
|
package/docs/architecture.md
CHANGED
|
@@ -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.
|
package/docs/configuration.md
CHANGED
|
@@ -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.
|
package/docs/evaluator-plus.md
CHANGED
|
@@ -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
|
package/docs/operating-guide.md
CHANGED
|
@@ -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.
|
package/docs/verification.md
CHANGED
|
@@ -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.
|