triad-plus 1.8.0 → 1.10.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 +36 -0
- package/README.md +21 -12
- package/adapters/antigravity/.agents/agents/triad-developer/agent.md +7 -3
- package/adapters/antigravity/.agents/agents/triad-orchestrator/agent.md +5 -0
- package/adapters/antigravity/.agents/agents/triad-reviewer/agent.md +7 -2
- package/adapters/antigravity/.agents/skills/triad/SKILL.md +22 -0
- package/adapters/antigravity/README.md +1 -1
- package/adapters/antigravity/install.sh +1 -1
- package/adapters/claude-code/.claude/agents/triad-developer.md +9 -4
- package/adapters/claude-code/.claude/agents/triad-reviewer.md +10 -5
- package/adapters/claude-code/.claude/commands/triad.md +22 -0
- package/adapters/claude-code/README.md +12 -4
- package/adapters/claude-code/install.sh +1 -1
- package/adapters/codex/README.md +1 -1
- package/adapters/codex/install.sh +3 -3
- package/adapters/codex/prompts/triad.md +28 -0
- package/adapters/copilot/.github/agents/triad-developer.agent.md +12 -6
- package/adapters/copilot/.github/agents/triad-evaluator.agent.md +2 -1
- package/adapters/copilot/.github/agents/triad-orchestrator.agent.md +15 -1
- package/adapters/copilot/.github/agents/triad-reviewer.agent.md +11 -5
- package/adapters/copilot/.github/skills/triad/SKILL.md +23 -0
- package/adapters/hermes/install.sh +1 -1
- package/adapters/hermes/skills/triad/SKILL.md +18 -0
- package/adapters/opencode/.opencode/agents/triad-developer.md +23 -4
- package/adapters/opencode/.opencode/agents/triad-orchestrator.md +16 -0
- package/adapters/opencode/.opencode/agents/triad-reviewer.md +25 -3
- package/adapters/opencode/.opencode/commands/triad.md +21 -0
- package/adapters/opencode/README.md +29 -5
- package/adapters/opencode/install.sh +1 -1
- package/adapters/registry.mjs +23 -1
- package/bin/triad-plus.js +210 -38
- package/docs/architecture.md +8 -0
- package/docs/assignment-packets.md +35 -0
- package/docs/bmad-integration.md +187 -82
- package/docs/configuration.md +29 -0
- package/docs/npx-installation.md +20 -0
- package/docs/opencode-replication.md +8 -0
- package/docs/operating-guide.it.md +8 -0
- package/docs/operating-guide.md +7 -0
- package/docs/runtimes.md +1 -1
- package/docs/verification.md +20 -0
- package/integrations/bmad/README.md +17 -9
- package/integrations/bmad/epics-parser.mjs +697 -0
- package/integrations/bmad/story-importer.mjs +143 -5
- package/package.json +2 -2
- package/runtime/lib/assignment-packet.mjs +553 -0
- package/runtime/lib/model-config.mjs +165 -0
- package/runtime/lib/repository-context.mjs +192 -0
- package/runtime/lib/terminal.mjs +86 -0
- package/runtime/triad-assignment-packet.mjs +62 -0
- package/runtime/triad-bmad-intake.mjs +150 -0
- package/runtime/triad-runtime-context.mjs +88 -0
- package/runtime/triad-verify.mjs +41 -21
- package/schemas/verification-evidence.schema.json +8 -0
- package/skills/triad-loop-bootstrap/SKILL.md +39 -4
- package/skills/triad-loop-bootstrap/assets/loop-template/runtime/assignments/assignment.template.json +14 -1
- package/skills/triad-loop-developer/SKILL.md +33 -8
- package/skills/triad-loop-orchestrator/SKILL.md +96 -11
- package/skills/triad-loop-reviewer/SKILL.md +31 -5
- package/skills/triad-model-configuration/SKILL.md +98 -0
- package/skills/triad-model-configuration/agents/openai.yaml +4 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { resolveAssignmentContext, validateAssignmentPacket } from "./lib/assignment-packet.mjs";
|
|
7
|
+
import { inspectRepositoryContext } from "./lib/repository-context.mjs";
|
|
8
|
+
|
|
9
|
+
function option(argv, name) {
|
|
10
|
+
const index = argv.indexOf(name);
|
|
11
|
+
return index >= 0 ? argv[index + 1] : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function usage() {
|
|
15
|
+
process.stderr.write("Usage: node .triad-runtime/triad-runtime-context.mjs --project <control-workspace> --assignment <assignment.json>\n");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function issue(code, message) {
|
|
19
|
+
return { code, message };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
const argv = process.argv.slice(2);
|
|
24
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
25
|
+
usage();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const projectRoot = path.resolve(option(argv, "--project") ?? process.cwd());
|
|
29
|
+
const assignmentOption = option(argv, "--assignment");
|
|
30
|
+
if (!assignmentOption) throw Object.assign(new Error("--assignment is required"), { code: "runtime_context_invalid" });
|
|
31
|
+
const assignmentPath = path.resolve(projectRoot, assignmentOption);
|
|
32
|
+
const assignment = JSON.parse(await readFile(assignmentPath, "utf8"));
|
|
33
|
+
const output = {
|
|
34
|
+
status: "blocked",
|
|
35
|
+
assignment: {
|
|
36
|
+
id: assignment.assignment_id ?? null,
|
|
37
|
+
feature_id: assignment.feature_id ?? null,
|
|
38
|
+
attempt: assignment.attempt ?? null,
|
|
39
|
+
worktree: assignment.worktree ?? null,
|
|
40
|
+
packet_metadata_cwd: null
|
|
41
|
+
},
|
|
42
|
+
repository_mapping: null,
|
|
43
|
+
expected: null,
|
|
44
|
+
actual: null,
|
|
45
|
+
repository_skills: [],
|
|
46
|
+
issues: []
|
|
47
|
+
};
|
|
48
|
+
try {
|
|
49
|
+
const context = await resolveAssignmentContext(assignment, { projectRoot });
|
|
50
|
+
output.assignment.worktree = context.worktree;
|
|
51
|
+
output.repository_mapping = context.repositoryMapping;
|
|
52
|
+
output.expected = {
|
|
53
|
+
worktree: context.worktree,
|
|
54
|
+
git_top_level: context.repositoryContext.assigned_git_top_level,
|
|
55
|
+
repository: context.repository
|
|
56
|
+
};
|
|
57
|
+
if (assignment.assignment_packet_path || assignment.assignment_packet_sha256) {
|
|
58
|
+
const packet = await validateAssignmentPacket(assignment, projectRoot);
|
|
59
|
+
output.assignment.packet_metadata_cwd = packet.metadata.cwd ?? null;
|
|
60
|
+
}
|
|
61
|
+
const inspected = await inspectRepositoryContext({
|
|
62
|
+
worktree: context.worktree,
|
|
63
|
+
requiredSkills: context.requiredSkills,
|
|
64
|
+
actualCwd: process.cwd(),
|
|
65
|
+
checkActualCwd: true
|
|
66
|
+
});
|
|
67
|
+
output.actual = inspected.actual;
|
|
68
|
+
output.repository_skills = inspected.skills;
|
|
69
|
+
output.issues = inspected.issues;
|
|
70
|
+
output.status = inspected.status;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error.repositoryContext) {
|
|
73
|
+
output.actual = error.repositoryContext.actual ?? null;
|
|
74
|
+
output.repository_skills = error.repositoryContext.skills ?? [];
|
|
75
|
+
output.issues = error.repositoryContext.issues ?? [];
|
|
76
|
+
}
|
|
77
|
+
output.issues.push(issue(error.code ?? "runtime_context_invalid", error.message));
|
|
78
|
+
}
|
|
79
|
+
process.stdout.write(`${JSON.stringify(output)}\n`);
|
|
80
|
+
if (output.status !== "pass") process.exitCode = 2;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
84
|
+
main().catch((error) => {
|
|
85
|
+
process.stderr.write(`${error.code ? `${error.code}: ` : ""}${error.message}\n`);
|
|
86
|
+
process.exitCode = 2;
|
|
87
|
+
});
|
|
88
|
+
}
|
package/runtime/triad-verify.mjs
CHANGED
|
@@ -8,6 +8,8 @@ import { calculateCandidateFingerprint, collectCandidateChanges, worktreeBranch
|
|
|
8
8
|
import { executeGates, gateSelectionEvidence, loadTrustedGates, resolveGateSelection } from "./lib/gates.mjs";
|
|
9
9
|
import { resolveQualityContract } from "./lib/quality-baseline.mjs";
|
|
10
10
|
import { evaluateScopeContract, parseScopeContract } from "./lib/scope-contract.mjs";
|
|
11
|
+
import { resolveAssignmentContext, validateAssignmentPacket } from "./lib/assignment-packet.mjs";
|
|
12
|
+
import { inspectRepositoryContext } from "./lib/repository-context.mjs";
|
|
11
13
|
|
|
12
14
|
const argv = process.argv.slice(2);
|
|
13
15
|
const option = (name) => {
|
|
@@ -34,23 +36,27 @@ async function sha256File(value) {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
async function validateRepositorySkills(required, worktree) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
const declared = required !== undefined;
|
|
40
|
+
const requiredSkills = declared ? required : [];
|
|
41
|
+
const context = await inspectRepositoryContext({ worktree, requiredSkills });
|
|
42
|
+
if (!declared) return { declared: false, skills: [], context };
|
|
43
|
+
if (!Array.isArray(required) || required.length === 0) {
|
|
44
|
+
const error = new Error("repository skill binding must declare at least one skill");
|
|
45
|
+
error.code = "repository_context_invalid";
|
|
46
|
+
error.repositoryContext = context;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
if (context.status !== "pass") {
|
|
50
|
+
const error = new Error(context.issues.map((item) => item.message).join("; ") || "repository skill binding is invalid");
|
|
51
|
+
error.code = "repository_context_invalid";
|
|
52
|
+
error.repositoryContext = context;
|
|
53
|
+
throw error;
|
|
52
54
|
}
|
|
53
|
-
return {
|
|
55
|
+
return {
|
|
56
|
+
declared: true,
|
|
57
|
+
skills: context.skills.map(({ path: relative, sha256 }) => ({ path: relative, sha256 })),
|
|
58
|
+
context
|
|
59
|
+
};
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
function triggerFrom(payload) {
|
|
@@ -70,7 +76,7 @@ async function resolveAssignment(projectRoot, trigger, explicitAssignment) {
|
|
|
70
76
|
return { assignmentPath, assignment: JSON.parse(source), assignmentHash: sha256(source) };
|
|
71
77
|
}
|
|
72
78
|
|
|
73
|
-
async function buildInvalidEvidence({ runId, trigger, assignment, reason, outputPath, failureCode = "verification_context_invalid", gateSelection = null, qualityBaselineFingerprint = null }) {
|
|
79
|
+
async function buildInvalidEvidence({ runId, trigger, assignment, reason, outputPath, failureCode = "verification_context_invalid", gateSelection = null, qualityBaselineFingerprint = null, assignmentPacket = null, repositorySkills = null }) {
|
|
74
80
|
const evidence = {
|
|
75
81
|
schema_version: 1,
|
|
76
82
|
run_id: runId,
|
|
@@ -86,6 +92,10 @@ async function buildInvalidEvidence({ runId, trigger, assignment, reason, output
|
|
|
86
92
|
git_head: null,
|
|
87
93
|
candidate_fingerprint: null,
|
|
88
94
|
},
|
|
95
|
+
assignment_packet: assignmentPacket ?? (assignment?.assignment_packet_path || assignment?.assignment_packet_sha256
|
|
96
|
+
? { path: assignment.assignment_packet_path ?? null, sha256: assignment.assignment_packet_sha256 ?? null }
|
|
97
|
+
: null),
|
|
98
|
+
...(repositorySkills ? { repository_skills: repositorySkills } : {}),
|
|
89
99
|
gates: [],
|
|
90
100
|
required_gates_passed: false,
|
|
91
101
|
status: "invalid_context",
|
|
@@ -154,6 +164,8 @@ async function main() {
|
|
|
154
164
|
let outputPath;
|
|
155
165
|
let gateSelection = null;
|
|
156
166
|
let qualityBaseline = null;
|
|
167
|
+
let assignmentPacket = null;
|
|
168
|
+
let repositorySkills = null;
|
|
157
169
|
try {
|
|
158
170
|
let assignmentHash;
|
|
159
171
|
({ assignmentPath, assignment, assignmentHash } = await resolveAssignment(projectRoot, trigger, option("--assignment")));
|
|
@@ -167,19 +179,23 @@ async function main() {
|
|
|
167
179
|
if (!assignment.assignment_id) throw new Error("assignment ID is required");
|
|
168
180
|
if (assignment.agent_id !== trigger.agent_id || assignment.agent_type !== "triad_developer") throw new Error("developer assignment does not match trigger");
|
|
169
181
|
if ((await realpath(path.resolve(assignment.project_root ?? projectRoot))) !== projectRoot) throw new Error("assignment project root mismatch");
|
|
170
|
-
const
|
|
171
|
-
|
|
182
|
+
const assignmentContext = await resolveAssignmentContext(assignment, { projectRoot });
|
|
183
|
+
const worktree = assignmentContext.worktree;
|
|
172
184
|
const prdPath = path.resolve(projectRoot, assignment.prd_path ?? "artifacts/prd.md");
|
|
173
185
|
const cardPath = path.resolve(projectRoot, assignment.card_path);
|
|
174
186
|
await access(prdPath);
|
|
175
187
|
await access(cardPath);
|
|
188
|
+
// The packet is an immutable, shared assignment contract. Validate its
|
|
189
|
+
// binding before skills, scope, or expensive gates so a stale packet is
|
|
190
|
+
// reported as invalid context and consumes no retry budget.
|
|
191
|
+
assignmentPacket = await validateAssignmentPacket(assignment, projectRoot);
|
|
176
192
|
// Resolve the optional immutable Quality Contract before any baseline or
|
|
177
193
|
// expensive-gate check so a bound source drift keeps its precise failure
|
|
178
194
|
// classification (including when the source is the PRD itself).
|
|
179
195
|
qualityBaseline = await resolveQualityContract(assignment, projectRoot);
|
|
180
196
|
if ((await sha256File(prdPath)) !== assignment.expected_prd_sha256) throw new Error("PRD baseline hash mismatch");
|
|
181
197
|
if ((await sha256File(cardPath)) !== assignment.expected_card_sha256) throw new Error("feature card hash mismatch");
|
|
182
|
-
|
|
198
|
+
repositorySkills = await validateRepositorySkills(assignment.required_repository_skills, worktree);
|
|
183
199
|
const before = await calculateCandidateFingerprint(worktree);
|
|
184
200
|
const branch = await worktreeBranch(worktree);
|
|
185
201
|
if (assignment.expected_branch && assignment.expected_branch !== branch) throw new Error("worktree branch does not match assignment");
|
|
@@ -220,6 +236,7 @@ async function main() {
|
|
|
220
236
|
candidate_fingerprint: before.value,
|
|
221
237
|
branch,
|
|
222
238
|
},
|
|
239
|
+
assignment_packet: assignmentPacket,
|
|
223
240
|
repository_skills: repositorySkills,
|
|
224
241
|
scope,
|
|
225
242
|
gates: [],
|
|
@@ -257,6 +274,7 @@ async function main() {
|
|
|
257
274
|
candidate_fingerprint: before.value,
|
|
258
275
|
branch,
|
|
259
276
|
},
|
|
277
|
+
assignment_packet: assignmentPacket,
|
|
260
278
|
repository_skills: repositorySkills,
|
|
261
279
|
scope,
|
|
262
280
|
gate_selection: gateSelectionEvidence(gateSelection),
|
|
@@ -283,7 +301,9 @@ async function main() {
|
|
|
283
301
|
outputPath,
|
|
284
302
|
failureCode: error.code ?? "verification_context_invalid",
|
|
285
303
|
gateSelection,
|
|
286
|
-
qualityBaselineFingerprint: qualityBaseline?.fingerprint ?? null
|
|
304
|
+
qualityBaselineFingerprint: qualityBaseline?.fingerprint ?? null,
|
|
305
|
+
assignmentPacket,
|
|
306
|
+
repositorySkills: repositorySkills ?? error.repositoryContext ?? null,
|
|
287
307
|
});
|
|
288
308
|
process.stdout.write(`${JSON.stringify({ run_id: runId, status: evidence.status, evidence: outputPath ?? null })}\n`);
|
|
289
309
|
process.exitCode = 3;
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
"assignment_sha256": { "type": ["string", "null"] },
|
|
14
14
|
"trigger": { "type": "object", "required": ["event", "agent_id", "agent_type"], "properties": { "event": { "type": "string" }, "agent_id": { "type": ["string", "null"] }, "agent_type": { "type": ["string", "null"] } } },
|
|
15
15
|
"baseline": { "type": "object", "required": ["prd_sha256", "card_sha256", "git_head", "candidate_fingerprint"], "properties": { "prd_sha256": { "type": ["string", "null"] }, "card_sha256": { "type": ["string", "null"] }, "quality_baseline_fingerprint": { "type": ["string", "null"], "pattern": "^[a-fA-F0-9]{64}$" }, "git_head": { "type": ["string", "null"] }, "candidate_fingerprint": { "type": ["string", "null"] }, "branch": { "type": ["string", "null"] } } },
|
|
16
|
+
"assignment_packet": {
|
|
17
|
+
"type": ["object", "null"],
|
|
18
|
+
"required": ["path", "sha256"],
|
|
19
|
+
"properties": {
|
|
20
|
+
"path": { "type": ["string", "null"] },
|
|
21
|
+
"sha256": { "type": ["string", "null"], "pattern": "^[a-fA-F0-9]{64}$" }
|
|
22
|
+
}
|
|
23
|
+
},
|
|
16
24
|
"scope": {
|
|
17
25
|
"type": "object",
|
|
18
26
|
"properties": {
|
|
@@ -13,13 +13,28 @@ Collect only missing essentials: project ID/title, readable PRD source, target
|
|
|
13
13
|
repositories with base/project branches and worktrees, measurable success
|
|
14
14
|
conditions, runnable quality gates, practical-test need, and integration need.
|
|
15
15
|
|
|
16
|
+
## Native BMAD planning boundary
|
|
17
|
+
|
|
18
|
+
When the owner input is a native BMAD `epics.md` artifact, BMAD is the
|
|
19
|
+
planning authority for Epic/Story boundaries and acceptance criteria. The
|
|
20
|
+
canonical Stories in that read-only artifact are the sole planning boundaries:
|
|
21
|
+
one canonical Story maps to one normal Triad Card. Bootstrap must not independently
|
|
22
|
+
re-decompose the PRD into another feature-card plan, create a second Story set,
|
|
23
|
+
or invoke an LLM to decide native Story boundaries. The native flow is
|
|
24
|
+
BMAD `epics.md` → canonical Stories → Triad Cards; the generic PRD-only
|
|
25
|
+
feature-plan step below does not apply to that input. Triad remains execution
|
|
26
|
+
authority after the planning handoff.
|
|
27
|
+
|
|
16
28
|
1. Create `<control-repository>/projects/<project-id>/`.
|
|
17
29
|
2. Copy `assets/project.yaml` to `project.yaml`; register only declared product
|
|
18
30
|
repositories, branches, and worktrees.
|
|
19
31
|
3. Copy the approved PRD to `artifacts/prd.md`; record source, collection time,
|
|
20
32
|
snapshot, revision when available, and SHA-256.
|
|
21
|
-
4. Copy `assets/loop-template/` to `.loop
|
|
22
|
-
under `features/` and a complete
|
|
33
|
+
4. Copy `assets/loop-template/` to `.loop/`. For ordinary PRD-only input,
|
|
34
|
+
create bounded feature cards under `features/` and a complete
|
|
35
|
+
`feature-plan.md`. For native BMAD input, ingest the canonical Stories and
|
|
36
|
+
materialize one normal Card per Story instead; do not perform a second
|
|
37
|
+
semantic decomposition.
|
|
23
38
|
When an owner-approved Quality Bar or additional intent/architecture source
|
|
24
39
|
exists, copy `assets/loop-template/quality-baseline.json`, fill only the
|
|
25
40
|
declared source hashes and criteria, compute its canonical fingerprint, and
|
|
@@ -47,10 +62,30 @@ conditions, runnable quality gates, practical-test need, and integration need.
|
|
|
47
62
|
The normal state route is `draft → ready → in_progress → verifying → in_review
|
|
48
63
|
→ approved|rework|blocked`. The Orchestrator owns those records. Before each
|
|
49
64
|
Developer dispatch, write an active assignment with a unique assignment ID,
|
|
50
|
-
feature, attempt, expected branch, worktree, PRD/card/gate
|
|
51
|
-
run ID.
|
|
65
|
+
feature, repository ID, attempt, expected branch, worktree, PRD/card/gate
|
|
66
|
+
hashes, and verifier run ID. Populate only the packet's bounded `context`
|
|
67
|
+
fields (relevant excerpts, acceptance, verification mapping, touch points,
|
|
68
|
+
constraints, risks, and prior evidence). Then create its immutable Assignment Packet and bind the returned path
|
|
69
|
+
and SHA-256 in that assignment:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
node .triad-runtime/triad-assignment-packet.mjs \
|
|
73
|
+
--project /absolute/path/to/control-workspace \
|
|
74
|
+
--assignment .loop/runtime/assignments/<assignment-file>.json
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The command returns JSON containing the packet path and the dispatch context;
|
|
78
|
+
launch the delegated role with `dispatch.cwd` set to the declared product
|
|
79
|
+
worktree. The verifier produces environment-derived evidence; it never changes
|
|
52
80
|
card state.
|
|
53
81
|
|
|
82
|
+
The control workspace and product worktree may be separate directories. An
|
|
83
|
+
external worktree is authorized by an exact repository mapping in
|
|
84
|
+
`project.yaml` (`repositories[].worktree`, falling back to `path`), not by an
|
|
85
|
+
assignment escape hatch. A mapped external worktree is valid even when the
|
|
86
|
+
legacy `allow_external_worktree` field is absent or false; an unmapped external
|
|
87
|
+
path fails closed before dispatch or gate execution.
|
|
88
|
+
|
|
54
89
|
Use local worktrees for enabled multi-repository integration and declare a final
|
|
55
90
|
integration card. Do not publish packages, alter registry versions, or create a
|
|
56
91
|
release as a bootstrap/integration step. Re-baseline only after an explicit owner
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"feature_id": "REPLACE_ME_FEATURE_ID",
|
|
8
8
|
"attempt": 1,
|
|
9
9
|
"project_root": "REPLACE_ME_ABSOLUTE_CONTROL_WORKSPACE",
|
|
10
|
+
"repository_id": "REPLACE_ME_REPOSITORY_ID",
|
|
10
11
|
"worktree": "REPLACE_ME_DECLARED_WORKTREE_OR_ABSOLUTE_PATH",
|
|
11
12
|
"expected_branch": "REPLACE_ME_PROJECT_BRANCH",
|
|
12
13
|
"allow_external_worktree": false,
|
|
@@ -31,5 +32,17 @@
|
|
|
31
32
|
}
|
|
32
33
|
],
|
|
33
34
|
"verification_run_id": "REPLACE_ME_UUID",
|
|
34
|
-
"evidence_directory": ".loop/evidence/REPLACE_ME_FEATURE_ID/attempt-001"
|
|
35
|
+
"evidence_directory": ".loop/evidence/REPLACE_ME_FEATURE_ID/attempt-001",
|
|
36
|
+
"assignment_packet_path": null,
|
|
37
|
+
"assignment_packet_sha256": null,
|
|
38
|
+
"context": {
|
|
39
|
+
"relevant_prd_excerpts": [],
|
|
40
|
+
"relevant_adr_excerpts": [],
|
|
41
|
+
"acceptance_criteria": [],
|
|
42
|
+
"verification_mapping": [],
|
|
43
|
+
"expected_paths": [],
|
|
44
|
+
"constraints": [],
|
|
45
|
+
"risks": [],
|
|
46
|
+
"previous_evidence": []
|
|
47
|
+
}
|
|
35
48
|
}
|
|
@@ -5,14 +5,25 @@ description: Implement one bounded Triad feature card, add focused tests, and re
|
|
|
5
5
|
|
|
6
6
|
# Triad Developer
|
|
7
7
|
|
|
8
|
-
Change the artifact to satisfy the assigned goal.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
Change the artifact to satisfy the assigned goal. The host MUST launch this
|
|
9
|
+
activation with `cwd`/`workdir` equal to the assignment's declared product
|
|
10
|
+
worktree, not the control workspace. Verify that operational directory and
|
|
11
|
+
branch before editing. Read the immutable Assignment Packet first, then the
|
|
12
|
+
card, the mandatory repository skills, project instructions, and prior evidence
|
|
13
|
+
references named by the assignment. The packet is the primary contract and
|
|
14
|
+
eliminates broad startup discovery; read only the needed PRD/ADR section when a
|
|
15
|
+
packet detail is missing or contradictory, never the full documents by default.
|
|
16
|
+
When the assignment contains a scope contract, read it as a deterministic path
|
|
17
|
+
boundary in addition to the human card scope: keep every cumulative change from
|
|
18
|
+
the card baseline within its allowed paths, and use the recorded offending paths
|
|
19
|
+
for `scope_cleanup`. Do not change scope, policy, assignments, queue, state, or
|
|
20
|
+
evidence records.
|
|
21
|
+
|
|
22
|
+
The Orchestrator supplies explicit control-workspace, repository, worktree,
|
|
23
|
+
branch, card, packet, and mandatory-skill paths. Use native `read`, `grep`,
|
|
24
|
+
`glob`, and `list` primitives for simple reads/discovery when the host provides
|
|
25
|
+
them. Use shell/Bash for builds, tests, git, scripts, and system commands; do
|
|
26
|
+
not use shell as the default filesystem API.
|
|
16
27
|
|
|
17
28
|
At the beginning of every activation, read `.triad-plus/team.json` when it
|
|
18
29
|
exists. Your first report to the Orchestrator must identify you as its configured
|
|
@@ -26,6 +37,20 @@ SHA-256 values in the report as a **repository-skill attestation**. If a bound
|
|
|
26
37
|
skill is missing or its hash differs, stop and report the mismatch; do not
|
|
27
38
|
replace it with external context or a similarly named skill.
|
|
28
39
|
|
|
40
|
+
Before editing, prove the runtime context from the actual activation cwd with
|
|
41
|
+
the installed control-plane diagnostic:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
node /absolute/path/to/control-workspace/.triad-runtime/triad-runtime-context.mjs \
|
|
45
|
+
--project /absolute/path/to/control-workspace \
|
|
46
|
+
--assignment /absolute/path/to/control-workspace/.loop/runtime/assignments/<assignment-file>.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Record its `process_cwd`, `shell_pwd`, `git_top_level`, assigned worktree, and
|
|
50
|
+
resolved repository-skill paths/hashes. A non-zero result, a cwd/root mismatch,
|
|
51
|
+
or a missing/mutated skill is an invalid runtime context; stop before editing
|
|
52
|
+
and do not substitute a same-named control-workspace or global skill.
|
|
53
|
+
|
|
29
54
|
Implement the smallest complete change and focused tests. Run useful local
|
|
30
55
|
checks and measure declared criteria. Report changed files, tests, exact command
|
|
31
56
|
results, metrics, worktree/branch, risks, and blockers to the Orchestrator.
|
|
@@ -8,6 +8,10 @@ description: Coordinate a Triad engineering run from declared feature cards thro
|
|
|
8
8
|
Maintain the goal and operational context. Decide the next step; do not perform
|
|
9
9
|
ordinary implementation or review. Read `project.yaml`, the frozen PRD, queue,
|
|
10
10
|
decision policy, current records, and `.triad-plus/team.json` when present.
|
|
11
|
+
The control workspace is the policy/state/evidence source; a delegated
|
|
12
|
+
Developer or Reviewer operates from the assigned product worktree. Prepare one
|
|
13
|
+
immutable Assignment Packet per assignment so role contexts do not reconstruct
|
|
14
|
+
the entire PRD/ADR from scratch.
|
|
11
15
|
|
|
12
16
|
Before the first owner-facing reply, read `.triad-plus/team.json` when it exists.
|
|
13
17
|
User-facing identity is permanent: adopt its non-empty
|
|
@@ -26,6 +30,50 @@ localized to the configured interaction language; then state whether the run is
|
|
|
26
30
|
new or resumed and what input was received. If the entry point already made that
|
|
27
31
|
presentation for this invocation, do not repeat it.
|
|
28
32
|
|
|
33
|
+
## Native BMAD intake
|
|
34
|
+
|
|
35
|
+
When the owner input is an `epics.md` file, or a directory that deterministically
|
|
36
|
+
contains `_bmad-output/planning-artifacts/epics.md`, use the native BMAD intake
|
|
37
|
+
before selecting a card. Do not ask the owner to split the file into Story
|
|
38
|
+
files or to run `import-bmad-story` once per Story. The BMAD source is read-only
|
|
39
|
+
and planning-owned.
|
|
40
|
+
|
|
41
|
+
For native BMAD input, the canonical Stories from `epics.md` are the sole
|
|
42
|
+
planning boundaries and the source of Triad Cards: one canonical Story maps to
|
|
43
|
+
one normal Triad Card. Do not run the generic PRD bootstrap decomposition a
|
|
44
|
+
second time, create parallel Cards, or use an LLM to reinterpret the Epic/Story
|
|
45
|
+
boundaries. BMAD decides what the executable Story is; Triad decides how it is
|
|
46
|
+
implemented, verified, and reviewed.
|
|
47
|
+
|
|
48
|
+
First ingest the source without execution assumptions so the Orchestrator can
|
|
49
|
+
inspect the canonical Epic/Story set:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
node .triad-runtime/triad-bmad-intake.mjs \
|
|
53
|
+
--source /absolute/path/to/_bmad-output/planning-artifacts/epics.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
After the control workspace is initialized and its project/repository/gate
|
|
57
|
+
readiness is valid, materialize normal Cards with the same deterministic
|
|
58
|
+
primitive and an explicit output directory:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
node .triad-runtime/triad-bmad-intake.mjs \
|
|
62
|
+
--source /absolute/path/to/_bmad-output/planning-artifacts/epics.md \
|
|
63
|
+
--project /absolute/path/to/control-workspace \
|
|
64
|
+
--output /absolute/path/to/control-workspace/features
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Supply `--repository` only when the Story or project configuration does not
|
|
68
|
+
resolve one deterministically. Supply per-Story `--required-gate STORY-ID=ID`
|
|
69
|
+
and `--depends-on STORY-ID=CARD-ID` only from explicit planning/owner input;
|
|
70
|
+
never infer them from prose or document order. Ingestion readiness (ID, title,
|
|
71
|
+
intent, acceptance criteria, Epic parent) is distinct from execution readiness
|
|
72
|
+
(repository, worktree, trusted gates, and Card contract). A native Story may be
|
|
73
|
+
ingested without `status: ready-for-dev`, but an explicit non-ready status stays
|
|
74
|
+
blocked from execution. Ambiguous or unavailable execution context is a
|
|
75
|
+
fail-closed escalation; do not dispatch a Developer or consume retry budget.
|
|
76
|
+
|
|
29
77
|
## Run one card
|
|
30
78
|
|
|
31
79
|
1. Verify the PRD hash, declared worktree/branch, repository instructions,
|
|
@@ -61,15 +109,48 @@ presentation for this invocation, do not repeat it.
|
|
|
61
109
|
gates, or consume retry/remediation budget. Legacy projects without the
|
|
62
110
|
quality contract retain PRD-only behavior.
|
|
63
111
|
2. Choose one dependency-approved `ready` card, mark it `in_progress`, append an
|
|
64
|
-
attempt, and create an active assignment before delegating.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
112
|
+
attempt, and create an active assignment before delegating. Bind the selected
|
|
113
|
+
repository ID as well as its declared branch/worktree. Populate the optional
|
|
114
|
+
`context` object with only bounded, assignment-relevant fields such as
|
|
115
|
+
`relevant_prd_excerpts`, `relevant_adr_excerpts`, `acceptance_criteria`,
|
|
116
|
+
`verification_mapping`, `expected_paths`, `constraints`, `risks`, and
|
|
117
|
+
`previous_evidence` (short text or references, never whole source files).
|
|
118
|
+
Before each delegation, publish an owner-facing
|
|
119
|
+
activation notice that attributes the configured display name, technical role,
|
|
120
|
+
and card/attempt to that role. Then run the exact packet command:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
node .triad-runtime/triad-assignment-packet.mjs \
|
|
124
|
+
--project /absolute/path/to/control-workspace \
|
|
125
|
+
--assignment .loop/runtime/assignments/<assignment-file>.json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
It atomically creates (or verifies) the immutable packet, binds its path and
|
|
129
|
+
SHA-256 to the assignment, and returns explicit `dispatch.cwd`, control
|
|
130
|
+
workspace, repository, branch, card, packet, and mandatory-skill paths.
|
|
131
|
+
Require each delegated Developer and Reviewer to run the installed runtime
|
|
132
|
+
context diagnostic from its actual activation cwd before reading repository
|
|
133
|
+
skills:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
node /absolute/path/to/control-workspace/.triad-runtime/triad-runtime-context.mjs \
|
|
137
|
+
--project /absolute/path/to/control-workspace \
|
|
138
|
+
--assignment /absolute/path/to/control-workspace/.loop/runtime/assignments/<assignment-file>.json
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Collect its process `cwd`, shell `pwd`, Git top-level, repository mapping,
|
|
142
|
+
and resolved skill paths/hashes. A mismatch or missing skill blocks that role;
|
|
143
|
+
do not substitute control-workspace/global copies or continue to review.
|
|
144
|
+
3. Dispatch the Developer with `cwd`/`workdir` exactly equal to the returned
|
|
145
|
+
assigned product worktree. Pass the card and Assignment Packet as the
|
|
146
|
+
primary contract, followed by the mandatory skill paths and any prior
|
|
147
|
+
evidence. Include the effective gate IDs (all trusted `required: true` gates
|
|
148
|
+
plus the card's selected IDs), risks, and findings. Globally required gates
|
|
149
|
+
are never suppressed. In selected mode, a selected optional gate is required
|
|
150
|
+
for that card and unselected optional gates may be skipped; dedupe the
|
|
151
|
+
effective set. Treat command results and role reports as **agent-reported
|
|
152
|
+
claims**, never as control-plane gate truth. The full PRD/ADR is a fallback
|
|
153
|
+
for a missing detail or contradiction, not systematic startup context.
|
|
73
154
|
4. After completion, move to `verifying`. Follow the recorded dispatch route:
|
|
74
155
|
wait for a valid hook-produced file when one is configured, otherwise invoke
|
|
75
156
|
the verifier explicitly. Accept only current evidence whose assignment ID,
|
|
@@ -94,8 +175,12 @@ presentation for this invocation, do not repeat it.
|
|
|
94
175
|
5. A passing verifier result is **environment-derived evidence**. Move only then
|
|
95
176
|
to `in_review`. Missing, stale, failed, timed-out, invalid-context, or
|
|
96
177
|
invalidated evidence never advances the card.
|
|
97
|
-
6.
|
|
98
|
-
|
|
178
|
+
6. Dispatch the Reviewer with the same immutable Assignment Packet and
|
|
179
|
+
`cwd`/`workdir` equal to the candidate worktree when the review needs direct
|
|
180
|
+
candidate inspection. Add the diff, Developer report, verifier evidence,
|
|
181
|
+
prior attempts, and risks. The Reviewer may consult only a necessary PRD/ADR
|
|
182
|
+
section when the packet is insufficient; it must not reconstruct the whole
|
|
183
|
+
requirement by default. Record its recommendation:
|
|
99
184
|
- `approved`: verify scope/evidence, commit the card locally, promote every
|
|
100
185
|
dependency-satisfied draft card to `ready`, then immediately select and
|
|
101
186
|
assign the next ready card;
|
|
@@ -5,11 +5,17 @@ description: Independently review one verified Triad implementation attempt agai
|
|
|
5
5
|
|
|
6
6
|
# Triad Reviewer
|
|
7
7
|
|
|
8
|
-
Look for defects and request correction until the result is acceptable.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
Look for defects and request correction until the result is acceptable. The host
|
|
9
|
+
should launch the Reviewer with `cwd`/`workdir` equal to the candidate worktree
|
|
10
|
+
when direct candidate inspection is required, while control-workspace paths are
|
|
11
|
+
passed explicitly. Read the same immutable Assignment Packet used by the
|
|
12
|
+
Developer, then the card, Developer report, actual diff/worktree, verifier
|
|
13
|
+
evidence, project policy, repository instructions, and prior attempts. The
|
|
14
|
+
packet is the shared assignment contract; consult only the necessary PRD/ADR
|
|
15
|
+
section when it is insufficient or contradictory rather than reconstructing
|
|
16
|
+
the whole requirement on every review. You may know prior attempts and
|
|
17
|
+
correction history; independence means assess the artifact and evidence
|
|
18
|
+
yourself, not blindness.
|
|
13
19
|
|
|
14
20
|
At the beginning of every activation, read `.triad-plus/team.json` when it
|
|
15
21
|
exists. Your first report to the Orchestrator must identify you as its configured
|
|
@@ -24,6 +30,26 @@ mismatched, or unreported required repository skill is a `blocked` result until
|
|
|
24
30
|
the Orchestrator creates a valid assignment; external context never substitutes
|
|
25
31
|
for the repository skill policy.
|
|
26
32
|
|
|
33
|
+
Before inspecting the candidate, prove the actual Reviewer runtime context by
|
|
34
|
+
running this command from the activation cwd:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
node /absolute/path/to/control-workspace/.triad-runtime/triad-runtime-context.mjs \
|
|
38
|
+
--project /absolute/path/to/control-workspace \
|
|
39
|
+
--assignment /absolute/path/to/control-workspace/.loop/runtime/assignments/<assignment-file>.json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Record packet `cwd`, process `cwd`, `pwd`, `git rev-parse --show-toplevel`, and
|
|
43
|
+
each repository-skill path, resolved path, and SHA-256. Compare the actual cwd
|
|
44
|
+
and Git root with the assigned product worktree, and resolve every relative
|
|
45
|
+
skill path under that worktree only. On `NotFound`, hash mismatch, mixed Git
|
|
46
|
+
roots, or cwd inconsistency, return `blocked` / invalid runtime context; never
|
|
47
|
+
fall back to a copy in the control workspace or a global skill.
|
|
48
|
+
|
|
49
|
+
Use native `read`, `grep`, `glob`, and `list` primitives for simple reads when
|
|
50
|
+
the host provides them. Use shell/Bash for builds, tests, git, scripts, and
|
|
51
|
+
system commands, not as the default filesystem API.
|
|
52
|
+
|
|
27
53
|
Confirm scope, dependencies, candidate fingerprint, assignment ID, feature,
|
|
28
54
|
attempt, expected branch, and PRD/card/gate hashes. Reject stale, missing,
|
|
29
55
|
invalidated, or failed verifier evidence. Independently rerun enough required
|