@orchestrator-claude/definitions 3.32.2 → 3.34.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/README.md +145 -0
- package/agents/adversarial-reviewer.md +2 -2
- package/agents/azure-devops-engineer.md +9 -1
- package/agents/cicd-platform-engineer.md +477 -0
- package/agents/debug-sidecar.md +6 -1
- package/agents/doc-sidecar.md +6 -1
- package/agents/frontend-engineer.md +173 -0
- package/agents/implementer.md +39 -23
- package/agents/methodology-researcher.md +2 -1
- package/agents/operational-artifact-builder.md +2 -1
- package/agents/orchestrator.md +47 -76
- package/agents/planner.md +2 -2
- package/agents/presentation-engineer.md +1 -1
- package/agents/python-data-engineer.md +214 -0
- package/agents/reviewer.md +16 -8
- package/agents/specifier.md +1 -1
- package/agents/task-generator.md +1 -1
- package/agents/test-sidecar.md +15 -4
- package/dist/cli.js +78 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/DiffCommand.js +1 -1
- package/dist/commands/DiffCommand.js.map +1 -1
- package/dist/commands/InstallCommand.d.ts +41 -0
- package/dist/commands/InstallCommand.d.ts.map +1 -0
- package/dist/commands/InstallCommand.js +253 -0
- package/dist/commands/InstallCommand.js.map +1 -0
- package/dist/http/ApiClient.d.ts +5 -0
- package/dist/http/ApiClient.d.ts.map +1 -1
- package/dist/http/ApiClient.js.map +1 -1
- package/dist/install/DevCheckoutDetector.d.ts +66 -0
- package/dist/install/DevCheckoutDetector.d.ts.map +1 -0
- package/dist/install/DevCheckoutDetector.js +150 -0
- package/dist/install/DevCheckoutDetector.js.map +1 -0
- package/dist/install/HooksWirer.d.ts +29 -0
- package/dist/install/HooksWirer.d.ts.map +1 -0
- package/dist/install/HooksWirer.js +80 -0
- package/dist/install/HooksWirer.js.map +1 -0
- package/dist/install/LocalDiffReporter.d.ts +25 -0
- package/dist/install/LocalDiffReporter.d.ts.map +1 -0
- package/dist/install/LocalDiffReporter.js +50 -0
- package/dist/install/LocalDiffReporter.js.map +1 -0
- package/dist/install/Materializer.d.ts +67 -0
- package/dist/install/Materializer.d.ts.map +1 -0
- package/dist/install/Materializer.js +196 -0
- package/dist/install/Materializer.js.map +1 -0
- package/dist/install/McpRegistrar.d.ts +63 -0
- package/dist/install/McpRegistrar.d.ts.map +1 -0
- package/dist/install/McpRegistrar.js +114 -0
- package/dist/install/McpRegistrar.js.map +1 -0
- package/dist/install/ProcessRunner.d.ts +25 -0
- package/dist/install/ProcessRunner.d.ts.map +1 -0
- package/dist/install/ProcessRunner.js +41 -0
- package/dist/install/ProcessRunner.js.map +1 -0
- package/dist/install/ProjectSettingsNeutralizer.d.ts +39 -0
- package/dist/install/ProjectSettingsNeutralizer.d.ts.map +1 -0
- package/dist/install/ProjectSettingsNeutralizer.js +100 -0
- package/dist/install/ProjectSettingsNeutralizer.js.map +1 -0
- package/dist/install/types.d.ts +52 -0
- package/dist/install/types.d.ts.map +1 -0
- package/dist/install/types.js +8 -0
- package/dist/install/types.js.map +1 -0
- package/dist/manifest/ManifestLoader.d.ts.map +1 -1
- package/dist/manifest/ManifestLoader.js +3 -2
- package/dist/manifest/ManifestLoader.js.map +1 -1
- package/dist/manifest/types.d.ts +3 -0
- package/dist/manifest/types.d.ts.map +1 -1
- package/dist/scripts/bundle-hooks.d.ts +39 -0
- package/dist/scripts/bundle-hooks.d.ts.map +1 -0
- package/dist/scripts/bundle-hooks.js +165 -0
- package/dist/scripts/bundle-hooks.js.map +1 -0
- package/dist/scripts/generate-manifest.d.ts +22 -3
- package/dist/scripts/generate-manifest.d.ts.map +1 -1
- package/dist/scripts/generate-manifest.js +78 -35
- package/dist/scripts/generate-manifest.js.map +1 -1
- package/hooks/dangling-guard.mjs +7426 -0
- package/hooks/dangling-guard.ts +223 -6
- package/hooks/gate-guardian.mjs +463 -0
- package/hooks/hooks.json +63 -0
- package/hooks/lib/additional-directories-sync.ts +295 -0
- package/hooks/lib/api-client.ts +388 -7
- package/hooks/lib/session-selection.ts +81 -0
- package/hooks/mailbox-listener.mjs +259 -0
- package/hooks/post-compact.mjs +7931 -0
- package/hooks/post-implement.mjs +238 -0
- package/hooks/session-start.mjs +8234 -0
- package/hooks/session-start.ts +264 -23
- package/hooks/sprint-registry.mjs +59 -0
- package/hooks/subagent-start.mjs +289 -0
- package/hooks/subagent-stop.mjs +7793 -0
- package/hooks/subagent-stop.ts +4 -2
- package/hooks/user-prompt.mjs +329 -0
- package/hooks/workflow-guard.mjs +7805 -0
- package/hooks/workflow-guard.ts +360 -119
- package/hooks-settings-template.json +63 -0
- package/manifest.json +124 -90
- package/mcp-servers.json +48 -0
- package/package.json +10 -4
- package/personas/auditor-compliance.md +104 -0
- package/personas/cfo-conservador.md +88 -0
- package/personas/designer-perfeccionista.md +104 -0
- package/personas/diretor-cetico-fmcg.md +80 -0
- package/personas/engenheiro-cetico-construcao.md +107 -0
- package/skills/artifact-generator/SKILL.md +1 -1
- package/skills/artifact-production/SKILL.md +28 -0
- package/skills/backlog-synthesis/SKILL.md +8 -3
- package/skills/checkpoint-protocol/SKILL.md +7 -0
- package/skills/infographic-engine/SKILL.md +116 -0
- package/skills/orchestrator/SKILL.md +93 -7
- package/skills/persona-library/SKILL.md +1 -1
- package/skills/project-conventions/SKILL.md +53 -30
- package/skills/project-management-enterprise/SKILL.md +1 -1
- package/skills/python-data-engineering/SKILL.md +792 -0
- package/skills/release/SKILL.md +29 -18
- package/skills/release/release.sh +65 -4
- package/skills/storybook-validated-loop/SKILL.md +110 -0
- package/skills/tdd-discipline/SKILL.md +16 -0
- package/skills/workflow-coordination/SKILL.md +117 -16
package/hooks/workflow-guard.ts
CHANGED
|
@@ -4,22 +4,39 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Blocks writes to src/ and tests/ without active workflow.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* RFC-040 TD-204 §⑤-A (S366): Classe-A asset-safety gate, made
|
|
8
|
+
* "cwd-scoped-safe" — resolves the OWNING project of the write TARGET
|
|
9
|
+
* (file_path), not the session's cwd (per TD-204 §"cwd não some, rebaixa":
|
|
10
|
+
* "o guard Classe-A ainda resolve file_path -> projeto pra autorizar cada
|
|
11
|
+
* Write contra o projeto selecionado"). Today, in this repo (a registered
|
|
12
|
+
* project with an `.orchestrator` sentinel), targetProjectRoot always
|
|
13
|
+
* resolves non-null, so every write still falls through to the pre-existing
|
|
14
|
+
* mode/sub-agent logic (rules 6/7 below) — byte-equivalent to pre-⑤-A
|
|
15
|
+
* behavior. The two NEW branches (4, 5) are dormant-but-tested until this
|
|
16
|
+
* hook is installed globally (future installer) against an unregistered or
|
|
17
|
+
* foreign (trc/red-bull) cwd, where they become load-bearing: a Classe-A
|
|
18
|
+
* gate NEVER no-ops silently on an unrecognized project path.
|
|
19
|
+
*
|
|
20
|
+
* Allow paths (in evaluation order — see decideWorkflowGuardAction):
|
|
8
21
|
* 1. SKIP_WORKFLOW_GUARD=true (explicit bypass, all paths)
|
|
9
22
|
* 2. Non-guarded path (file outside src/ or tests/)
|
|
10
23
|
* 3. Config/doc file extension (.json|.yaml|.yml|.md|.env*)
|
|
11
|
-
* 4.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
24
|
+
* 4. [TD-204 ⑤-A] `.orchestrator/.unmanaged` sentinel present at the
|
|
25
|
+
* target's resolved project root — explicit, auditable opt-out.
|
|
26
|
+
* 5. [TD-204 ⑤-A, deny] target path resolves to NO registered project
|
|
27
|
+
* (targetProjectRoot === null) — instructs registration or the
|
|
28
|
+
* `.unmanaged` opt-out. NEVER a silent no-op.
|
|
29
|
+
* 6. Active workflow + mode='quick'|'interactive'|'direct', or caller is
|
|
30
|
+
* a sub-agent (agent_id present), or SKIP_SUBAGENT_GUARD=true
|
|
31
|
+
* (DEPRECATED — ADR-013 §6.5.3, telemetered to observations.yaml)
|
|
16
32
|
* Deny paths:
|
|
17
|
-
* - Active workflow + standard/full/legacy mode + main agent direct write
|
|
18
|
-
*
|
|
33
|
+
* - Active workflow + standard/full/legacy mode + main agent direct write
|
|
34
|
+
* (no agent_id, no bypass)
|
|
35
|
+
* - No active workflow (target project IS registered)
|
|
19
36
|
*/
|
|
20
37
|
|
|
21
|
-
import { join } from 'node:path';
|
|
22
|
-
|
|
38
|
+
import { dirname, join } from 'node:path';
|
|
39
|
+
import { existsSync } from 'node:fs';
|
|
23
40
|
import {
|
|
24
41
|
log,
|
|
25
42
|
readStdin,
|
|
@@ -27,153 +44,377 @@ import {
|
|
|
27
44
|
getActiveWorkflow,
|
|
28
45
|
getWorkflowMode,
|
|
29
46
|
outputPreToolUse,
|
|
47
|
+
resolveAgentMemoryBaseDir,
|
|
48
|
+
resolveHookProjectRoot,
|
|
30
49
|
} from './lib/api-client.js';
|
|
31
50
|
import { appendObservation } from './lib/observations-helper.js';
|
|
32
51
|
|
|
33
52
|
const HOOK = 'WORKFLOW-GUARD';
|
|
34
53
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// TD-204 ⑤-A: cwd-scoped-safe block-decision (pure, unit-testable)
|
|
56
|
+
// Mirrors the TD-203 dangling-guard.ts pattern: decideWorkflowGuardAction()
|
|
57
|
+
// takes ONLY injected inputs (zero fs/API), main() gathers signals and
|
|
58
|
+
// delegates.
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
41
60
|
|
|
42
|
-
|
|
61
|
+
export type WorkflowGuardAction = 'allow' | 'deny';
|
|
62
|
+
|
|
63
|
+
export interface WorkflowGuardOutput {
|
|
64
|
+
decision: WorkflowGuardAction;
|
|
65
|
+
reason?: string;
|
|
66
|
+
context?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Mirrors ObservationEntry minus timestamp/session_id (added by main()). */
|
|
70
|
+
export interface WorkflowGuardObservation {
|
|
71
|
+
phase: string;
|
|
72
|
+
workflow_id: string;
|
|
73
|
+
summary: string;
|
|
74
|
+
tags: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface WorkflowGuardDecision {
|
|
78
|
+
/**
|
|
79
|
+
* 1-7, matches the numbered precedence in TD-204 ⑤-A. Rule 6 covers every
|
|
80
|
+
* "active workflow" sub-branch (quick/interactive/direct/sub-agent/
|
|
81
|
+
* SKIP_SUBAGENT_GUARD/main-agent-deny) — they all require a REAL fetched
|
|
82
|
+
* workflow to distinguish, unlike rule 7 (no active workflow) which a
|
|
83
|
+
* `workflow: null` trial call can already prove correctly. main() uses
|
|
84
|
+
* `matchedRule === 7` as the sole signal for whether the (network) active
|
|
85
|
+
* workflow must actually be fetched to confirm the decision — see main().
|
|
86
|
+
*/
|
|
87
|
+
matchedRule: 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
88
|
+
/** Whether outputPreToolUse must be called. Rules 2/3 are silent pass-throughs (pre-existing behavior — no hookSpecificOutput emitted). */
|
|
89
|
+
emitOutput: boolean;
|
|
90
|
+
output?: WorkflowGuardOutput;
|
|
91
|
+
logMessages: string[];
|
|
92
|
+
observation?: WorkflowGuardObservation;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface WorkflowGuardWorkflowInput {
|
|
96
|
+
id: string;
|
|
97
|
+
/** getWorkflowMode() result: executionMode ?? mode ?? null. */
|
|
98
|
+
mode: string | null;
|
|
99
|
+
/** WorkflowStatus.currentPhase — required upstream, defensively `|| 'unknown'` at use-site (mirrors pre-⑤-A code). */
|
|
100
|
+
currentPhase: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface WorkflowGuardDecisionInput {
|
|
104
|
+
filePath: string;
|
|
105
|
+
skipWorkflowGuard: boolean;
|
|
106
|
+
skipSubagentGuard: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* TD-204 ⑤-A: resolved via resolveHookProjectRoot(dirname(filePath)) — the
|
|
109
|
+
* file_path's OWNING project root, NOT the session's cwd. `null` means the
|
|
110
|
+
* write target does not belong to any project with an `.orchestrator`
|
|
111
|
+
* sentinel in its ancestry.
|
|
112
|
+
*/
|
|
113
|
+
targetProjectRoot: string | null;
|
|
114
|
+
/**
|
|
115
|
+
* True iff `<targetProjectRoot>/.orchestrator/.unmanaged` exists (explicit,
|
|
116
|
+
* auditable opt-out sentinel, TD-204 ⑤-A). Always false when
|
|
117
|
+
* targetProjectRoot is null (the marker itself lives inside `.orchestrator`,
|
|
118
|
+
* so a null root implies no `.orchestrator` dir was found at all).
|
|
119
|
+
*/
|
|
120
|
+
isUnmanaged: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* `null` covers BOTH "genuinely no active workflow" and "not yet fetched"
|
|
123
|
+
* (see WorkflowGuardDecision.matchedRule doc — main() distinguishes the two
|
|
124
|
+
* via a cheap trial call before paying for the network fetch).
|
|
125
|
+
*/
|
|
126
|
+
workflow: WorkflowGuardWorkflowInput | null;
|
|
127
|
+
/** getField(stdin, 'agent_id') — empty string when absent (never fetched over network). */
|
|
128
|
+
agentId: string;
|
|
129
|
+
/** getField(stdin, 'agent_type') — empty string when absent. */
|
|
130
|
+
agentType: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const CONFIG_DOC_RE = /\.(json|ya?ml|md|env(\..*)?)$/;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Pure decision function for the workflow-guard PreToolUse[Write|Edit] hook
|
|
137
|
+
* (TD-204 ⑤-A). Evaluates precedence rules 1-7 top-down, first match wins.
|
|
138
|
+
* Takes only injected inputs — no fs/API calls — so every branch is
|
|
139
|
+
* independently testable and deterministic.
|
|
140
|
+
*/
|
|
141
|
+
export function decideWorkflowGuardAction(
|
|
142
|
+
input: WorkflowGuardDecisionInput
|
|
143
|
+
): WorkflowGuardDecision {
|
|
144
|
+
const {
|
|
145
|
+
filePath,
|
|
146
|
+
skipWorkflowGuard,
|
|
147
|
+
skipSubagentGuard,
|
|
148
|
+
targetProjectRoot,
|
|
149
|
+
isUnmanaged,
|
|
150
|
+
workflow,
|
|
151
|
+
agentId,
|
|
152
|
+
agentType,
|
|
153
|
+
} = input;
|
|
43
154
|
|
|
44
|
-
//
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
155
|
+
// Rule 1: explicit global bypass
|
|
156
|
+
if (skipWorkflowGuard) {
|
|
157
|
+
return {
|
|
158
|
+
matchedRule: 1,
|
|
159
|
+
emitOutput: true,
|
|
160
|
+
output: {
|
|
161
|
+
decision: 'allow',
|
|
162
|
+
context: 'Workflow guard bypassed via SKIP_WORKFLOW_GUARD=true.',
|
|
163
|
+
},
|
|
164
|
+
logMessages: ['ALLOW (SKIP_WORKFLOW_GUARD=true)'],
|
|
165
|
+
};
|
|
52
166
|
}
|
|
53
167
|
|
|
54
|
-
//
|
|
168
|
+
// Rule 2: non-guarded path (outside src/ or tests/) — silent pass-through
|
|
55
169
|
if (!filePath.includes('/src/') && !filePath.includes('/tests/')) {
|
|
56
|
-
|
|
57
|
-
|
|
170
|
+
return { matchedRule: 2, emitOutput: false, logMessages: ['ALLOW (non-guarded path)'] };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Rule 3: config/doc file extension — silent pass-through
|
|
174
|
+
if (CONFIG_DOC_RE.test(filePath)) {
|
|
175
|
+
return { matchedRule: 3, emitOutput: false, logMessages: ['ALLOW (config/doc file)'] };
|
|
58
176
|
}
|
|
59
177
|
|
|
60
|
-
//
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
178
|
+
// Rule 4 [TD-204 ⑤-A]: explicit, auditable unmanaged opt-out sentinel
|
|
179
|
+
if (isUnmanaged) {
|
|
180
|
+
return {
|
|
181
|
+
matchedRule: 4,
|
|
182
|
+
emitOutput: true,
|
|
183
|
+
output: {
|
|
184
|
+
decision: 'allow',
|
|
185
|
+
context: `Workflow guard bypassed via .orchestrator/.unmanaged sentinel at ${String(targetProjectRoot)}. Explicit, auditable opt-out (RFC-040 TD-204 Classe-A asset-safety gate).`,
|
|
186
|
+
},
|
|
187
|
+
logMessages: [`ALLOW (.orchestrator/.unmanaged sentinel at ${String(targetProjectRoot)})`],
|
|
188
|
+
};
|
|
64
189
|
}
|
|
65
190
|
|
|
66
|
-
//
|
|
67
|
-
|
|
191
|
+
// Rule 5 [TD-204 ⑤-A, deny]: write target does not belong to ANY registered
|
|
192
|
+
// project. Branches the block message ("não-registrado") — NEVER a silent
|
|
193
|
+
// no-op, per TD-204's Classe-A contract.
|
|
194
|
+
if (targetProjectRoot === null) {
|
|
195
|
+
return {
|
|
196
|
+
matchedRule: 5,
|
|
197
|
+
emitOutput: true,
|
|
198
|
+
output: {
|
|
199
|
+
decision: 'deny',
|
|
200
|
+
reason:
|
|
201
|
+
'Workflow Guard: target path does not belong to any registered project (no .orchestrator sentinel found).',
|
|
202
|
+
context: [
|
|
203
|
+
`The file ${filePath} is outside any project registered with the Orchestrator — no .orchestrator sentinel was found walking up from its directory.`,
|
|
204
|
+
'',
|
|
205
|
+
'You MUST either:',
|
|
206
|
+
" 1. Register this project: mcp__orchestrator-tools__registerProject({ rootPath: '<project-root>', ... }) — see the registration runbook, or",
|
|
207
|
+
" 2. Mark it explicitly unmanaged (opt out of this guard): create an empty file at '<project-root>/.orchestrator/.unmanaged'.",
|
|
208
|
+
'',
|
|
209
|
+
'The workflow-guard hook NEVER no-ops silently on an unrecognized project path (RFC-040 TD-204 Classe-A asset-safety gate).',
|
|
210
|
+
].join('\n'),
|
|
211
|
+
},
|
|
212
|
+
logMessages: [`DENY (unregistered target path, file=${filePath})`],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
68
215
|
|
|
216
|
+
// Rules 6/7: from here on, targetProjectRoot is a REGISTERED project —
|
|
217
|
+
// behavior is byte-equivalent to pre-⑤-A workflow-guard.ts.
|
|
69
218
|
if (workflow) {
|
|
70
|
-
const mode =
|
|
71
|
-
|
|
219
|
+
const mode = workflow.mode;
|
|
220
|
+
const modeLogLine = `workflow=${workflow.id} mode=${mode || 'legacy'}`;
|
|
72
221
|
|
|
222
|
+
// Rule 6a: mode='quick' — main agent writes allowed by design
|
|
73
223
|
if (mode === 'quick') {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
224
|
+
return {
|
|
225
|
+
matchedRule: 6,
|
|
226
|
+
emitOutput: true,
|
|
227
|
+
output: {
|
|
228
|
+
decision: 'allow',
|
|
229
|
+
context: `Active workflow: ${workflow.id} (mode: quick). Direct writes allowed in quick mode.`,
|
|
230
|
+
},
|
|
231
|
+
logMessages: [modeLogLine],
|
|
232
|
+
};
|
|
79
233
|
}
|
|
80
234
|
|
|
235
|
+
// Rule 6b: mode='interactive' — writes unexpected, but allowed
|
|
81
236
|
if (mode === 'interactive') {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
237
|
+
return {
|
|
238
|
+
matchedRule: 6,
|
|
239
|
+
emitOutput: true,
|
|
240
|
+
output: {
|
|
241
|
+
decision: 'allow',
|
|
242
|
+
context: `Active workflow: ${workflow.id} (mode: interactive). Note: code writes are unexpected in interactive/analysis workflows.`,
|
|
243
|
+
},
|
|
244
|
+
logMessages: [modeLogLine],
|
|
245
|
+
};
|
|
87
246
|
}
|
|
88
247
|
|
|
89
|
-
//
|
|
248
|
+
// Rule 6c: mode='direct' — ADR-017 Phase 3
|
|
90
249
|
if (mode === 'direct') {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
250
|
+
return {
|
|
251
|
+
matchedRule: 6,
|
|
252
|
+
emitOutput: true,
|
|
253
|
+
output: {
|
|
254
|
+
decision: 'allow',
|
|
255
|
+
context: `Active workflow: ${workflow.id} (executionMode: direct). Direct writes allowed — record will be tracked via startDirectExecution/completeDirectExecution.`,
|
|
256
|
+
},
|
|
257
|
+
logMessages: [modeLogLine, `ALLOW (executionMode=direct, workflow: ${workflow.id})`],
|
|
258
|
+
};
|
|
97
259
|
}
|
|
98
260
|
|
|
99
|
-
//
|
|
100
|
-
const agentId = getField(stdin, 'agent_id');
|
|
261
|
+
// Rule 6d: caller is a sub-agent (agent_id present in stdin)
|
|
101
262
|
if (agentId) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
263
|
+
return {
|
|
264
|
+
matchedRule: 6,
|
|
265
|
+
emitOutput: true,
|
|
266
|
+
output: {
|
|
267
|
+
decision: 'allow',
|
|
268
|
+
context: `Active workflow: ${workflow.id}. Sub-agent ${agentType || 'unknown'} write allowed.`,
|
|
269
|
+
},
|
|
270
|
+
logMessages: [
|
|
271
|
+
modeLogLine,
|
|
272
|
+
`ALLOW (sub-agent: ${agentType || 'unknown'}, workflow: ${workflow.id})`,
|
|
273
|
+
],
|
|
274
|
+
};
|
|
109
275
|
}
|
|
110
276
|
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
277
|
+
// Rule 6e: main agent direct write, SKIP_SUBAGENT_GUARD=true (DEPRECATED,
|
|
278
|
+
// ADR-013 §6.5.3). Telemetered via an observation for cross-session usage
|
|
279
|
+
// review.
|
|
280
|
+
if (skipSubagentGuard) {
|
|
281
|
+
return {
|
|
282
|
+
matchedRule: 6,
|
|
283
|
+
emitOutput: true,
|
|
284
|
+
output: {
|
|
285
|
+
decision: 'allow',
|
|
286
|
+
context: [
|
|
287
|
+
`Active workflow: ${workflow.id}. Direct write allowed via SKIP_SUBAGENT_GUARD.`,
|
|
288
|
+
'',
|
|
289
|
+
'⚠️ DEPRECATED per ADR-013 §6.5.3. Prefer:',
|
|
290
|
+
" • mode:'quick' (startWorkflow({ mode: 'quick' })) — single-phase, main-agent writes allowed by design",
|
|
291
|
+
" • mode:'direct' (ADR-017 Phase 3) — multi-phase, main-agent writes with startDirectExecution audit trail",
|
|
292
|
+
'Bypass logged to .claude/agent-memory/main/observations.yaml for usage review (S200+ remediation decision).',
|
|
293
|
+
].join('\n'),
|
|
294
|
+
},
|
|
295
|
+
logMessages: [
|
|
296
|
+
modeLogLine,
|
|
297
|
+
`ALLOW (SKIP_SUBAGENT_GUARD=true, workflow: ${workflow.id}) [DEPRECATED]`,
|
|
298
|
+
],
|
|
299
|
+
observation: {
|
|
128
300
|
phase: workflow.currentPhase || 'unknown',
|
|
129
301
|
workflow_id: workflow.id,
|
|
130
302
|
summary: `SKIP_SUBAGENT_GUARD bypass triggered. file=${filePath} mode=${mode || 'legacy'}. Prefer mode:'quick' or mode:'direct' (ADR-013 §6.5.3).`,
|
|
131
303
|
tags: ['skip_subagent_guard_bypass', 'deprecated_bypass', `mode:${mode || 'legacy'}`],
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
// fail-open — never block on telemetry
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
outputPreToolUse({
|
|
138
|
-
decision: 'allow',
|
|
139
|
-
context: [
|
|
140
|
-
`Active workflow: ${workflow.id}. Direct write allowed via SKIP_SUBAGENT_GUARD.`,
|
|
141
|
-
'',
|
|
142
|
-
'⚠️ DEPRECATED per ADR-013 §6.5.3. Prefer:',
|
|
143
|
-
" • mode:'quick' (startWorkflow({ mode: 'quick' })) — single-phase, main-agent writes allowed by design",
|
|
144
|
-
" • mode:'direct' (ADR-017 Phase 3) — multi-phase, main-agent writes with startDirectExecution audit trail",
|
|
145
|
-
'Bypass logged to .claude/agent-memory/main/observations.yaml for usage review (S200+ remediation decision).',
|
|
146
|
-
].join('\n'),
|
|
147
|
-
});
|
|
148
|
-
return;
|
|
304
|
+
},
|
|
305
|
+
};
|
|
149
306
|
}
|
|
150
307
|
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
308
|
+
// Rule 6f: main agent direct write — DENY
|
|
309
|
+
return {
|
|
310
|
+
matchedRule: 6,
|
|
311
|
+
emitOutput: true,
|
|
312
|
+
output: {
|
|
313
|
+
decision: 'deny',
|
|
314
|
+
reason:
|
|
315
|
+
'Workflow Guard: Direct code writes are blocked. You must invoke a sub-agent (e.g. implementer) to write code. The sub-agent will have write access.',
|
|
316
|
+
context:
|
|
317
|
+
'Use the Agent tool to spawn an implementer sub-agent for code changes. The workflow-guard allows writes only from sub-agents (identified by agent_id in hook input).',
|
|
318
|
+
},
|
|
319
|
+
logMessages: [modeLogLine, `DENY (main agent direct write, mode=${mode || 'legacy'})`],
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Rule 7: no active workflow (target project IS registered) — DENY
|
|
324
|
+
return {
|
|
325
|
+
matchedRule: 7,
|
|
326
|
+
emitOutput: true,
|
|
327
|
+
output: {
|
|
154
328
|
decision: 'deny',
|
|
155
|
-
reason:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
'
|
|
159
|
-
|
|
160
|
-
|
|
329
|
+
reason: 'Workflow Guard: No active workflow. Direct implementation is not allowed.',
|
|
330
|
+
context: [
|
|
331
|
+
'You MUST start a workflow before writing code:',
|
|
332
|
+
'1. mcp__orchestrator-tools__detectWorkflow({ prompt: "..." })',
|
|
333
|
+
'2. mcp__orchestrator-tools__startWorkflow({ workflowType: "...", prompt: "..." })',
|
|
334
|
+
'3. Follow the nextStep returned by startWorkflow',
|
|
335
|
+
'',
|
|
336
|
+
'The workflow-guard hook blocks all writes to src/ and tests/ without an active workflow.',
|
|
337
|
+
].join('\n'),
|
|
338
|
+
},
|
|
339
|
+
logMessages: ['DENY (no active workflow)'],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function main(): Promise<void> {
|
|
344
|
+
const stdin = await readStdin();
|
|
345
|
+
const filePath =
|
|
346
|
+
getField(stdin, 'tool_input.file_path') ||
|
|
347
|
+
getField(stdin, 'file_path') ||
|
|
348
|
+
getField(stdin, 'input.file_path');
|
|
349
|
+
|
|
350
|
+
log(HOOK, `file_path=${filePath}`);
|
|
351
|
+
|
|
352
|
+
const skipWorkflowGuard = process.env.SKIP_WORKFLOW_GUARD === 'true';
|
|
353
|
+
const skipSubagentGuard = process.env.SKIP_SUBAGENT_GUARD === 'true';
|
|
354
|
+
const agentId = getField(stdin, 'agent_id');
|
|
355
|
+
const agentType = getField(stdin, 'agent_type');
|
|
356
|
+
|
|
357
|
+
// RFC-040 TD-204 ⑤-A: resolve the OWNING project of the write TARGET
|
|
358
|
+
// (file_path), not the session's cwd. Cheap fs walk-up (no network) — safe
|
|
359
|
+
// to compute unconditionally for every Write/Edit.
|
|
360
|
+
const targetDir = filePath.length > 0 ? dirname(filePath) : process.cwd();
|
|
361
|
+
const targetProjectRoot = resolveHookProjectRoot(targetDir);
|
|
362
|
+
const isUnmanaged =
|
|
363
|
+
targetProjectRoot !== null &&
|
|
364
|
+
existsSync(join(targetProjectRoot, '.orchestrator', '.unmanaged'));
|
|
365
|
+
|
|
366
|
+
const baseInput: Omit<WorkflowGuardDecisionInput, 'workflow'> = {
|
|
367
|
+
filePath,
|
|
368
|
+
skipWorkflowGuard,
|
|
369
|
+
skipSubagentGuard,
|
|
370
|
+
targetProjectRoot,
|
|
371
|
+
isUnmanaged,
|
|
372
|
+
agentId,
|
|
373
|
+
agentType,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// Two-pass evaluation: try with workflow=null first. Rules 1-5 never
|
|
377
|
+
// inspect `workflow`, so a `null` trial resolves them correctly WITHOUT
|
|
378
|
+
// any network I/O — preserving the pre-⑤-A short-circuit that skips
|
|
379
|
+
// getActiveWorkflow()/getWorkflowMode() for non-guarded, config-doc,
|
|
380
|
+
// unmanaged, and unregistered paths. Only when the trial itself lands on
|
|
381
|
+
// rule 7 ("no active workflow") do we actually need the network fetch to
|
|
382
|
+
// confirm — at most one real fetch, matching original behavior exactly.
|
|
383
|
+
let decision = decideWorkflowGuardAction({ ...baseInput, workflow: null });
|
|
384
|
+
|
|
385
|
+
if (decision.matchedRule === 7) {
|
|
386
|
+
const activeWorkflow = await getActiveWorkflow();
|
|
387
|
+
let workflow: WorkflowGuardWorkflowInput | null = null;
|
|
388
|
+
if (activeWorkflow) {
|
|
389
|
+
const mode = await getWorkflowMode(activeWorkflow.id);
|
|
390
|
+
workflow = { id: activeWorkflow.id, mode, currentPhase: activeWorkflow.currentPhase };
|
|
391
|
+
}
|
|
392
|
+
decision = decideWorkflowGuardAction({ ...baseInput, workflow });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
for (const line of decision.logMessages) {
|
|
396
|
+
log(HOOK, line);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (decision.observation) {
|
|
400
|
+
try {
|
|
401
|
+
const sessionId = getField(stdin, 'session_id') || getField(stdin, 'claude_session_id') || '';
|
|
402
|
+
// RFC-040 Operator Multi-Project — Slice 5 (C5): resolves under the
|
|
403
|
+
// project root walked up from cwd (falls back to cwd when unregistered).
|
|
404
|
+
const memoryBaseDir = resolveAgentMemoryBaseDir(process.cwd());
|
|
405
|
+
appendObservation(memoryBaseDir, 'main', {
|
|
406
|
+
timestamp: new Date().toISOString(),
|
|
407
|
+
session_id: sessionId,
|
|
408
|
+
...decision.observation,
|
|
409
|
+
});
|
|
410
|
+
} catch {
|
|
411
|
+
// fail-open — never block on telemetry
|
|
412
|
+
}
|
|
161
413
|
}
|
|
162
414
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
decision: 'deny',
|
|
167
|
-
reason: 'Workflow Guard: No active workflow. Direct implementation is not allowed.',
|
|
168
|
-
context: [
|
|
169
|
-
'You MUST start a workflow before writing code:',
|
|
170
|
-
'1. mcp__orchestrator-tools__detectWorkflow({ prompt: "..." })',
|
|
171
|
-
'2. mcp__orchestrator-tools__startWorkflow({ workflowType: "...", prompt: "..." })',
|
|
172
|
-
'3. Follow the nextStep returned by startWorkflow',
|
|
173
|
-
'',
|
|
174
|
-
'The workflow-guard hook blocks all writes to src/ and tests/ without an active workflow.',
|
|
175
|
-
].join('\n'),
|
|
176
|
-
});
|
|
415
|
+
if (decision.emitOutput && decision.output) {
|
|
416
|
+
outputPreToolUse(decision.output);
|
|
417
|
+
}
|
|
177
418
|
}
|
|
178
419
|
|
|
179
420
|
main().catch(() => process.exit(0));
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|resume",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{ "type": "command", "command": "hooks/session-start.mjs", "timeout": 10000 },
|
|
8
|
+
{ "type": "command", "command": "hooks/sprint-registry.mjs", "timeout": 5000 }
|
|
9
|
+
]
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"UserPromptSubmit": [
|
|
13
|
+
{
|
|
14
|
+
"matcher": "",
|
|
15
|
+
"hooks": [{ "type": "command", "command": "hooks/user-prompt.mjs", "timeout": 10000 }]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"PreToolUse": [
|
|
19
|
+
{
|
|
20
|
+
"matcher": "mcp__orchestrator-tools__advancePhase",
|
|
21
|
+
"hooks": [{ "type": "command", "command": "hooks/gate-guardian.mjs", "timeout": 15000 }]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"matcher": "Write|Edit",
|
|
25
|
+
"hooks": [{ "type": "command", "command": "hooks/workflow-guard.mjs", "timeout": 10000 }]
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"SubagentStart": [
|
|
29
|
+
{
|
|
30
|
+
"matcher": "implementer|specifier|planner|task-generator|reviewer|researcher",
|
|
31
|
+
"hooks": [{ "type": "command", "command": "hooks/subagent-start.mjs", "timeout": 10000 }]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"SubagentStop": [
|
|
35
|
+
{
|
|
36
|
+
"matcher": "implementer|specifier|planner|task-generator|reviewer|researcher",
|
|
37
|
+
"hooks": [{ "type": "command", "command": "hooks/subagent-stop.mjs", "timeout": 30000 }]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"Stop": [
|
|
41
|
+
{
|
|
42
|
+
"matcher": "",
|
|
43
|
+
"hooks": [{ "type": "command", "command": "hooks/dangling-guard.mjs", "timeout": 15000 }]
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"PostToolUse": [
|
|
47
|
+
{
|
|
48
|
+
"matcher": "",
|
|
49
|
+
"hooks": [{ "type": "command", "command": "hooks/mailbox-listener.mjs", "timeout": 5000 }]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"matcher": "mcp__orchestrator-tools__advancePhase",
|
|
53
|
+
"hooks": [{ "type": "command", "command": "hooks/post-implement.mjs", "timeout": 15000 }]
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"PostCompact": [
|
|
57
|
+
{
|
|
58
|
+
"matcher": "",
|
|
59
|
+
"hooks": [{ "type": "command", "command": "hooks/post-compact.mjs", "timeout": 5000 }]
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
}
|