triad-plus 1.1.1 → 1.3.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 +25 -0
- package/README.md +8 -5
- package/adapters/antigravity/.agents/agents/triad-orchestrator/agent.md +8 -0
- package/adapters/antigravity/.agents/skills/triad/SKILL.md +7 -0
- package/adapters/claude-code/.claude/commands/triad.md +7 -0
- package/adapters/codex/README.md +6 -4
- package/adapters/codex/prompts/triad.md +28 -4
- package/adapters/codex/runtime.json +4 -0
- package/adapters/hermes/skills/triad/SKILL.md +7 -0
- package/adapters/opencode/.opencode/agents/triad-developer.md +4 -0
- package/adapters/opencode/.opencode/agents/triad-evaluator.md +8 -5
- package/adapters/opencode/.opencode/agents/triad-orchestrator.md +8 -0
- package/adapters/opencode/.opencode/agents/triad-reviewer.md +4 -0
- package/adapters/opencode/.opencode/commands/triad.md +7 -0
- package/bin/triad-plus.js +138 -3
- package/docs/codex-replication.md +8 -4
- package/docs/compatibility.md +1 -1
- package/docs/configuration.md +6 -5
- package/docs/npx-installation.md +17 -0
- package/docs/operating-guide.it.md +14 -1
- package/docs/operating-guide.md +14 -1
- package/docs/runtimes.md +1 -1
- package/docs/verification.md +25 -0
- package/integrations/codex/README.md +20 -12
- package/integrations/codex/hooks.json +1 -2
- package/package.json +1 -1
- package/runtime/legacy-adapters.json +1 -0
- package/runtime/triad-runtime-capabilities.mjs +30 -2
- package/runtime/triad-verify.mjs +22 -0
- package/skills/triad-loop-bootstrap/SKILL.md +3 -1
- package/skills/triad-loop-bootstrap/assets/loop-template/feature-card.template.md +6 -0
- package/skills/triad-loop-bootstrap/assets/loop-template/handoff-report.template.md +8 -0
- package/skills/triad-loop-bootstrap/assets/loop-template/run-state.yaml +8 -0
- package/skills/triad-loop-bootstrap/assets/loop-template/runtime/assignments/assignment.template.json +10 -0
- package/skills/triad-loop-bootstrap/assets/project.yaml +10 -0
- package/skills/triad-loop-developer/SKILL.md +12 -0
- package/skills/triad-loop-evaluator/SKILL.md +12 -0
- package/skills/triad-loop-orchestrator/SKILL.md +66 -6
- package/skills/triad-loop-reviewer/SKILL.md +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.0 — 2026-08-26
|
|
4
|
+
|
|
5
|
+
- Keep the declared card chain unattended from Developer completion through
|
|
6
|
+
verification, review, and the next dependency-satisfied card.
|
|
7
|
+
- Bind repository skill files and hashes to attempts as verifier evidence, and
|
|
8
|
+
make role activations observable to the owner.
|
|
9
|
+
- Require a formal owner delivery closure: final push evidence, optional
|
|
10
|
+
Evaluator+ report, handoff, run-record update, and practical-test guidance.
|
|
11
|
+
- Make local versus remote demo access explicit; `localhost` is never reported
|
|
12
|
+
as a remote endpoint.
|
|
13
|
+
- Use explicit verification dispatch by default on Codex; retain the async
|
|
14
|
+
`SubagentStop` hook as an experimental opt-in path.
|
|
15
|
+
|
|
16
|
+
## Unreleased
|
|
17
|
+
|
|
18
|
+
_No changes beyond the 1.3.0 review candidate._
|
|
19
|
+
|
|
20
|
+
## 1.2.0 — 2026-08-25
|
|
21
|
+
|
|
22
|
+
- Add safe dry-run/apply upgrades for existing project-control workspaces.
|
|
23
|
+
- Maintain a narrowly scoped Triad+ role-run instruction overlay and report
|
|
24
|
+
potential host identity-policy conflicts in `doctor`.
|
|
25
|
+
- Require the configured Orchestrator to introduce itself at the beginning of
|
|
26
|
+
every Triad+ run.
|
|
27
|
+
|
|
3
28
|
## 1.1.1 — 2026-08-25
|
|
4
29
|
|
|
5
30
|
- Ensure every supported host adopts the configured Orchestrator display name
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ implementation, review, and orchestration.
|
|
|
16
16
|
| | What changes |
|
|
17
17
|
| --- | --- |
|
|
18
18
|
| **Independent review** | A Reviewer examines the candidate and verifier evidence instead of asking the Developer to grade its own work. |
|
|
19
|
-
| **Different models per role** | Choose
|
|
19
|
+
| **Different models per role** | Choose models, personas, and supported effort per role within one selected runtime. |
|
|
20
20
|
| **Evidence-backed verification** | A claim that tests passed is not proof: `triad-verify` records environment-derived gate evidence. |
|
|
21
21
|
| **Works across coding agents** | Use Codex, Claude Code, OpenCode, Antigravity, or Hermes Agent through peer adapters. |
|
|
22
22
|
|
|
@@ -138,7 +138,7 @@ It sees only the goal, quality target, final candidate, and verifier evidence.
|
|
|
138
138
|
|
|
139
139
|
| Runtime | Orchestrator | Developer | Reviewer | Evaluator+ | Verification dispatch |
|
|
140
140
|
| --- | --- | --- | --- | --- | --- |
|
|
141
|
-
| Codex | Yes | Yes | Yes | Yes | Explicit
|
|
141
|
+
| Codex | Yes | Yes | Yes | Yes | Explicit dispatch by default; experimental async hook opt-in |
|
|
142
142
|
| Claude Code | Yes | Yes | Yes | Yes | Explicit fallback; validated hook when available |
|
|
143
143
|
| OpenCode | Yes | Yes | Yes | Yes | Explicit dispatch |
|
|
144
144
|
| Antigravity | Yes | Yes | Yes | Yes | Explicit dispatch |
|
|
@@ -147,9 +147,12 @@ It sees only the goal, quality target, final candidate, and verifier evidence.
|
|
|
147
147
|
## Configuration and verification
|
|
148
148
|
|
|
149
149
|
The installer writes `.triad-plus/team.json`, which separates stable role IDs
|
|
150
|
-
from user-facing names, personas, models, and supported effort/options.
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
from user-facing names, personas, models, and supported effort/options. A control
|
|
151
|
+
workspace selects one host adapter for the whole run; its roles can use different
|
|
152
|
+
models where that host supports them. To use another host, create another control
|
|
153
|
+
workspace with that adapter. Triad+ does not dispatch different roles across
|
|
154
|
+
different hosts in one run. Read [configuration](docs/configuration.md) and
|
|
155
|
+
[runtime details](docs/runtimes.md).
|
|
153
156
|
|
|
154
157
|
`triad-verify` validates assignment/candidate binding, runs declared
|
|
155
158
|
control-plane gates, detects mutation, and writes evidence. It supports the
|
|
@@ -19,6 +19,14 @@ non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
|
19
19
|
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
20
20
|
outputs, but never claim their identity.
|
|
21
21
|
|
|
22
|
+
If the current Triad+ invocation has not yet introduced the Orchestrator, its
|
|
23
|
+
first owner-facing message is a presentation, not a generic acknowledgement or
|
|
24
|
+
bootstrap report. Before any other owner-facing content, begin with a
|
|
25
|
+
first-person sentence that includes `<displayName>` and "Triad+ Orchestrator",
|
|
26
|
+
localized to the configured interaction language; then state whether the run is
|
|
27
|
+
new or resumed and what input was received. If the entry point already made that
|
|
28
|
+
presentation for this invocation, do not repeat it.
|
|
29
|
+
|
|
22
30
|
Detect the runtime with `.triad-runtime/triad-runtime-capabilities.mjs --host
|
|
23
31
|
antigravity`. Use explicit verification dispatch unless the recorded capability
|
|
24
32
|
snapshot proves a supported asynchronous route. Govern ordinary disagreements
|
|
@@ -18,6 +18,13 @@ non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
|
18
18
|
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
19
19
|
outputs, but never claim their identity.
|
|
20
20
|
|
|
21
|
+
The first owner-facing message of a Triad+ invocation is a presentation, not a
|
|
22
|
+
generic acknowledgement or bootstrap report. Before any other owner-facing
|
|
23
|
+
content, begin with a first-person sentence that includes `<displayName>` and
|
|
24
|
+
"Triad+ Orchestrator", localized to the configured interaction language; then
|
|
25
|
+
state whether the run is new or resumed and what input was received. Do not
|
|
26
|
+
repeat this presentation when an already-introduced run loads its role skill.
|
|
27
|
+
|
|
21
28
|
Before work, compare every role model that the host exposes with the recorded
|
|
22
29
|
contract. If Antigravity does not expose a model identity, say that it cannot be
|
|
23
30
|
verified and ask the owner to select or confirm it; never claim a model match
|
|
@@ -26,6 +26,13 @@ non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
|
26
26
|
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
27
27
|
outputs, but never claim their identity.
|
|
28
28
|
|
|
29
|
+
The first owner-facing message of a Triad+ invocation is a presentation, not a
|
|
30
|
+
generic acknowledgement or bootstrap report. Before any other owner-facing
|
|
31
|
+
content, begin with a first-person sentence that includes `<displayName>` and
|
|
32
|
+
"Triad+ Orchestrator", localized to the configured interaction language; then
|
|
33
|
+
state whether the run is new or resumed and what input was received. Do not
|
|
34
|
+
repeat this presentation when an already-introduced run loads its role skill.
|
|
35
|
+
|
|
29
36
|
Delegate implementation to `triad-developer` and review to `triad-reviewer`.
|
|
30
37
|
After Triad approval, automatically invoke fresh `triad-evaluator` when
|
|
31
38
|
`.triad-plus/team.json` has `roles.evaluator.enabled: true`; false or omitted
|
package/adapters/codex/README.md
CHANGED
|
@@ -33,7 +33,9 @@ Before it starts work, configure the four named role profiles described in
|
|
|
33
33
|
does not edit profile or model configuration, because their availability and
|
|
34
34
|
model routing are host-owner decisions.
|
|
35
35
|
|
|
36
|
-
At bootstrap and resume, the Orchestrator runs the runtime capability detector
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
At bootstrap and resume, the Orchestrator runs the runtime capability detector
|
|
37
|
+
with the project's `control_plane.dispatch_mode`. Codex `auto` selects explicit
|
|
38
|
+
verification dispatch even when an async hook is available. The async
|
|
39
|
+
`SubagentStop` route remains an experimental opt-in (`async_hook`) and falls
|
|
40
|
+
back to explicit dispatch when unavailable. On Codex CLI 0.142, explicit
|
|
41
|
+
dispatch is the safe expected route.
|
|
@@ -8,10 +8,13 @@ $ARGUMENTS
|
|
|
8
8
|
|
|
9
9
|
Act as the Triad Orchestrator. Load `triad-loop-bootstrap` for a new project or
|
|
10
10
|
`triad-loop-orchestrator` for an initialized project. At bootstrap and resume,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
read `project.control_plane.dispatch_mode` (defaulting to `auto`) and pass it as
|
|
12
|
+
`--requested-mode` to `.triad-runtime/triad-runtime-capabilities.mjs --adapter
|
|
13
|
+
.triad-runtime/adapter.json`; when `.codex/hooks.json` exists, pass it as
|
|
14
|
+
`--hook-config .codex/hooks.json`. Record the result. Codex `auto` selects
|
|
15
|
+
`explicit_dispatch`; use `async_hook` only when `dispatch_mode: async_hook` was
|
|
16
|
+
explicitly configured and the installed Codex lifecycle hook has been validated.
|
|
17
|
+
Otherwise explicitly invoke the verifier after Developer completion.
|
|
15
18
|
|
|
16
19
|
If `.triad-plus/team.json` exists, load it before replying. Use its interaction
|
|
17
20
|
language, owner address, display names, personas, and model contract in communication;
|
|
@@ -26,6 +29,14 @@ non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
|
26
29
|
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
27
30
|
outputs, but never claim their identity.
|
|
28
31
|
|
|
32
|
+
The first owner-facing message of a Triad+ invocation is a presentation, not a
|
|
33
|
+
generic acknowledgement or bootstrap report. Before any other owner-facing
|
|
34
|
+
content, begin with a first-person sentence that includes `<displayName>` and
|
|
35
|
+
"Triad+ Orchestrator", localized to the configured interaction language; then
|
|
36
|
+
state in one sentence whether the run is new or resumed and what input was
|
|
37
|
+
received. Do not repeat this presentation when an already-introduced run later
|
|
38
|
+
loads `triad-loop-orchestrator`.
|
|
39
|
+
|
|
29
40
|
Delegate implementation to the configured `triad_developer` profile and review to
|
|
30
41
|
`triad_reviewer`. Once Triad is approved, automatically invoke a fresh
|
|
31
42
|
`triad_evaluator` when `.triad-plus/team.json` has `roles.evaluator.enabled: true`.
|
|
@@ -33,3 +44,16 @@ When false or omitted, do not invoke it. Its report never reopens the completed
|
|
|
33
44
|
`--evaluator` and `--no-evaluator` are per-run overrides when supplied. Continue autonomously through declared cards and normal branch
|
|
34
45
|
pushes once all gates pass. Escalate only the decision types defined by the
|
|
35
46
|
Triad skills. Do not start or stop a demo without an owner instruction.
|
|
47
|
+
|
|
48
|
+
After each Reviewer `approved` verdict, immediately promote every
|
|
49
|
+
dependency-satisfied draft card to `ready`, select the next ready card, create
|
|
50
|
+
its assignment, and delegate it. Do not ask the owner to continue or pause
|
|
51
|
+
between cards while a dependency-satisfied card remains. Stop only for a
|
|
52
|
+
declared escalation, a blocked card, or when every required card is terminal.
|
|
53
|
+
|
|
54
|
+
The ordinary chain is unattended: Developer completion → verifier → Reviewer →
|
|
55
|
+
rework or approval → next card. A Developer report is never a reason to wait
|
|
56
|
+
for owner input: immediately wait for configured hook evidence or invoke the
|
|
57
|
+
verifier, then dispatch the Reviewer on a pass. Ask the owner only for a
|
|
58
|
+
declared escalation, a blocked verdict, an unrecoverable runtime error, or an
|
|
59
|
+
explicit owner pause.
|
|
@@ -19,6 +19,13 @@ non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
|
19
19
|
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
20
20
|
outputs, but never claim their identity.
|
|
21
21
|
|
|
22
|
+
The first owner-facing message of a Triad+ invocation is a presentation, not a
|
|
23
|
+
generic acknowledgement or bootstrap report. Before any other owner-facing
|
|
24
|
+
content, begin with a first-person sentence that includes `<displayName>` and
|
|
25
|
+
"Triad+ Orchestrator", localized to the configured interaction language; then
|
|
26
|
+
state whether the run is new or resumed and what input was received. Do not
|
|
27
|
+
repeat this presentation when an already-introduced run loads its role skill.
|
|
28
|
+
|
|
22
29
|
For a normal run, govern only Orchestrator, Developer, and Reviewer. Delegate
|
|
23
30
|
ordinary implementation with `hermes chat -q --in <worktree> --no-restore-cwd`
|
|
24
31
|
using `--skills triad-loop-developer`; obtain a separate Reviewer result with
|
|
@@ -22,6 +22,10 @@ feature card in its declared worktree. Read the card, PRD excerpt, repository
|
|
|
22
22
|
instructions, prior attempts, allowed change surface, and required gates before
|
|
23
23
|
changing code.
|
|
24
24
|
|
|
25
|
+
At activation, read `.triad-plus/team.json`. Your first report to the
|
|
26
|
+
Orchestrator identifies the configured `roles.developer.displayName` as the
|
|
27
|
+
Triad+ Developer and names the assigned card.
|
|
28
|
+
|
|
25
29
|
Verify the worktree and branch. Do not expand scope, silently change project
|
|
26
30
|
policy, add dependencies without authorization, or make delivery decisions. Run
|
|
27
31
|
the required gates, measure the declared metrics, and report exact commands and
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Freshly evaluates
|
|
2
|
+
description: Freshly evaluates an approved Triad+ result against its stated quality target.
|
|
3
3
|
mode: subagent
|
|
4
4
|
hidden: true
|
|
5
5
|
temperature: 0.1
|
|
@@ -23,7 +23,10 @@ snapshotted quality bar, real candidate artifact, and valid verification
|
|
|
23
23
|
summary. Do not request developer reports, prior evaluator or reviewer findings,
|
|
24
24
|
attempt history, or implementation narrative.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
At activation, read `.triad-plus/team.json`. Your first report identifies the
|
|
27
|
+
configured `roles.evaluator.displayName` as Evaluator+ and names the completed
|
|
28
|
+
result being assessed.
|
|
29
|
+
|
|
30
|
+
Return `PASS`, `FAIL`, or `INDETERMINATE` with concise evidence. Do not edit
|
|
31
|
+
source, make delivery decisions, commit, push, or change workflow state. A
|
|
32
|
+
verdict never reopens Triad or starts repair.
|
|
@@ -36,6 +36,14 @@ non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
|
36
36
|
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
37
37
|
outputs, but never claim their identity.
|
|
38
38
|
|
|
39
|
+
If the current Triad+ invocation has not yet introduced the Orchestrator, its
|
|
40
|
+
first owner-facing message is a presentation, not a generic acknowledgement or
|
|
41
|
+
bootstrap report. Before any other owner-facing content, begin with a
|
|
42
|
+
first-person sentence that includes `<displayName>` and "Triad+ Orchestrator",
|
|
43
|
+
localized to the configured interaction language; then state whether the run is
|
|
44
|
+
new or resumed and what input was received. If the entry point already made that
|
|
45
|
+
presentation for this invocation, do not repeat it.
|
|
46
|
+
|
|
39
47
|
Start by loading `triad-loop-bootstrap` for a new project, or
|
|
40
48
|
`triad-loop-orchestrator` for an initialized project. Follow the loaded skill
|
|
41
49
|
exactly. Keep the project-control workspace separate from product repositories,
|
|
@@ -22,6 +22,10 @@ Review the actual card, PRD excerpt, diff, worktree, developer evidence,
|
|
|
22
22
|
repository instructions, prior attempts, gates, and metrics. Independently rerun
|
|
23
23
|
enough required gates to verify claims.
|
|
24
24
|
|
|
25
|
+
At activation, read `.triad-plus/team.json`. Your first report to the
|
|
26
|
+
Orchestrator identifies the configured `roles.reviewer.displayName` as the
|
|
27
|
+
Triad+ Reviewer and names the feature and attempt under review.
|
|
28
|
+
|
|
25
29
|
Return one evidence-based recommendation: `approved`, `rework`, or `blocked`.
|
|
26
30
|
List severity-ranked findings before gate and metric evidence, residual risks,
|
|
27
31
|
and the recommendation. A blocked recommendation must identify the exact owner
|
|
@@ -19,6 +19,13 @@ non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
|
19
19
|
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
20
20
|
outputs, but never claim their identity.
|
|
21
21
|
|
|
22
|
+
The first owner-facing message of a Triad+ invocation is a presentation, not a
|
|
23
|
+
generic acknowledgement or bootstrap report. Before any other owner-facing
|
|
24
|
+
content, begin with a first-person sentence that includes `<displayName>` and
|
|
25
|
+
"Triad+ Orchestrator", localized to the configured interaction language; then
|
|
26
|
+
state whether the run is new or resumed and what input was received. Do not
|
|
27
|
+
repeat this presentation when an already-introduced run loads its role skill.
|
|
28
|
+
|
|
22
29
|
If this is a new project, load `triad-loop-bootstrap`, collect only missing
|
|
23
30
|
inputs that prevent safe setup or measurable feature cards, create the isolated
|
|
24
31
|
project-control workspace, and show the full feature-card plan before starting.
|
package/bin/triad-plus.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { access, cp, mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { access, cp, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -19,22 +19,24 @@ Usage:
|
|
|
19
19
|
npx triad-plus
|
|
20
20
|
npx triad-plus init --host <adapter-id> --control <path> [--global] [--team-config <path>] [--allow-product-repo]
|
|
21
21
|
npx triad-plus doctor --host <adapter-id> --control <path> [--hook-config <path>]
|
|
22
|
+
npx triad-plus upgrade --host <adapter-id> --control <path> [--global] [--apply]
|
|
22
23
|
|
|
23
24
|
Adapters: ${listAdapters().map((adapter) => adapter.id).join(', ')}
|
|
24
25
|
|
|
25
26
|
The control path is a project-control workspace, not a product repository.
|
|
26
|
-
Installation refuses every asset overwrite.
|
|
27
|
+
Installation refuses every asset overwrite. Upgrade is a dry run unless --apply is supplied.
|
|
27
28
|
`);
|
|
28
29
|
process.exit(exitCode);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function parseArgs(args) {
|
|
32
33
|
const [command, ...rest] = args;
|
|
33
|
-
const options = { command, global: false, allowProductRepo: false };
|
|
34
|
+
const options = { command, global: false, allowProductRepo: false, apply: false };
|
|
34
35
|
for (let index = 0; index < rest.length; index += 1) {
|
|
35
36
|
const argument = rest[index];
|
|
36
37
|
if (argument === '--global') options.global = true;
|
|
37
38
|
else if (argument === '--allow-product-repo') options.allowProductRepo = true;
|
|
39
|
+
else if (argument === '--apply') options.apply = true;
|
|
38
40
|
else if (['--host', '--control', '--team-config', '--hook-config'].includes(argument)) {
|
|
39
41
|
const value = rest[index + 1];
|
|
40
42
|
if (!value || value.startsWith('--')) throw new Error(`${argument} requires a value.`);
|
|
@@ -100,10 +102,74 @@ async function installAssets(assets, root, installContext) {
|
|
|
100
102
|
for (const asset of assets) await copyAsset(asset, root, installContext);
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
async function replaceManagedPath(source, destination, backup, apply) {
|
|
106
|
+
const present = await exists(destination);
|
|
107
|
+
process.stdout.write(` ${apply ? 'Update' : 'Would update'} ${destination}${present ? ' (backup)' : ''}\n`);
|
|
108
|
+
if (!apply) return;
|
|
109
|
+
if (present) {
|
|
110
|
+
await mkdir(dirname(backup), { recursive: true });
|
|
111
|
+
await cp(destination, backup, { recursive: true });
|
|
112
|
+
await rm(destination, { recursive: true, force: true });
|
|
113
|
+
}
|
|
114
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
115
|
+
await cp(source, destination, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function refreshAssets(assets, root, installContext, backupRoot, apply) {
|
|
119
|
+
for (const asset of assets) {
|
|
120
|
+
const destination = resolveDestination(asset.destination, root, installContext);
|
|
121
|
+
if (asset.source === 'shared-skills') {
|
|
122
|
+
for (const name of sharedSkillNames) await replaceManagedPath(join(packageRoot, 'skills', name), join(destination, name), join(backupRoot, name), apply);
|
|
123
|
+
} else {
|
|
124
|
+
await replaceManagedPath(sourcePath(asset.source), destination, join(backupRoot, asset.source.replaceAll('/', '__')), apply);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
103
129
|
function teamConfigPath(controlRoot) {
|
|
104
130
|
return join(controlRoot, '.triad-plus', 'team.json');
|
|
105
131
|
}
|
|
106
132
|
|
|
133
|
+
const overlayStart = '<!-- triad-plus:managed-instructions:start -->';
|
|
134
|
+
const overlayEnd = '<!-- triad-plus:managed-instructions:end -->';
|
|
135
|
+
function instructionOverlay(team) {
|
|
136
|
+
const displayName = typeof team?.roles?.orchestrator?.displayName === 'string' && team.roles.orchestrator.displayName.trim()
|
|
137
|
+
? team.roles.orchestrator.displayName.trim()
|
|
138
|
+
: 'Triad Orchestrator';
|
|
139
|
+
return `${overlayStart}
|
|
140
|
+
## Triad+ role-run overlay
|
|
141
|
+
|
|
142
|
+
When a Triad+ entry point is invoked in this control workspace, read
|
|
143
|
+
\`.triad-plus/team.json\` before the first owner-facing reply. The active
|
|
144
|
+
Orchestrator is \`${displayName}\` for this run. Its first owner-facing message
|
|
145
|
+
is a presentation, not a bootstrap report: begin with a first-person sentence
|
|
146
|
+
that explicitly names \`${displayName}\` and says it is the Triad+ Orchestrator,
|
|
147
|
+
then state whether the run is new or resumed and the received input. Do this
|
|
148
|
+
before reporting bootstrap, inspecting artifacts, delegating, or asking a
|
|
149
|
+
question. This is a role-run presentation rule; it does not change technical
|
|
150
|
+
authority, repository policy, safety instructions, or the host's identity
|
|
151
|
+
outside Triad+.
|
|
152
|
+
${overlayEnd}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function overlayPlan(controlRoot, team) {
|
|
156
|
+
const target = join(controlRoot, 'AGENTS.md');
|
|
157
|
+
const overlay = instructionOverlay(team);
|
|
158
|
+
if (!(await exists(target))) return { target, action: 'create', content: `# Project instructions\n\n${overlay}\n` };
|
|
159
|
+
const source = await readFile(target, 'utf8');
|
|
160
|
+
const start = source.indexOf(overlayStart);
|
|
161
|
+
const end = source.indexOf(overlayEnd);
|
|
162
|
+
if (start === -1 && end === -1) return { target, action: 'append', content: `${source.replace(/\s*$/, '')}\n\n${overlay}\n` };
|
|
163
|
+
if (start < 0 || end < start) throw new Error(`Cannot safely update managed instruction block: ${target}`);
|
|
164
|
+
return { target, action: 'update', content: `${source.slice(0, start)}${overlay}${source.slice(end + overlayEnd.length)}` };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function applyOverlay(controlRoot, apply, team) {
|
|
168
|
+
const plan = await overlayPlan(controlRoot, team);
|
|
169
|
+
process.stdout.write(` Instructions ${apply ? plan.action : `would ${plan.action}`} ${plan.target}\n`);
|
|
170
|
+
if (apply) await writeFile(plan.target, plan.content, 'utf8');
|
|
171
|
+
}
|
|
172
|
+
|
|
107
173
|
async function loadTeamConfig(options) {
|
|
108
174
|
if (options.team) return options.team;
|
|
109
175
|
if (!options.teamConfig) return null;
|
|
@@ -144,6 +210,7 @@ async function writeRoleProfiles(paths, team) {
|
|
|
144
210
|
`Act as ${configuration.displayName}, the ${role.label} role in Triad+.`,
|
|
145
211
|
`Persona: ${configuration.persona || 'professional and role-focused'}.`,
|
|
146
212
|
'Read .triad-plus/team.json in the active project-control workspace before working.',
|
|
213
|
+
`At the beginning of each activation, identify yourself as ${configuration.displayName}, the Triad+ ${role.label}, in your first role report.`,
|
|
147
214
|
'Technical role IDs define authority; display names never change it.'
|
|
148
215
|
].join('\n');
|
|
149
216
|
const profile = [
|
|
@@ -236,6 +303,7 @@ async function init(options) {
|
|
|
236
303
|
if (existing.length > 0) throw new Error(`Installation aborted; existing paths would be overwritten:\n${existing.map((target) => ` ${target}`).join('\n')}`);
|
|
237
304
|
await installAssets(adapter.projectAssets, controlRoot, installContext);
|
|
238
305
|
if (team) await writeTeamConfig(controlRoot, team);
|
|
306
|
+
if (team) await applyOverlay(controlRoot, true, team);
|
|
239
307
|
if (team) await applyTeamBinding(adapter, controlRoot, team, installContext);
|
|
240
308
|
if (options.global) await installAssets(adapter.globalAssets, controlRoot, installContext);
|
|
241
309
|
process.stdout.write(`Triad+ installed for ${adapter.label} in ${controlRoot}\n`);
|
|
@@ -245,6 +313,64 @@ async function init(options) {
|
|
|
245
313
|
process.stdout.write(`Open the control workspace and use ${adapter.entry} <PRD path>. Configured Evaluator+ runs automatically post-approval.\n`);
|
|
246
314
|
}
|
|
247
315
|
|
|
316
|
+
async function currentTeam(controlRoot) {
|
|
317
|
+
const target = teamConfigPath(controlRoot);
|
|
318
|
+
if (!(await exists(target))) return null;
|
|
319
|
+
try { return JSON.parse(await readFile(target, 'utf8')); }
|
|
320
|
+
catch { throw new Error(`Cannot safely upgrade an invalid team config: ${target}`); }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const deliveryStateDefault = `
|
|
324
|
+
delivery:
|
|
325
|
+
status: not_delivered
|
|
326
|
+
handoff: null
|
|
327
|
+
branches: []
|
|
328
|
+
evaluator_report: null
|
|
329
|
+
delivered_at: null
|
|
330
|
+
owner_message: null
|
|
331
|
+
`;
|
|
332
|
+
|
|
333
|
+
async function upgradeRunStateDelivery(controlRoot, backupRoot, apply) {
|
|
334
|
+
const target = join(controlRoot, '.loop', 'run-state.yaml');
|
|
335
|
+
if (!(await exists(target))) {
|
|
336
|
+
process.stdout.write(' Delivery state skipped: no initialized .loop/run-state.yaml\n');
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const source = await readFile(target, 'utf8');
|
|
340
|
+
if (/^delivery:\s*$/m.test(source)) {
|
|
341
|
+
process.stdout.write(` Delivery state already present ${target}\n`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
process.stdout.write(` ${apply ? 'Initialize' : 'Would initialize'} delivery state ${target} (backup)\n`);
|
|
345
|
+
if (!apply) return;
|
|
346
|
+
const backup = join(backupRoot, 'project', 'run-state.yaml');
|
|
347
|
+
await mkdir(dirname(backup), { recursive: true });
|
|
348
|
+
await cp(target, backup);
|
|
349
|
+
await writeFile(target, `${source.replace(/\s*$/, '')}\n${deliveryStateDefault}`, 'utf8');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function upgrade(options) {
|
|
353
|
+
const adapter = getAdapter(options.host);
|
|
354
|
+
if (!adapter) throw new Error(`Choose --host ${listAdapters().map((item) => item.id).join(', ')}.`);
|
|
355
|
+
if (!options.control) throw new Error('Provide --control <project-control-path>.');
|
|
356
|
+
const controlRoot = resolve(options.control);
|
|
357
|
+
await requireDirectory(controlRoot);
|
|
358
|
+
const team = await currentTeam(controlRoot);
|
|
359
|
+
const installContext = context(controlRoot);
|
|
360
|
+
const stamp = new Date().toISOString().replaceAll(':', '-').replaceAll('.', '-');
|
|
361
|
+
const backupRoot = join(controlRoot, '.triad-plus', 'backups', stamp);
|
|
362
|
+
process.stdout.write(`Triad+ upgrade ${options.apply ? 'applying' : 'plan'} for ${adapter.label}\n`);
|
|
363
|
+
await refreshAssets(adapter.projectAssets, controlRoot, installContext, join(backupRoot, 'project'), options.apply);
|
|
364
|
+
await upgradeRunStateDelivery(controlRoot, backupRoot, options.apply);
|
|
365
|
+
if (team) await applyOverlay(controlRoot, options.apply, team);
|
|
366
|
+
else process.stdout.write(' Instructions skipped: .triad-plus/team.json is not configured\n');
|
|
367
|
+
if (options.global) {
|
|
368
|
+
await refreshAssets(adapter.globalAssets, controlRoot, installContext, join(backupRoot, 'global'), options.apply);
|
|
369
|
+
if (team) await applyTeamBinding(adapter, controlRoot, team, installContext);
|
|
370
|
+
}
|
|
371
|
+
if (!options.apply) process.stdout.write('Dry run only. Re-run with --apply to update managed assets.\n');
|
|
372
|
+
}
|
|
373
|
+
|
|
248
374
|
async function doctor(options) {
|
|
249
375
|
if (!options.control) throw new Error('Provide --control <project-control-path>.');
|
|
250
376
|
const controlRoot = resolve(options.control);
|
|
@@ -270,6 +396,14 @@ async function doctor(options) {
|
|
|
270
396
|
process.stdout.write(` Adapter ${manifest ? 'OK' : 'missing or different adapter'}\n`);
|
|
271
397
|
process.stdout.write(` Team config ${team === 'invalid' ? 'invalid' : team ? 'OK' : 'not configured'}\n`);
|
|
272
398
|
process.stdout.write(` Evaluator+ ${team?.roles?.evaluator?.enabled === true ? 'configured' : 'not configured'}\n`);
|
|
399
|
+
const overlay = await overlayPlan(controlRoot, team).catch(() => null);
|
|
400
|
+
process.stdout.write(` Instructions ${overlay ? overlay.action === 'update' ? 'managed' : `needs ${overlay.action}` : 'invalid managed block'}\n`);
|
|
401
|
+
const globalAgents = join(codexHome(), 'AGENTS.md');
|
|
402
|
+
if (await exists(globalAgents)) {
|
|
403
|
+
const globalText = await readFile(globalAgents, 'utf8');
|
|
404
|
+
const fixedIdentity = /(?:identity|name)[\s\S]{0,100}(?:always|only|must)/i.test(globalText);
|
|
405
|
+
process.stdout.write(` Identity policy ${fixedIdentity ? 'host rule detected; review for Triad role conflicts' : 'no fixed host rule detected'}\n`);
|
|
406
|
+
}
|
|
273
407
|
if (options.hookConfig && adapter.lifecycle) {
|
|
274
408
|
process.stdout.write(` Hook config declared; run runtime capability detection for detailed status\n`);
|
|
275
409
|
}
|
|
@@ -302,6 +436,7 @@ try {
|
|
|
302
436
|
const options = parseArgs(process.argv.slice(2));
|
|
303
437
|
if (options.command === 'init') await init(options);
|
|
304
438
|
else if (options.command === 'doctor') await doctor(options);
|
|
439
|
+
else if (options.command === 'upgrade') await upgrade(options);
|
|
305
440
|
else if (!options.command) await interactiveInit();
|
|
306
441
|
else if (options.command === '--help' || options.command === '-h') usage(0);
|
|
307
442
|
else throw new Error(`Unknown command: ${options.command}`);
|
|
@@ -4,7 +4,11 @@ Install with `npx triad-plus init --host codex --control <path> --global`.
|
|
|
4
4
|
Open the control workspace and run `/prompts:triad <absolute-prd-path>`.
|
|
5
5
|
|
|
6
6
|
Codex uses role profiles for the Orchestrator, Developer, Reviewer, and optional
|
|
7
|
-
Evaluator+.
|
|
8
|
-
`SubagentStop`
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
Evaluator+. Verification uses explicit dispatch by default, even when a complete,
|
|
8
|
+
version-compatible async `SubagentStop` hook is installed. This keeps the normal
|
|
9
|
+
unattended path on the directly auditable verifier route.
|
|
10
|
+
|
|
11
|
+
The async hook remains available as an experimental opt-in by setting
|
|
12
|
+
`requested_mode=async_hook` in capability detection. Hook output is evidence
|
|
13
|
+
only; it never changes Triad state by itself. If the requested experimental hook
|
|
14
|
+
is unavailable, capability detection fails safe to explicit dispatch.
|
package/docs/compatibility.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| Runtime | Install | Orchestrator | Developer | Reviewer | Evaluator+ | Verification dispatch | Hook support |
|
|
4
4
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
5
|
-
| Codex | Yes | Yes | Yes | Yes | Yes | Explicit
|
|
5
|
+
| Codex | Yes | Yes | Yes | Yes | Yes | Explicit dispatch by default; experimental async `SubagentStop` opt-in | Optional async `SubagentStop` |
|
|
6
6
|
| Claude Code | Yes | Yes | Yes | Yes | Yes | Explicit fallback; hook when validated | Optional `SubagentStop` |
|
|
7
7
|
| OpenCode | Yes | Yes | Yes | Yes | Yes | Explicit dispatch | No adapter hook |
|
|
8
8
|
| Antigravity | Yes | Yes | Yes | Yes | Yes | Explicit dispatch | No adapter hook |
|
package/docs/configuration.md
CHANGED
|
@@ -24,8 +24,9 @@ When enabled, Evaluator+ is automatically dispatched by the Orchestrator after
|
|
|
24
24
|
Triad reaches Reviewer approval. It receives a fresh post-run packet and cannot
|
|
25
25
|
change the closed Triad result. Set `enabled` to `false` to disable this default.
|
|
26
26
|
|
|
27
|
-
The runtime adapter is selected per installed control workspace (`--host`).
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
The runtime adapter is selected once per installed control workspace (`--host`).
|
|
28
|
+
All roles in that run use that adapter; Triad+ does not orchestrate roles across
|
|
29
|
+
different hosts. The team file records role-level models and effort, but an
|
|
30
|
+
adapter writes those into host-native profiles only where the selected host
|
|
31
|
+
supports that facility. A blank model means the host default. Never put tokens,
|
|
32
|
+
API keys, or private deployment data in this file.
|
package/docs/npx-installation.md
CHANGED
|
@@ -18,6 +18,23 @@ npx triad-plus init --host codex --control /path/to/project-control --global
|
|
|
18
18
|
npx triad-plus doctor --host codex --control /path/to/project-control
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## Upgrade an existing control workspace
|
|
22
|
+
|
|
23
|
+
`upgrade` refreshes only Triad-managed runtime, skill, adapter, and optional
|
|
24
|
+
host-entry assets. It never changes `team.json`, `.loop/`, PRD files, evidence,
|
|
25
|
+
or product repositories. The default is a dry run:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx triad-plus upgrade --host codex --control /path/to/project-control --global
|
|
29
|
+
npx triad-plus upgrade --host codex --control /path/to/project-control --global --apply
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Before replacing a managed asset, the applied upgrade saves its prior copy under
|
|
33
|
+
`.triad-plus/backups/`. Triad+ also maintains a clearly marked role-run block in
|
|
34
|
+
the control workspace `AGENTS.md`; existing instructions are preserved. Review
|
|
35
|
+
`doctor` output when host-level instructions impose a fixed identity, because a
|
|
36
|
+
higher-priority host policy can prevent the configured Orchestrator identity.
|
|
37
|
+
|
|
21
38
|
Supported hosts: `codex`, `opencode`, `claude-code`, `antigravity`, `hermes`.
|
|
22
39
|
Use `--global` to install a host-level entry point where desired. The installer
|
|
23
40
|
refuses overwrites. If the control path is recognizably a product Git repository,
|
|
@@ -26,10 +26,18 @@ PRD/card e definizione dei gate. “I test passano” detto dall’agente è una
|
|
|
26
26
|
l’output del verifier è evidence derivata dall’ambiente. Evidence fallita o
|
|
27
27
|
invalida non può essere trattata come pass.
|
|
28
28
|
|
|
29
|
+
Per Codex la modalità `auto` usa intenzionalmente il dispatch esplicito. Il
|
|
30
|
+
percorso asincrono `SubagentStop` resta disponibile solo come opt-in
|
|
31
|
+
sperimentale: gli hook producono evidence, mentre l’Orchestrator mantiene
|
|
32
|
+
l’autorità sull’avanzamento del loop.
|
|
33
|
+
|
|
29
34
|
Il Reviewer riceve card, diff, report Developer, rilievi precedenti ed evidence.
|
|
30
35
|
`rework` torna al Developer con una correzione delimitata; `blocked` richiede
|
|
31
36
|
all’Orchestrator di escalare la decisione. Le push normali possono essere autonome
|
|
32
|
-
dopo i goal dichiarati.
|
|
37
|
+
dopo i goal dichiarati. La consegna formale al proprietario è un gate distinto:
|
|
38
|
+
registra push finale, eventuale valutazione, handoff, stato finale della run e
|
|
39
|
+
prova pratica prima di dichiarare il progetto consegnato. Avvio e stop della demo
|
|
40
|
+
restano del proprietario.
|
|
33
41
|
|
|
34
42
|
## Evaluator+
|
|
35
43
|
|
|
@@ -48,3 +56,8 @@ eventuali report Evaluator+ e handoff in un workspace di controllo separato. Non
|
|
|
48
56
|
inserire token o segreti. Gli hook sono un’ottimizzazione, non autorità: il
|
|
49
57
|
dispatch esplicito della verification resta sempre disponibile. Vedi la
|
|
50
58
|
[matrice di compatibilità](compatibility.md).
|
|
59
|
+
|
|
60
|
+
Per ogni demo configurata, registra nel progetto e nell’handoff comando, URL
|
|
61
|
+
locale, modalità di accesso remoto e URL remoto. `localhost` è solo locale: non
|
|
62
|
+
va indicato a chi prova da remoto. Avvia il servizio soltanto su richiesta del
|
|
63
|
+
proprietario e verifica l’URL remoto dichiarato prima di comunicarlo.
|
package/docs/operating-guide.md
CHANGED
|
@@ -27,10 +27,18 @@ hash. A developer saying tests pass is a claim; verifier output is
|
|
|
27
27
|
environment-derived evidence. Failed or invalid evidence cannot be treated as a
|
|
28
28
|
pass.
|
|
29
29
|
|
|
30
|
+
Codex is intentionally an exception to automatic hook selection: its `auto`
|
|
31
|
+
mode uses explicit dispatch. The async `SubagentStop` route remains an
|
|
32
|
+
experimental opt-in; this does not change the rule that hooks produce evidence
|
|
33
|
+
and the Orchestrator governs progress.
|
|
34
|
+
|
|
30
35
|
The Reviewer sees the card, diff, Developer report, previous findings, and
|
|
31
36
|
verifier evidence. `rework` returns a bounded finding to Developer; `blocked`
|
|
32
37
|
asks the Orchestrator to escalate the stated decision. Normal pushes may happen
|
|
33
|
-
autonomously once declared goals pass.
|
|
38
|
+
autonomously once declared goals pass. A final owner delivery is a separate
|
|
39
|
+
closure gate: it records the final push, optional evaluation, handoff, final run
|
|
40
|
+
record, and practical test before the project is called delivered. Demo start and
|
|
41
|
+
stop remain owner-controlled.
|
|
34
42
|
|
|
35
43
|
## Evaluator+
|
|
36
44
|
|
|
@@ -48,3 +56,8 @@ immutable PRD, cards, assignments, evidence, review reports, optional Evaluator+
|
|
|
48
56
|
reports, and handoff in a separate project-control workspace. Never place tokens
|
|
49
57
|
or secrets there. Hooks are an optimization, not authority; explicit verification
|
|
50
58
|
is always the fallback. See the [compatibility matrix](compatibility.md).
|
|
59
|
+
|
|
60
|
+
For a configured demo service, record its command, local URL, remote-access mode,
|
|
61
|
+
and remote URL in the project and handoff. `localhost` is local-only; do not give
|
|
62
|
+
it to a remote tester as a reachable endpoint. Start the service only on the
|
|
63
|
+
owner's request and validate any declared remote URL before presenting it.
|
package/docs/runtimes.md
CHANGED
|
@@ -5,7 +5,7 @@ host-specific entry points and configuration behavior.
|
|
|
5
5
|
|
|
6
6
|
| Runtime | Prerequisite | Entry point | Verification | Hook limitation |
|
|
7
7
|
| --- | --- | --- | --- | --- |
|
|
8
|
-
| Codex | Codex CLI | `/prompts:triad` | Explicit dispatch; async hook
|
|
8
|
+
| Codex | Codex CLI | `/prompts:triad` | Explicit dispatch by default; experimental async hook opt-in | Hook is optional, requires trusted configuration, and is not selected by `auto`. |
|
|
9
9
|
| Claude Code | Claude Code CLI | `/triad` | Explicit dispatch; hook when validated | Hook is optional and requires trusted configuration. |
|
|
10
10
|
| OpenCode | OpenCode | `/triad` | Explicit dispatch | No Triad lifecycle hook. |
|
|
11
11
|
| Antigravity | Antigravity | `/triad` | Explicit dispatch | No Triad lifecycle hook. |
|
package/docs/verification.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Verification and evidence
|
|
2
2
|
|
|
3
|
+
## Repository skill bindings
|
|
4
|
+
|
|
5
|
+
When a target repository declares a skill router, the Orchestrator binds the
|
|
6
|
+
router, routed task skills, and completion skill to the Developer assignment as
|
|
7
|
+
worktree-relative paths and SHA-256 values. `triad-verify` checks that every
|
|
8
|
+
declared skill exists inside the declared worktree and still matches its bound
|
|
9
|
+
hash, then records the result in environment-derived verification evidence.
|
|
10
|
+
|
|
11
|
+
This proves the exact repository skill policy available to the attempt; it does
|
|
12
|
+
not claim to observe an LLM's private reasoning. Developer, Reviewer, and
|
|
13
|
+
Evaluator+ must separately report their use of the same bound skills, so a
|
|
14
|
+
missing or inconsistent attestation is visible to the Orchestrator.
|
|
15
|
+
|
|
3
16
|
An agent-reported claim is not the same as verification evidence. A Developer can
|
|
4
17
|
report the commands it ran; `triad-verify` independently observes declared
|
|
5
18
|
required `control-plane` gates and writes atomic evidence.
|
|
@@ -15,3 +28,15 @@ it does not itself approve, rework, or transition a run.
|
|
|
15
28
|
|
|
16
29
|
Evidence files and logs are diagnostics. Users normally need only the
|
|
17
30
|
Orchestrator's summary and the Reviewer verdict.
|
|
31
|
+
|
|
32
|
+
## Codex dispatch modes
|
|
33
|
+
|
|
34
|
+
Codex uses `explicit_dispatch` by default, including when a compatible async
|
|
35
|
+
`SubagentStop` hook is installed. This is the directly auditable path used for
|
|
36
|
+
normal unattended progress. The async hook remains supported as an experimental
|
|
37
|
+
opt-in with `requested_mode=async_hook`; it is selected only when the requested
|
|
38
|
+
hook is valid and available. If that requested hook is unavailable, capability
|
|
39
|
+
detection fails safe to `explicit_dispatch` and records the reason.
|
|
40
|
+
|
|
41
|
+
Hooks may produce evidence; the Orchestrator governs progress. The hook never
|
|
42
|
+
dispatches a Reviewer, selects a card, reopens a run, or performs repair.
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Triad Codex hook adapter
|
|
2
2
|
|
|
3
|
-
This adapter is for Codex CLI 0.148.0 or newer.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
This adapter is for Codex CLI 0.148.0 or newer. Its normal verification route is
|
|
4
|
+
explicit dispatch by the Orchestrator. It also provides an experimental,
|
|
5
|
+
opt-in asynchronous command hook that can dispatch the Node verification runner
|
|
6
|
+
when a subagent whose type is exactly `triad_developer` stops. The hook writes
|
|
7
|
+
immutable evidence and has no authority to approve, reject, or transition a
|
|
8
|
+
feature card.
|
|
7
9
|
|
|
8
10
|
## Install only after verifying the local Codex schema
|
|
9
11
|
|
|
@@ -44,18 +46,24 @@ The JSON payload needs an `agent_id` matching an active assignment under
|
|
|
44
46
|
assignment and emits only structured evidence. The orchestrator still validates
|
|
45
47
|
the evidence against the active candidate before changing state.
|
|
46
48
|
|
|
47
|
-
##
|
|
49
|
+
## Default and experimental routes
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
Keep `project.control_plane.dispatch_mode: auto` for the normal Codex path and
|
|
52
|
+
run:
|
|
51
53
|
|
|
52
54
|
```bash
|
|
53
55
|
node /absolute/path/to/triad-plus-engineering-loop/runtime/triad-runtime-capabilities.mjs \
|
|
54
56
|
--hook-config /absolute/path/to/installed-triad-hooks.json
|
|
55
57
|
```
|
|
56
58
|
|
|
57
|
-
Store the JSON output at `.loop/runtime/capabilities.json`.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
Store the JSON output at `.loop/runtime/capabilities.json`. For Codex, `auto`
|
|
60
|
+
always selects `explicit_dispatch`, including when the async hook is detected as
|
|
61
|
+
available. This is the default because the live Codex collaboration path did
|
|
62
|
+
not reliably emit `SubagentStop` during the validation runs.
|
|
63
|
+
|
|
64
|
+
To deliberately test the experimental route, pass
|
|
65
|
+
`--requested-mode async_hook`. A compatible Codex version and a valid trusted
|
|
66
|
+
hook configuration select `async_hook`; an unavailable or invalid hook falls
|
|
67
|
+
back to `explicit_dispatch` with a diagnostic reason. The Orchestrator follows
|
|
68
|
+
the snapshot and re-detects only before a new assignment or on resume after
|
|
69
|
+
runtime configuration changes.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"purpose": "Triad external verification dispatch only; this hook never changes card state.",
|
|
2
|
+
"description": "Triad external verification dispatch only; requires Codex CLI 0.148.0 or newer and never changes card state.",
|
|
4
3
|
"hooks": {
|
|
5
4
|
"SubagentStop": [
|
|
6
5
|
{
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"schema_version": 1,
|
|
4
4
|
"id": "codex",
|
|
5
5
|
"binary": "codex",
|
|
6
|
+
"verification": { "default_mode": "explicit_dispatch", "experimental_modes": ["async_hook"] },
|
|
6
7
|
"lifecycle": { "kind": "SubagentStop", "agent_type": "triad_developer", "requires_async": true, "minimum_version": "0.148.0" }
|
|
7
8
|
},
|
|
8
9
|
"opencode": { "schema_version": 1, "id": "opencode", "binary": "opencode", "lifecycle": null },
|
|
@@ -104,12 +104,40 @@ const hookAvailable = lifecycleAvailable && hook.configured;
|
|
|
104
104
|
const explicitAvailable = Boolean(hostVersion && nodeVersion);
|
|
105
105
|
let selectedMode = "unavailable";
|
|
106
106
|
let reason = "verification_runtime_unavailable";
|
|
107
|
-
|
|
107
|
+
const defaultMode = adapter.verification?.default_mode ?? null;
|
|
108
|
+
const experimentalModes = new Set(adapter.verification?.experimental_modes ?? []);
|
|
109
|
+
const experimentalHookRequested = requestedMode === "async_hook" && experimentalModes.has("async_hook");
|
|
110
|
+
|
|
111
|
+
// An adapter may declare a safer automatic mode. This keeps runtime policy in
|
|
112
|
+
// adapter metadata instead of adding host-specific branches to the Core.
|
|
113
|
+
if (requestedMode === "auto" && defaultMode) {
|
|
114
|
+
if (defaultMode === "explicit_dispatch" && explicitAvailable) {
|
|
115
|
+
selectedMode = "explicit_dispatch";
|
|
116
|
+
reason = `${adapter.id}_default_explicit_dispatch`;
|
|
117
|
+
} else if (defaultMode === "async_hook" && hookAvailable) {
|
|
118
|
+
selectedMode = "async_hook";
|
|
119
|
+
reason = `${adapter.id}_default_async_hook`;
|
|
120
|
+
} else if (defaultMode === "hook_dispatch" && hookAvailable) {
|
|
121
|
+
selectedMode = "hook_dispatch";
|
|
122
|
+
reason = `${adapter.id}_default_hook_dispatch`;
|
|
123
|
+
} else if (explicitAvailable) {
|
|
124
|
+
selectedMode = "explicit_dispatch";
|
|
125
|
+
reason = `${adapter.id}_default_${defaultMode}_unavailable_using_explicit_dispatch`;
|
|
126
|
+
}
|
|
127
|
+
} else if (experimentalHookRequested && hookAvailable) {
|
|
128
|
+
selectedMode = "async_hook";
|
|
129
|
+
reason = "experimental_async_hook_requested";
|
|
130
|
+
} else if (experimentalHookRequested && explicitAvailable) {
|
|
131
|
+
selectedMode = "explicit_dispatch";
|
|
132
|
+
reason = "requested_async_hook_unavailable_using_explicit_dispatch";
|
|
133
|
+
} else if (hookAvailable && (requestedMode === "auto" || requestedMode === "async_hook" || requestedMode === "hook_dispatch") && (!lifecycle?.requires_async || requestedMode === "async_hook")) {
|
|
108
134
|
selectedMode = lifecycle.requires_async ? "async_hook" : "hook_dispatch";
|
|
109
135
|
reason = lifecycle.requires_async ? "validated_async_hook_available" : "validated_hook_dispatch_available";
|
|
110
136
|
} else if (explicitAvailable) {
|
|
111
137
|
selectedMode = "explicit_dispatch";
|
|
112
|
-
reason =
|
|
138
|
+
reason = requestedMode === "explicit_dispatch"
|
|
139
|
+
? "explicit_dispatch_requested"
|
|
140
|
+
: ["async_hook", "hook_dispatch"].includes(requestedMode)
|
|
113
141
|
? "requested_hook_unavailable_using_explicit_dispatch"
|
|
114
142
|
: "explicit_dispatch_available";
|
|
115
143
|
}
|
package/runtime/triad-verify.mjs
CHANGED
|
@@ -27,6 +27,26 @@ async function sha256File(value) {
|
|
|
27
27
|
return sha256(await readFile(value));
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
async function validateRepositorySkills(required, worktree) {
|
|
31
|
+
if (required === undefined) return { declared: false, skills: [] };
|
|
32
|
+
if (!Array.isArray(required) || required.length === 0) throw new Error("repository skill binding must declare at least one skill");
|
|
33
|
+
const root = await realpath(worktree);
|
|
34
|
+
const skills = [];
|
|
35
|
+
for (const item of required) {
|
|
36
|
+
if (!item || typeof item.path !== "string" || typeof item.sha256 !== "string") {
|
|
37
|
+
throw new Error("repository skill binding entries require path and sha256");
|
|
38
|
+
}
|
|
39
|
+
const candidate = path.resolve(root, item.path);
|
|
40
|
+
if (!candidate.startsWith(`${root}${path.sep}`)) throw new Error("repository skill path escapes worktree");
|
|
41
|
+
try { await access(candidate); }
|
|
42
|
+
catch { throw new Error(`repository skill missing: ${item.path}`); }
|
|
43
|
+
const actual = await sha256File(candidate);
|
|
44
|
+
if (actual !== item.sha256) throw new Error(`repository skill hash mismatch: ${item.path}`);
|
|
45
|
+
skills.push({ path: item.path, sha256: actual });
|
|
46
|
+
}
|
|
47
|
+
return { declared: true, skills };
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
function triggerFrom(payload) {
|
|
31
51
|
return {
|
|
32
52
|
event: payload.event ?? payload.hook_event_name ?? "manual",
|
|
@@ -100,6 +120,7 @@ async function main() {
|
|
|
100
120
|
await access(cardPath);
|
|
101
121
|
if ((await sha256File(prdPath)) !== assignment.expected_prd_sha256) throw new Error("PRD baseline hash mismatch");
|
|
102
122
|
if ((await sha256File(cardPath)) !== assignment.expected_card_sha256) throw new Error("feature card hash mismatch");
|
|
123
|
+
const repositorySkills = await validateRepositorySkills(assignment.required_repository_skills, worktree);
|
|
103
124
|
const before = await calculateCandidateFingerprint(worktree);
|
|
104
125
|
const branch = await worktreeBranch(worktree);
|
|
105
126
|
if (assignment.expected_branch && assignment.expected_branch !== branch) throw new Error("worktree branch does not match assignment");
|
|
@@ -128,6 +149,7 @@ async function main() {
|
|
|
128
149
|
candidate_fingerprint: before.value,
|
|
129
150
|
branch,
|
|
130
151
|
},
|
|
152
|
+
repository_skills: repositorySkills,
|
|
131
153
|
gates,
|
|
132
154
|
required_gates_passed: requiredGatesPassed,
|
|
133
155
|
status: candidateChanged ? "invalidated" : requiredGatesPassed ? "pass" : "fail",
|
|
@@ -26,7 +26,9 @@ conditions, runnable quality gates, practical-test need, and integration need.
|
|
|
26
26
|
6. Create `.loop/runtime/assignments/` and record the active adapter metadata in
|
|
27
27
|
`.loop/runtime/capabilities.json` by running
|
|
28
28
|
`.triad-runtime/triad-runtime-capabilities.mjs --adapter
|
|
29
|
-
.triad-runtime/adapter.json
|
|
29
|
+
.triad-runtime/adapter.json --requested-mode <project.control_plane.dispatch_mode>`.
|
|
30
|
+
Use `auto` when the project does not declare a mode. For Codex, `auto` is
|
|
31
|
+
explicit dispatch; `async_hook` is an experimental opt-in only.
|
|
30
32
|
7. Show the full card division before delivery work. Continue unless the owner
|
|
31
33
|
changes it or an escalation condition exists.
|
|
32
34
|
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
- Allowed dependencies: `<names or none>`
|
|
25
25
|
- Test fixtures/examples: `<paths>`
|
|
26
26
|
|
|
27
|
+
## Repository skill binding
|
|
28
|
+
|
|
29
|
+
- Repository policy/router: `<path to the repository router SKILL.md or not applicable>`
|
|
30
|
+
- Required skills: `<router, routed skills, and completion skill>`
|
|
31
|
+
- Assignment evidence: `<relative SKILL.md paths and SHA-256 values>`
|
|
32
|
+
|
|
27
33
|
## Integration, practical test, and risk
|
|
28
34
|
|
|
29
35
|
- Local-worktree setup: `<not applicable or command>`
|
|
@@ -50,6 +50,14 @@ State: `not_applicable | ready_to_start | active | closed | start_failed`
|
|
|
50
50
|
- Start: `<only on owner request>`
|
|
51
51
|
- Stop: `<only on owner completion>`
|
|
52
52
|
- Closure evidence: `<timestamp, process result, port-release check>`
|
|
53
|
+
- Remote access contract: `<none | declared remote URL and access mode>`.
|
|
54
|
+
`localhost` is local-only and must not be presented as a remote endpoint.
|
|
55
|
+
|
|
56
|
+
## Delivery closure record
|
|
57
|
+
|
|
58
|
+
- Run record updated: `<path and final decision>`
|
|
59
|
+
- Final branch/commit map recorded: `<yes/no; reference>`
|
|
60
|
+
- Final delivery message issued: `<yes/no; timestamp or reference>`
|
|
53
61
|
|
|
54
62
|
## Practical test and follow-up
|
|
55
63
|
|
|
@@ -2,6 +2,14 @@ version: 2
|
|
|
2
2
|
updated_at: null
|
|
3
3
|
project_decision: not_started
|
|
4
4
|
|
|
5
|
+
delivery:
|
|
6
|
+
status: not_delivered # not_delivered | delivered | delivery_blocked | delivered_without_demo
|
|
7
|
+
handoff: null
|
|
8
|
+
branches: []
|
|
9
|
+
evaluator_report: null
|
|
10
|
+
delivered_at: null
|
|
11
|
+
owner_message: null
|
|
12
|
+
|
|
5
13
|
feature_plan:
|
|
6
14
|
path: feature-plan.md
|
|
7
15
|
status: declared
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
"expected_prd_sha256": "REPLACE_ME_SHA256",
|
|
17
17
|
"expected_card_sha256": "REPLACE_ME_SHA256",
|
|
18
18
|
"expected_gates_sha256": "REPLACE_ME_SHA256",
|
|
19
|
+
"required_repository_skills": [
|
|
20
|
+
{
|
|
21
|
+
"path": ".agents/skills/router/SKILL.md",
|
|
22
|
+
"sha256": "REPLACE_ME_SHA256"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": ".agents/skills/agent-completion/SKILL.md",
|
|
26
|
+
"sha256": "REPLACE_ME_SHA256"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
19
29
|
"verification_run_id": "REPLACE_ME_UUID",
|
|
20
30
|
"evidence_directory": ".loop/evidence/REPLACE_ME_FEATURE_ID/attempt-001"
|
|
21
31
|
}
|
|
@@ -37,7 +37,17 @@ project:
|
|
|
37
37
|
enabled: false
|
|
38
38
|
start_policy: only_on_owner_request
|
|
39
39
|
stop_policy: only_on_owner_completion
|
|
40
|
+
# Declare a service only when the owner needs a practical demo. A remote
|
|
41
|
+
# URL is optional and must never be inferred from a localhost URL.
|
|
40
42
|
services: []
|
|
43
|
+
# Example service shape:
|
|
44
|
+
# - id: component-demo
|
|
45
|
+
# worktree: worktrees/product-repository
|
|
46
|
+
# start_command: npm run dev -- --host 0.0.0.0
|
|
47
|
+
# stop_command: <owner-completion procedure>
|
|
48
|
+
# local_url: http://127.0.0.1:6006
|
|
49
|
+
# remote_access: tailscale # none | tailscale | proxy | public
|
|
50
|
+
# remote_url: http://host.example:6006
|
|
41
51
|
|
|
42
52
|
integration:
|
|
43
53
|
enabled: false
|
|
@@ -10,6 +10,18 @@ project manifest, repository instructions, allowed surface, prior findings, and
|
|
|
10
10
|
gates. Verify the declared worktree and branch before editing. Do not change
|
|
11
11
|
scope, policy, assignments, queue, state, or evidence records.
|
|
12
12
|
|
|
13
|
+
At the beginning of every activation, read `.triad-plus/team.json` when it
|
|
14
|
+
exists. Your first report to the Orchestrator must identify you as its configured
|
|
15
|
+
`roles.developer.displayName` and the Triad+ Developer, then name the assigned
|
|
16
|
+
card. This is an attributed role-activation record, not an approval or a state
|
|
17
|
+
transition.
|
|
18
|
+
|
|
19
|
+
When the assignment declares `required_repository_skills`, read every bound
|
|
20
|
+
file from the declared worktree before editing. Include their relative paths and
|
|
21
|
+
SHA-256 values in the report as a **repository-skill attestation**. If a bound
|
|
22
|
+
skill is missing or its hash differs, stop and report the mismatch; do not
|
|
23
|
+
replace it with external context or a similarly named skill.
|
|
24
|
+
|
|
13
25
|
Implement the smallest complete change and focused tests. Run useful local
|
|
14
26
|
checks and measure declared criteria. Report changed files, tests, exact command
|
|
15
27
|
results, metrics, worktree/branch, risks, and blockers to the Orchestrator.
|
|
@@ -11,6 +11,18 @@ artifact/observation instructions, and current verifier evidence. Do not request
|
|
|
11
11
|
Developer reasoning, conversation history, prior Reviewer discussion, or attempt
|
|
12
12
|
history unless the owner explicitly requires it.
|
|
13
13
|
|
|
14
|
+
At the beginning of every activation, read `.triad-plus/team.json` when it
|
|
15
|
+
exists. Your first report must identify you as its configured
|
|
16
|
+
`roles.evaluator.displayName` and Evaluator+, then name the completed result
|
|
17
|
+
being assessed. This is an attributed post-run activation record; it does not
|
|
18
|
+
reopen or modify Triad.
|
|
19
|
+
|
|
20
|
+
When verifier evidence declares `repository_skills`, independently read the
|
|
21
|
+
listed bound files from the final worktree and include their paths and SHA-256
|
|
22
|
+
values in the evaluation report. A missing or mismatched binding makes the
|
|
23
|
+
evaluation `INDETERMINATE`; it never starts repair or changes the closed Triad
|
|
24
|
+
result.
|
|
25
|
+
|
|
14
26
|
Return a report conforming to `schemas/evaluator-plus-result.schema.json` and
|
|
15
27
|
store it separately under `artifacts/evaluator-plus/<evaluation-id>.json`:
|
|
16
28
|
|
|
@@ -18,12 +18,28 @@ intermediary or another Triad role to the owner. You may report delegated roles'
|
|
|
18
18
|
outputs, but never claim their identity. Technical role IDs and authority remain
|
|
19
19
|
unchanged.
|
|
20
20
|
|
|
21
|
+
If the current Triad+ invocation has not yet introduced the Orchestrator, its
|
|
22
|
+
first owner-facing message is a presentation, not a generic acknowledgement or
|
|
23
|
+
bootstrap report. Before any other owner-facing content, begin with a
|
|
24
|
+
first-person sentence that includes `<displayName>` and "Triad+ Orchestrator",
|
|
25
|
+
localized to the configured interaction language; then state whether the run is
|
|
26
|
+
new or resumed and what input was received. If the entry point already made that
|
|
27
|
+
presentation for this invocation, do not repeat it.
|
|
28
|
+
|
|
21
29
|
## Run one card
|
|
22
30
|
|
|
23
31
|
1. Verify the PRD hash, declared worktree/branch, repository instructions,
|
|
24
|
-
runnable gates, and capability snapshot.
|
|
32
|
+
runnable gates, and capability snapshot. The snapshot must reflect
|
|
33
|
+
`project.control_plane.dispatch_mode` as `requested_mode` (default `auto`).
|
|
34
|
+
When repository instructions define
|
|
35
|
+
a skill router, read it, select the router, routed skills, and completion
|
|
36
|
+
skill required by the card, and bind their worktree-relative paths plus
|
|
37
|
+
SHA-256 values in `required_repository_skills` on the Developer assignment.
|
|
38
|
+
Do not assign the card if this binding cannot be made.
|
|
25
39
|
2. Choose one dependency-approved `ready` card, mark it `in_progress`, append an
|
|
26
|
-
attempt, and create an active assignment before delegating.
|
|
40
|
+
attempt, and create an active assignment before delegating. Before each
|
|
41
|
+
delegation, publish an owner-facing activation notice that attributes the
|
|
42
|
+
configured display name, technical role, and card/attempt to that role.
|
|
27
43
|
3. Give the Developer the card, relevant PRD excerpt, allowed surface, gates,
|
|
28
44
|
risks, and prior findings. Treat its command results and report as
|
|
29
45
|
**agent-reported claims**, never as control-plane gate truth.
|
|
@@ -32,17 +48,37 @@ unchanged.
|
|
|
32
48
|
the verifier explicitly. Accept only current evidence whose assignment ID,
|
|
33
49
|
feature, attempt, PRD/card/gate hashes, expected branch, and candidate
|
|
34
50
|
fingerprint match the active candidate.
|
|
51
|
+
A Developer report is never a human-input wait condition: immediately wait
|
|
52
|
+
for the configured hook evidence or invoke the verifier, then immediately
|
|
53
|
+
dispatch the Reviewer on a verifier pass. Do not ask the owner to continue
|
|
54
|
+
between Developer completion, verification, and review.
|
|
35
55
|
5. A passing verifier result is **environment-derived evidence**. Move only then
|
|
36
56
|
to `in_review`. Missing, stale, failed, timed-out, invalid-context, or
|
|
37
57
|
invalidated evidence never advances the card.
|
|
38
58
|
6. Give the Reviewer the card, diff, developer report, verifier evidence, prior
|
|
39
59
|
attempts, and risks. Record its recommendation:
|
|
40
|
-
- `approved`: verify scope/evidence, commit the card locally,
|
|
41
|
-
|
|
60
|
+
- `approved`: verify scope/evidence, commit the card locally, promote every
|
|
61
|
+
dependency-satisfied draft card to `ready`, then immediately select and
|
|
62
|
+
assign the next ready card;
|
|
42
63
|
- `rework`: preserve findings and return the card to `in_progress` for a new
|
|
43
64
|
attempt and new verification;
|
|
44
65
|
- `blocked`: record the exact external condition or owner decision required.
|
|
45
66
|
7. Stop automatic retry at the declared limit and escalate the decision needed.
|
|
67
|
+
Do not ask the owner to continue, pause between cards, or finish the run
|
|
68
|
+
while a dependency-satisfied card remains `ready`; stop only for a declared
|
|
69
|
+
escalation, a blocked card, or when every required card is terminal.
|
|
70
|
+
|
|
71
|
+
## Unattended continuation rule
|
|
72
|
+
|
|
73
|
+
The normal chain is unattended: Developer completion → verification → Reviewer
|
|
74
|
+
→ rework or approval → next dependency-satisfied card. Do not stop for an
|
|
75
|
+
acknowledgement, progress update, or agent-reported claim. The only valid human
|
|
76
|
+
wait conditions are an escalation named by the policy, a `blocked` verdict, an
|
|
77
|
+
unrecoverable runtime error, or an explicit owner pause.
|
|
78
|
+
|
|
79
|
+
An owner-facing activation or progress update is informational output, never an
|
|
80
|
+
implicit pause. After sending it, continue the recorded next action without
|
|
81
|
+
waiting for a reply unless one of the valid human wait conditions applies.
|
|
46
82
|
|
|
47
83
|
## Authority and delivery
|
|
48
84
|
|
|
@@ -55,8 +91,30 @@ review, and restoration of normal roles.
|
|
|
55
91
|
After all required cards and project gates pass, commit each approved card if
|
|
56
92
|
needed and normally push declared branches. Never force-push, create/update a
|
|
57
93
|
pull request, publish, release, or start/stop a demo without owner direction.
|
|
58
|
-
|
|
59
|
-
|
|
94
|
+
|
|
95
|
+
## Delivery closure gate
|
|
96
|
+
|
|
97
|
+
`approved` is not an owner delivery. Do not declare a project delivered, closed,
|
|
98
|
+
or ready for owner testing until this gate has completed:
|
|
99
|
+
|
|
100
|
+
1. Record the final commit and normal-push evidence for every declared branch.
|
|
101
|
+
2. If configured, complete the isolated Evaluator+ dispatch and record its
|
|
102
|
+
report. Its verdict still cannot reopen Triad.
|
|
103
|
+
3. Write the final handoff from the handoff template with cards, commits,
|
|
104
|
+
pushes, verifier evidence, reviewer decisions, risks, exceptions, and
|
|
105
|
+
practical-test instructions.
|
|
106
|
+
4. Update the control-workspace run record with the delivery decision, handoff
|
|
107
|
+
reference, final branch/commit map, and optional Evaluator+ reference.
|
|
108
|
+
5. Give the owner one final delivery message that links the handoff, names the
|
|
109
|
+
practical test, and states the demo status.
|
|
110
|
+
|
|
111
|
+
For every configured demo service, copy its declared command, local URL, remote
|
|
112
|
+
URL, and remote-access mode into the handoff. Never present `localhost` as a
|
|
113
|
+
remote endpoint. If the service is loopback-only or has no configured remote
|
|
114
|
+
URL, explicitly say that remote testing is unavailable. On an owner request to
|
|
115
|
+
start a demo, validate the declared local URL and, when a remote URL is
|
|
116
|
+
configured, validate that endpoint before reporting it. Keep the service running
|
|
117
|
+
until the owner explicitly ends the demo.
|
|
60
118
|
|
|
61
119
|
Evaluator+ is outside the Triad production run. After final Triad approval,
|
|
62
120
|
read `roles.evaluator.enabled` from `.triad-plus/team.json`: when it is `true`,
|
|
@@ -66,3 +124,5 @@ false or omitted, finish without evaluation. Record the report separately;
|
|
|
66
124
|
`PASS`, `FAIL`, and `INDETERMINATE` cannot reopen, rework, assign Developer work,
|
|
67
125
|
or change the already closed Triad run. A per-run `--evaluator` or
|
|
68
126
|
`--no-evaluator` request may override the configuration when the host exposes it.
|
|
127
|
+
Before dispatching it, publish the corresponding attributed Evaluator+
|
|
128
|
+
activation notice.
|
|
@@ -11,6 +11,19 @@ instructions, actual diff/worktree, and verifier evidence. You may know prior
|
|
|
11
11
|
attempts and correction history; independence means assess the artifact and
|
|
12
12
|
evidence yourself, not blindness.
|
|
13
13
|
|
|
14
|
+
At the beginning of every activation, read `.triad-plus/team.json` when it
|
|
15
|
+
exists. Your first report to the Orchestrator must identify you as its configured
|
|
16
|
+
`roles.reviewer.displayName` and the Triad+ Reviewer, then name the feature and
|
|
17
|
+
attempt under review. This is an attributed role-activation record, not an
|
|
18
|
+
approval or a state transition.
|
|
19
|
+
|
|
20
|
+
When the assignment declares `required_repository_skills`, independently read
|
|
21
|
+
the same bound files from the declared worktree and compare their paths and
|
|
22
|
+
SHA-256 values with the Developer attestation and verifier evidence. A missing,
|
|
23
|
+
mismatched, or unreported required repository skill is a `blocked` result until
|
|
24
|
+
the Orchestrator creates a valid assignment; external context never substitutes
|
|
25
|
+
for the repository skill policy.
|
|
26
|
+
|
|
14
27
|
Confirm scope, dependencies, candidate fingerprint, assignment ID, feature,
|
|
15
28
|
attempt, expected branch, and PRD/card/gate hashes. Reject stale, missing,
|
|
16
29
|
invalidated, or failed verifier evidence. Independently rerun enough required
|