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.
Files changed (61) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +21 -12
  3. package/adapters/antigravity/.agents/agents/triad-developer/agent.md +7 -3
  4. package/adapters/antigravity/.agents/agents/triad-orchestrator/agent.md +5 -0
  5. package/adapters/antigravity/.agents/agents/triad-reviewer/agent.md +7 -2
  6. package/adapters/antigravity/.agents/skills/triad/SKILL.md +22 -0
  7. package/adapters/antigravity/README.md +1 -1
  8. package/adapters/antigravity/install.sh +1 -1
  9. package/adapters/claude-code/.claude/agents/triad-developer.md +9 -4
  10. package/adapters/claude-code/.claude/agents/triad-reviewer.md +10 -5
  11. package/adapters/claude-code/.claude/commands/triad.md +22 -0
  12. package/adapters/claude-code/README.md +12 -4
  13. package/adapters/claude-code/install.sh +1 -1
  14. package/adapters/codex/README.md +1 -1
  15. package/adapters/codex/install.sh +3 -3
  16. package/adapters/codex/prompts/triad.md +28 -0
  17. package/adapters/copilot/.github/agents/triad-developer.agent.md +12 -6
  18. package/adapters/copilot/.github/agents/triad-evaluator.agent.md +2 -1
  19. package/adapters/copilot/.github/agents/triad-orchestrator.agent.md +15 -1
  20. package/adapters/copilot/.github/agents/triad-reviewer.agent.md +11 -5
  21. package/adapters/copilot/.github/skills/triad/SKILL.md +23 -0
  22. package/adapters/hermes/install.sh +1 -1
  23. package/adapters/hermes/skills/triad/SKILL.md +18 -0
  24. package/adapters/opencode/.opencode/agents/triad-developer.md +23 -4
  25. package/adapters/opencode/.opencode/agents/triad-orchestrator.md +16 -0
  26. package/adapters/opencode/.opencode/agents/triad-reviewer.md +25 -3
  27. package/adapters/opencode/.opencode/commands/triad.md +21 -0
  28. package/adapters/opencode/README.md +29 -5
  29. package/adapters/opencode/install.sh +1 -1
  30. package/adapters/registry.mjs +23 -1
  31. package/bin/triad-plus.js +210 -38
  32. package/docs/architecture.md +8 -0
  33. package/docs/assignment-packets.md +35 -0
  34. package/docs/bmad-integration.md +187 -82
  35. package/docs/configuration.md +29 -0
  36. package/docs/npx-installation.md +20 -0
  37. package/docs/opencode-replication.md +8 -0
  38. package/docs/operating-guide.it.md +8 -0
  39. package/docs/operating-guide.md +7 -0
  40. package/docs/runtimes.md +1 -1
  41. package/docs/verification.md +20 -0
  42. package/integrations/bmad/README.md +17 -9
  43. package/integrations/bmad/epics-parser.mjs +697 -0
  44. package/integrations/bmad/story-importer.mjs +143 -5
  45. package/package.json +2 -2
  46. package/runtime/lib/assignment-packet.mjs +553 -0
  47. package/runtime/lib/model-config.mjs +165 -0
  48. package/runtime/lib/repository-context.mjs +192 -0
  49. package/runtime/lib/terminal.mjs +86 -0
  50. package/runtime/triad-assignment-packet.mjs +62 -0
  51. package/runtime/triad-bmad-intake.mjs +150 -0
  52. package/runtime/triad-runtime-context.mjs +88 -0
  53. package/runtime/triad-verify.mjs +41 -21
  54. package/schemas/verification-evidence.schema.json +8 -0
  55. package/skills/triad-loop-bootstrap/SKILL.md +39 -4
  56. package/skills/triad-loop-bootstrap/assets/loop-template/runtime/assignments/assignment.template.json +14 -1
  57. package/skills/triad-loop-developer/SKILL.md +33 -8
  58. package/skills/triad-loop-orchestrator/SKILL.md +96 -11
  59. package/skills/triad-loop-reviewer/SKILL.md +31 -5
  60. package/skills/triad-model-configuration/SKILL.md +98 -0
  61. 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
+ }
@@ -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
- if (required === undefined) return { declared: false, skills: [] };
38
- if (!Array.isArray(required) || required.length === 0) throw new Error("repository skill binding must declare at least one skill");
39
- const root = await realpath(worktree);
40
- const skills = [];
41
- for (const item of required) {
42
- if (!item || typeof item.path !== "string" || typeof item.sha256 !== "string") {
43
- throw new Error("repository skill binding entries require path and sha256");
44
- }
45
- const candidate = path.resolve(root, item.path);
46
- if (!candidate.startsWith(`${root}${path.sep}`)) throw new Error("repository skill path escapes worktree");
47
- try { await access(candidate); }
48
- catch { throw new Error(`repository skill missing: ${item.path}`); }
49
- const actual = await sha256File(candidate);
50
- if (actual !== item.sha256) throw new Error(`repository skill hash mismatch: ${item.path}`);
51
- skills.push({ path: item.path, sha256: actual });
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 { declared: true, skills };
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 worktree = await realpath(path.resolve(projectRoot, assignment.worktree));
171
- if (!worktree.startsWith(`${projectRoot}${path.sep}`) && !assignment.allow_external_worktree) throw new Error("undeclared external worktree");
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
- const repositorySkills = await validateRepositorySkills(assignment.required_repository_skills, worktree);
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/`, then create bounded feature cards
22
- under `features/` and a complete `feature-plan.md`.
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 hashes, and verifier
51
- run ID. The verifier produces environment-derived evidence; it never changes
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. Read the card, PRD excerpt,
9
- project manifest, repository instructions, allowed surface, prior findings, and
10
- gates. When the assignment contains a scope contract, read it as a deterministic
11
- path boundary in addition to the human card scope: keep every cumulative change
12
- from the card baseline within its allowed paths, and use the recorded offending
13
- paths for `scope_cleanup`. Verify the declared worktree and branch before
14
- editing. Do not change scope, policy, assignments, queue, state, or evidence
15
- records.
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. Before each
65
- delegation, publish an owner-facing activation notice that attributes the
66
- configured display name, technical role, and card/attempt to that role.
67
- 3. Give the Developer the card, relevant PRD excerpt, allowed surface, the
68
- effective gate IDs (all trusted `required: true` gates plus the card's
69
- selected IDs), risks, and prior findings. Globally required gates are never
70
- suppressed. In selected mode, a selected optional gate is required for that card and unselected optional gates may be
71
- skipped; dedupe the effective set. Treat its command results and report as
72
- **agent-reported claims**, never as control-plane gate truth.
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. Give the Reviewer the card, diff, developer report, verifier evidence, prior
98
- attempts, and risks. Record its recommendation:
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. Read the
9
- card, PRD excerpt, project policy, prior attempts, Developer report, repository
10
- instructions, actual diff/worktree, and verifier evidence. You may know prior
11
- attempts and correction history; independence means assess the artifact and
12
- evidence yourself, not blindness.
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