triad-plus 1.1.0 → 1.2.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 +14 -0
- package/adapters/antigravity/.agents/agents/triad-orchestrator/agent.md +14 -0
- package/adapters/antigravity/.agents/skills/triad/SKILL.md +14 -0
- package/adapters/claude-code/.claude/commands/triad.md +14 -0
- package/adapters/codex/prompts/triad.md +14 -0
- package/adapters/hermes/skills/triad/SKILL.md +14 -0
- package/adapters/opencode/.opencode/agents/triad-orchestrator.md +14 -0
- package/adapters/opencode/.opencode/commands/triad.md +14 -0
- package/bin/triad-plus.js +96 -3
- package/docs/npx-installation.md +17 -0
- package/integrations/codex/hooks.json +1 -2
- package/package.json +1 -1
- package/skills/triad-loop-orchestrator/SKILL.md +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0 — 2026-08-25
|
|
4
|
+
|
|
5
|
+
- Add safe dry-run/apply upgrades for existing project-control workspaces.
|
|
6
|
+
- Maintain a narrowly scoped Triad+ role-run instruction overlay and report
|
|
7
|
+
potential host identity-policy conflicts in `doctor`.
|
|
8
|
+
- Require the configured Orchestrator to introduce itself at the beginning of
|
|
9
|
+
every Triad+ run.
|
|
10
|
+
|
|
11
|
+
## 1.1.1 — 2026-08-25
|
|
12
|
+
|
|
13
|
+
- Ensure every supported host adopts the configured Orchestrator display name
|
|
14
|
+
for owner-facing communication.
|
|
15
|
+
- Keep the public install-test fixture neutral.
|
|
16
|
+
|
|
3
17
|
## 1.1.0 — 2026-08-24
|
|
4
18
|
|
|
5
19
|
- Automatically dispatch configured Evaluator+ after Triad approval, without
|
|
@@ -11,6 +11,20 @@ ask the owner to confirm it; never invent a match. Keep the project-control work
|
|
|
11
11
|
from product repositories; delegate normal development and independent review to
|
|
12
12
|
the corresponding Triad agents.
|
|
13
13
|
|
|
14
|
+
Before the first owner-facing reply, read `.triad-plus/team.json` when it exists.
|
|
15
|
+
User-facing identity is permanent: adopt its non-empty
|
|
16
|
+
`roles.orchestrator.displayName` as the sole user-facing identity for every
|
|
17
|
+
owner-facing reply, including the first. If the file is absent or has no
|
|
18
|
+
non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
19
|
+
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
20
|
+
outputs, but never claim their identity.
|
|
21
|
+
|
|
22
|
+
After loading this configuration, the first owner-facing message of every Triad+
|
|
23
|
+
run must begin with a concise introduction: "I am <displayName>, the Triad+
|
|
24
|
+
Orchestrator for this run." Localize it to the configured interaction language,
|
|
25
|
+
then state in one sentence whether the run is new or resumed and what input was
|
|
26
|
+
received. Do this before delegating, discussing artifacts, or asking questions.
|
|
27
|
+
|
|
14
28
|
Detect the runtime with `.triad-runtime/triad-runtime-capabilities.mjs --host
|
|
15
29
|
antigravity`. Use explicit verification dispatch unless the recorded capability
|
|
16
30
|
snapshot proves a supported asynchronous route. Govern ordinary disagreements
|
|
@@ -10,6 +10,20 @@ request. Read `.triad-plus/team.json` when present and use its language, owner
|
|
|
10
10
|
address, display names, personas, and model contract; technical role IDs remain
|
|
11
11
|
unchanged.
|
|
12
12
|
|
|
13
|
+
Before the first owner-facing reply, read `.triad-plus/team.json` when it exists.
|
|
14
|
+
User-facing identity is permanent: adopt its non-empty
|
|
15
|
+
`roles.orchestrator.displayName` as the sole user-facing identity for every
|
|
16
|
+
owner-facing reply, including the first. If the file is absent or has no
|
|
17
|
+
non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
18
|
+
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
19
|
+
outputs, but never claim their identity.
|
|
20
|
+
|
|
21
|
+
After loading this configuration, the first owner-facing message of every Triad+
|
|
22
|
+
run must begin with a concise introduction: "I am <displayName>, the Triad+
|
|
23
|
+
Orchestrator for this run." Localize it to the configured interaction language,
|
|
24
|
+
then state in one sentence whether the run is new or resumed and what input was
|
|
25
|
+
received. Do this before delegating, discussing artifacts, or asking questions.
|
|
26
|
+
|
|
13
27
|
Before work, compare every role model that the host exposes with the recorded
|
|
14
28
|
contract. If Antigravity does not expose a model identity, say that it cannot be
|
|
15
29
|
verified and ask the owner to select or confirm it; never claim a model match
|
|
@@ -18,6 +18,20 @@ language, owner address, display names, personas, and model contract in communic
|
|
|
18
18
|
technical role identifiers and authority remain unchanged. If the active
|
|
19
19
|
Orchestrator model cannot meet the recorded contract, say so before work starts.
|
|
20
20
|
|
|
21
|
+
Before the first owner-facing reply, read `.triad-plus/team.json` when it exists.
|
|
22
|
+
User-facing identity is permanent: adopt its non-empty
|
|
23
|
+
`roles.orchestrator.displayName` as the sole user-facing identity for every
|
|
24
|
+
owner-facing reply, including the first. If the file is absent or has no
|
|
25
|
+
non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
26
|
+
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
27
|
+
outputs, but never claim their identity.
|
|
28
|
+
|
|
29
|
+
After loading this configuration, the first owner-facing message of every Triad+
|
|
30
|
+
run must begin with a concise introduction: "I am <displayName>, the Triad+
|
|
31
|
+
Orchestrator for this run." Localize it to the configured interaction language,
|
|
32
|
+
then state in one sentence whether the run is new or resumed and what input was
|
|
33
|
+
received. Do this before delegating, discussing artifacts, or asking questions.
|
|
34
|
+
|
|
21
35
|
Delegate implementation to `triad-developer` and review to `triad-reviewer`.
|
|
22
36
|
After Triad approval, automatically invoke fresh `triad-evaluator` when
|
|
23
37
|
`.triad-plus/team.json` has `roles.evaluator.enabled: true`; false or omitted
|
|
@@ -18,6 +18,20 @@ language, owner address, display names, personas, and model contract in communic
|
|
|
18
18
|
technical role identifiers and authority remain unchanged. If the active
|
|
19
19
|
Orchestrator model cannot meet the recorded contract, say so before work starts.
|
|
20
20
|
|
|
21
|
+
Before the first owner-facing reply, read `.triad-plus/team.json` when it exists.
|
|
22
|
+
User-facing identity is permanent: adopt its non-empty
|
|
23
|
+
`roles.orchestrator.displayName` as the sole user-facing identity for every
|
|
24
|
+
owner-facing reply, including the first. If the file is absent or has no
|
|
25
|
+
non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
26
|
+
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
27
|
+
outputs, but never claim their identity.
|
|
28
|
+
|
|
29
|
+
After loading this configuration, the first owner-facing message of every Triad+
|
|
30
|
+
run must begin with a concise introduction: "I am <displayName>, the Triad+
|
|
31
|
+
Orchestrator for this run." Localize it to the configured interaction language,
|
|
32
|
+
then state in one sentence whether the run is new or resumed and what input was
|
|
33
|
+
received. Do this before delegating, discussing artifacts, or asking questions.
|
|
34
|
+
|
|
21
35
|
Delegate implementation to the configured `triad_developer` profile and review to
|
|
22
36
|
`triad_reviewer`. Once Triad is approved, automatically invoke a fresh
|
|
23
37
|
`triad_evaluator` when `.triad-plus/team.json` has `roles.evaluator.enabled: true`.
|
|
@@ -11,6 +11,20 @@ Operate the current Hermes session as the Triad Orchestrator. Load
|
|
|
11
11
|
when present; its role IDs, names, personas, and model/provider contract are
|
|
12
12
|
configuration, not workflow authority.
|
|
13
13
|
|
|
14
|
+
Before the first owner-facing reply, read `.triad-plus/team.json` when it exists.
|
|
15
|
+
User-facing identity is permanent: adopt its non-empty
|
|
16
|
+
`roles.orchestrator.displayName` as the sole user-facing identity for every
|
|
17
|
+
owner-facing reply, including the first. If the file is absent or has no
|
|
18
|
+
non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
19
|
+
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
20
|
+
outputs, but never claim their identity.
|
|
21
|
+
|
|
22
|
+
After loading this configuration, the first owner-facing message of every Triad+
|
|
23
|
+
run must begin with a concise introduction: "I am <displayName>, the Triad+
|
|
24
|
+
Orchestrator for this run." Localize it to the configured interaction language,
|
|
25
|
+
then state in one sentence whether the run is new or resumed and what input was
|
|
26
|
+
received. Do this before delegating, discussing artifacts, or asking questions.
|
|
27
|
+
|
|
14
28
|
For a normal run, govern only Orchestrator, Developer, and Reviewer. Delegate
|
|
15
29
|
ordinary implementation with `hermes chat -q --in <worktree> --no-restore-cwd`
|
|
16
30
|
using `--skills triad-loop-developer`; obtain a separate Reviewer result with
|
|
@@ -28,6 +28,20 @@ If `.triad-plus/team.json` exists, load it before replying and use its language,
|
|
|
28
28
|
owner address, display names, and personas in communication. Technical role IDs and their
|
|
29
29
|
authority never change; stop before work if the configured model contract fails.
|
|
30
30
|
|
|
31
|
+
Before the first owner-facing reply, read `.triad-plus/team.json` when it exists.
|
|
32
|
+
User-facing identity is permanent: adopt its non-empty
|
|
33
|
+
`roles.orchestrator.displayName` as the sole user-facing identity for every
|
|
34
|
+
owner-facing reply, including the first. If the file is absent or has no
|
|
35
|
+
non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
36
|
+
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
37
|
+
outputs, but never claim their identity.
|
|
38
|
+
|
|
39
|
+
After loading this configuration, the first owner-facing message of every Triad+
|
|
40
|
+
run must begin with a concise introduction: "I am <displayName>, the Triad+
|
|
41
|
+
Orchestrator for this run." Localize it to the configured interaction language,
|
|
42
|
+
then state in one sentence whether the run is new or resumed and what input was
|
|
43
|
+
received. Do this before delegating, discussing artifacts, or asking questions.
|
|
44
|
+
|
|
31
45
|
Start by loading `triad-loop-bootstrap` for a new project, or
|
|
32
46
|
`triad-loop-orchestrator` for an initialized project. Follow the loaded skill
|
|
33
47
|
exactly. Keep the project-control workspace separate from product repositories,
|
|
@@ -11,6 +11,20 @@ If `.triad-plus/team.json` exists, load it before replying. Use its interaction
|
|
|
11
11
|
language, owner address, display names, personas, and model contract in communication;
|
|
12
12
|
technical role identifiers and authority remain unchanged.
|
|
13
13
|
|
|
14
|
+
Before the first owner-facing reply, read `.triad-plus/team.json` when it exists.
|
|
15
|
+
User-facing identity is permanent: adopt its non-empty
|
|
16
|
+
`roles.orchestrator.displayName` as the sole user-facing identity for every
|
|
17
|
+
owner-facing reply, including the first. If the file is absent or has no
|
|
18
|
+
non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
19
|
+
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
20
|
+
outputs, but never claim their identity.
|
|
21
|
+
|
|
22
|
+
After loading this configuration, the first owner-facing message of every Triad+
|
|
23
|
+
run must begin with a concise introduction: "I am <displayName>, the Triad+
|
|
24
|
+
Orchestrator for this run." Localize it to the configured interaction language,
|
|
25
|
+
then state in one sentence whether the run is new or resumed and what input was
|
|
26
|
+
received. Do this before delegating, discussing artifacts, or asking questions.
|
|
27
|
+
|
|
14
28
|
If this is a new project, load `triad-loop-bootstrap`, collect only missing
|
|
15
29
|
inputs that prevent safe setup or measurable feature cards, create the isolated
|
|
16
30
|
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,63 @@ 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
|
+
const instructionOverlay = `${overlayStart}
|
|
136
|
+
## Triad+ role-run overlay
|
|
137
|
+
|
|
138
|
+
When a Triad+ entry point is invoked in this control workspace, read
|
|
139
|
+
\`.triad-plus/team.json\` before the first owner-facing reply. The active
|
|
140
|
+
Orchestrator presents itself only as \`roles.orchestrator.displayName\`. This
|
|
141
|
+
is a role-run presentation rule; it does not change technical authority,
|
|
142
|
+
repository policy, safety instructions, or the host's identity outside Triad+.
|
|
143
|
+
${overlayEnd}`;
|
|
144
|
+
|
|
145
|
+
async function overlayPlan(controlRoot) {
|
|
146
|
+
const target = join(controlRoot, 'AGENTS.md');
|
|
147
|
+
if (!(await exists(target))) return { target, action: 'create', content: `# Project instructions\n\n${instructionOverlay}\n` };
|
|
148
|
+
const source = await readFile(target, 'utf8');
|
|
149
|
+
const start = source.indexOf(overlayStart);
|
|
150
|
+
const end = source.indexOf(overlayEnd);
|
|
151
|
+
if (start === -1 && end === -1) return { target, action: 'append', content: `${source.replace(/\s*$/, '')}\n\n${instructionOverlay}\n` };
|
|
152
|
+
if (start < 0 || end < start) throw new Error(`Cannot safely update managed instruction block: ${target}`);
|
|
153
|
+
return { target, action: 'update', content: `${source.slice(0, start)}${instructionOverlay}${source.slice(end + overlayEnd.length)}` };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function applyOverlay(controlRoot, apply) {
|
|
157
|
+
const plan = await overlayPlan(controlRoot);
|
|
158
|
+
process.stdout.write(` Instructions ${apply ? plan.action : `would ${plan.action}`} ${plan.target}\n`);
|
|
159
|
+
if (apply) await writeFile(plan.target, plan.content, 'utf8');
|
|
160
|
+
}
|
|
161
|
+
|
|
107
162
|
async function loadTeamConfig(options) {
|
|
108
163
|
if (options.team) return options.team;
|
|
109
164
|
if (!options.teamConfig) return null;
|
|
@@ -236,6 +291,7 @@ async function init(options) {
|
|
|
236
291
|
if (existing.length > 0) throw new Error(`Installation aborted; existing paths would be overwritten:\n${existing.map((target) => ` ${target}`).join('\n')}`);
|
|
237
292
|
await installAssets(adapter.projectAssets, controlRoot, installContext);
|
|
238
293
|
if (team) await writeTeamConfig(controlRoot, team);
|
|
294
|
+
if (team) await applyOverlay(controlRoot, true);
|
|
239
295
|
if (team) await applyTeamBinding(adapter, controlRoot, team, installContext);
|
|
240
296
|
if (options.global) await installAssets(adapter.globalAssets, controlRoot, installContext);
|
|
241
297
|
process.stdout.write(`Triad+ installed for ${adapter.label} in ${controlRoot}\n`);
|
|
@@ -245,6 +301,34 @@ async function init(options) {
|
|
|
245
301
|
process.stdout.write(`Open the control workspace and use ${adapter.entry} <PRD path>. Configured Evaluator+ runs automatically post-approval.\n`);
|
|
246
302
|
}
|
|
247
303
|
|
|
304
|
+
async function currentTeam(controlRoot) {
|
|
305
|
+
const target = teamConfigPath(controlRoot);
|
|
306
|
+
if (!(await exists(target))) return null;
|
|
307
|
+
try { return JSON.parse(await readFile(target, 'utf8')); }
|
|
308
|
+
catch { throw new Error(`Cannot safely upgrade an invalid team config: ${target}`); }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function upgrade(options) {
|
|
312
|
+
const adapter = getAdapter(options.host);
|
|
313
|
+
if (!adapter) throw new Error(`Choose --host ${listAdapters().map((item) => item.id).join(', ')}.`);
|
|
314
|
+
if (!options.control) throw new Error('Provide --control <project-control-path>.');
|
|
315
|
+
const controlRoot = resolve(options.control);
|
|
316
|
+
await requireDirectory(controlRoot);
|
|
317
|
+
const team = await currentTeam(controlRoot);
|
|
318
|
+
const installContext = context(controlRoot);
|
|
319
|
+
const stamp = new Date().toISOString().replaceAll(':', '-').replaceAll('.', '-');
|
|
320
|
+
const backupRoot = join(controlRoot, '.triad-plus', 'backups', stamp);
|
|
321
|
+
process.stdout.write(`Triad+ upgrade ${options.apply ? 'applying' : 'plan'} for ${adapter.label}\n`);
|
|
322
|
+
await refreshAssets(adapter.projectAssets, controlRoot, installContext, join(backupRoot, 'project'), options.apply);
|
|
323
|
+
if (team) await applyOverlay(controlRoot, options.apply);
|
|
324
|
+
else process.stdout.write(' Instructions skipped: .triad-plus/team.json is not configured\n');
|
|
325
|
+
if (options.global) {
|
|
326
|
+
await refreshAssets(adapter.globalAssets, controlRoot, installContext, join(backupRoot, 'global'), options.apply);
|
|
327
|
+
if (team) await applyTeamBinding(adapter, controlRoot, team, installContext);
|
|
328
|
+
}
|
|
329
|
+
if (!options.apply) process.stdout.write('Dry run only. Re-run with --apply to update managed assets.\n');
|
|
330
|
+
}
|
|
331
|
+
|
|
248
332
|
async function doctor(options) {
|
|
249
333
|
if (!options.control) throw new Error('Provide --control <project-control-path>.');
|
|
250
334
|
const controlRoot = resolve(options.control);
|
|
@@ -270,6 +354,14 @@ async function doctor(options) {
|
|
|
270
354
|
process.stdout.write(` Adapter ${manifest ? 'OK' : 'missing or different adapter'}\n`);
|
|
271
355
|
process.stdout.write(` Team config ${team === 'invalid' ? 'invalid' : team ? 'OK' : 'not configured'}\n`);
|
|
272
356
|
process.stdout.write(` Evaluator+ ${team?.roles?.evaluator?.enabled === true ? 'configured' : 'not configured'}\n`);
|
|
357
|
+
const overlay = await overlayPlan(controlRoot).catch(() => null);
|
|
358
|
+
process.stdout.write(` Instructions ${overlay ? overlay.action === 'update' ? 'managed' : `needs ${overlay.action}` : 'invalid managed block'}\n`);
|
|
359
|
+
const globalAgents = join(codexHome(), 'AGENTS.md');
|
|
360
|
+
if (await exists(globalAgents)) {
|
|
361
|
+
const globalText = await readFile(globalAgents, 'utf8');
|
|
362
|
+
const fixedIdentity = /(?:identity|name)[\s\S]{0,100}(?:always|only|must)/i.test(globalText);
|
|
363
|
+
process.stdout.write(` Identity policy ${fixedIdentity ? 'host rule detected; review for Triad role conflicts' : 'no fixed host rule detected'}\n`);
|
|
364
|
+
}
|
|
273
365
|
if (options.hookConfig && adapter.lifecycle) {
|
|
274
366
|
process.stdout.write(` Hook config declared; run runtime capability detection for detailed status\n`);
|
|
275
367
|
}
|
|
@@ -302,6 +394,7 @@ try {
|
|
|
302
394
|
const options = parseArgs(process.argv.slice(2));
|
|
303
395
|
if (options.command === 'init') await init(options);
|
|
304
396
|
else if (options.command === 'doctor') await doctor(options);
|
|
397
|
+
else if (options.command === 'upgrade') await upgrade(options);
|
|
305
398
|
else if (!options.command) await interactiveInit();
|
|
306
399
|
else if (options.command === '--help' || options.command === '-h') usage(0);
|
|
307
400
|
else throw new Error(`Unknown command: ${options.command}`);
|
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,
|
|
@@ -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
|
@@ -9,6 +9,21 @@ 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
11
|
|
|
12
|
+
Before the first owner-facing reply, read `.triad-plus/team.json` when it exists.
|
|
13
|
+
User-facing identity is permanent: adopt its non-empty
|
|
14
|
+
`roles.orchestrator.displayName` as the sole user-facing identity for every
|
|
15
|
+
owner-facing reply, including the first. If the file is absent or has no
|
|
16
|
+
non-empty display name, use `Triad Orchestrator`; never present a hidden
|
|
17
|
+
intermediary or another Triad role to the owner. You may report delegated roles'
|
|
18
|
+
outputs, but never claim their identity. Technical role IDs and authority remain
|
|
19
|
+
unchanged.
|
|
20
|
+
|
|
21
|
+
After loading this configuration, the first owner-facing message of every Triad+
|
|
22
|
+
run must begin with a concise introduction: "I am <displayName>, the Triad+
|
|
23
|
+
Orchestrator for this run." Localize it to the configured interaction language,
|
|
24
|
+
then state in one sentence whether the run is new or resumed and what input was
|
|
25
|
+
received. Do this before delegating, discussing artifacts, or asking questions.
|
|
26
|
+
|
|
12
27
|
## Run one card
|
|
13
28
|
|
|
14
29
|
1. Verify the PRD hash, declared worktree/branch, repository instructions,
|