@orchestrator-claude/definitions 3.31.1 → 3.32.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/agents/docs-guardian.md +13 -73
- package/hooks/dangling-guard.ts +148 -10
- package/hooks/gate-guardian.ts +177 -40
- package/hooks/lib/api-client.ts +419 -38
- package/hooks/lib/capability-scanner.ts +640 -0
- package/hooks/lib/finops-parser.ts +247 -0
- package/hooks/lib/gate-logic.ts +7 -7
- package/hooks/lib/git-checkpoint.ts +20 -20
- package/hooks/lib/observations-helper.ts +104 -0
- package/hooks/lib/sprint-context.ts +102 -0
- package/hooks/mailbox-listener.ts +139 -25
- package/hooks/post-compact.ts +137 -11
- package/hooks/post-implement.ts +88 -0
- package/hooks/session-start.ts +416 -47
- package/hooks/sprint-registry.ts +6 -5
- package/hooks/subagent-start.ts +27 -15
- package/hooks/subagent-stop.ts +72 -13
- package/hooks/user-prompt.ts +43 -15
- package/hooks/workflow-guard.ts +96 -37
- package/manifest.json +53 -29
- package/package.json +1 -1
- package/skills/backlog-synthesis/SKILL.md +1 -1
- package/skills/workflow-status/SKILL.md +1 -1
- package/hooks/pre-agent-dispatch.ts +0 -57
- package/hooks/verification-before-completion.ts +0 -97
package/hooks/subagent-stop.ts
CHANGED
|
@@ -14,44 +14,103 @@ import {
|
|
|
14
14
|
getField,
|
|
15
15
|
getActiveWorkflow,
|
|
16
16
|
outputContext,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
readInvocationState,
|
|
18
|
+
consumeInvocationState,
|
|
19
|
+
completeAgentInvocation,
|
|
20
|
+
isInvocationStateStale,
|
|
21
|
+
} from './lib/api-client.js';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { createCheckpointIfNeeded } from './lib/git-checkpoint.js';
|
|
24
|
+
import { appendObservation } from './lib/observations-helper.js';
|
|
19
25
|
|
|
20
|
-
const HOOK =
|
|
26
|
+
const HOOK = 'SUBAGENT-STOP';
|
|
21
27
|
|
|
22
28
|
async function main(): Promise<void> {
|
|
23
29
|
const stdin = await readStdin();
|
|
24
|
-
log(HOOK,
|
|
30
|
+
log(HOOK, 'SubagentStop triggered');
|
|
25
31
|
|
|
26
32
|
const agentType =
|
|
27
|
-
getField(stdin,
|
|
28
|
-
getField(stdin, "tool_input.subagent_type") ||
|
|
29
|
-
"unknown";
|
|
33
|
+
getField(stdin, 'agent_type') || getField(stdin, 'tool_input.subagent_type') || 'unknown';
|
|
30
34
|
|
|
31
35
|
const workflow = await getActiveWorkflow();
|
|
32
36
|
if (!workflow) {
|
|
33
|
-
log(HOOK,
|
|
37
|
+
log(HOOK, 'No active workflow, skipping');
|
|
34
38
|
return;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
log(HOOK, `agent=${agentType} workflow=${workflow.id} phase=${workflow.currentPhase}`);
|
|
38
42
|
|
|
39
|
-
// 1. Invocation tracking — agent self-
|
|
40
|
-
|
|
43
|
+
// 1. Invocation tracking safety net — complete via state file if agent did not self-report via MCP
|
|
44
|
+
const invocationState = readInvocationState(workflow.id, agentType);
|
|
45
|
+
if (invocationState) {
|
|
46
|
+
log(
|
|
47
|
+
HOOK,
|
|
48
|
+
`COMPLETE safety-net: found state file for agent=${agentType} invocationId=${invocationState.invocationId}`
|
|
49
|
+
);
|
|
50
|
+
if (isInvocationStateStale(invocationState)) {
|
|
51
|
+
log(
|
|
52
|
+
HOOK,
|
|
53
|
+
`COMPLETE safety-net: state file is stale (>4h) for invocationId=${invocationState.invocationId} — deleting without completing`
|
|
54
|
+
);
|
|
55
|
+
consumeInvocationState(workflow.id, agentType);
|
|
56
|
+
} else {
|
|
57
|
+
const completed = await completeAgentInvocation(
|
|
58
|
+
invocationState.invocationId,
|
|
59
|
+
'completed',
|
|
60
|
+
`Safety-net completion: agent ${agentType} finished phase ${workflow.currentPhase}`
|
|
61
|
+
);
|
|
62
|
+
if (completed) {
|
|
63
|
+
consumeInvocationState(workflow.id, agentType);
|
|
64
|
+
log(
|
|
65
|
+
HOOK,
|
|
66
|
+
`COMPLETE safety-net: invocationId=${invocationState.invocationId} marked complete, state file deleted`
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
log(
|
|
70
|
+
HOOK,
|
|
71
|
+
`COMPLETE safety-net: POST failed for invocationId=${invocationState.invocationId} (fail-open, continuing)`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
log(
|
|
77
|
+
HOOK,
|
|
78
|
+
`COMPLETE: agent=${agentType} — no state file (agent self-reported via MCP or start hook skipped)`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
41
81
|
|
|
42
82
|
// 2. Git checkpoint if phase changed
|
|
43
83
|
const commitHash = await createCheckpointIfNeeded(workflow.id, workflow.currentPhase);
|
|
44
84
|
|
|
45
85
|
// 3. Guide main conversation — NO ping-pong, main IS the orchestrator
|
|
46
|
-
const parts: string[] = [
|
|
86
|
+
const parts: string[] = [
|
|
87
|
+
`[ORCHESTRATOR] Agent '${agentType}' completed for workflow ${workflow.id}.`,
|
|
88
|
+
];
|
|
47
89
|
|
|
48
90
|
if (commitHash) {
|
|
49
91
|
parts.push(`Checkpoint: ${commitHash}.`);
|
|
50
92
|
}
|
|
51
93
|
|
|
52
|
-
parts.push(
|
|
94
|
+
parts.push('Proceed to gate check via AskUserQuestion before advancing phase.');
|
|
95
|
+
|
|
96
|
+
outputContext('SubagentStop', parts.join(' '));
|
|
53
97
|
|
|
54
|
-
|
|
98
|
+
// 4. Write Tier B observation (fail-open)
|
|
99
|
+
try {
|
|
100
|
+
const sessionId = getField(stdin, 'session_id') || getField(stdin, 'claude_session_id') || '';
|
|
101
|
+
const memoryBaseDir = join(process.cwd(), '.claude', 'agent-memory');
|
|
102
|
+
appendObservation(memoryBaseDir, agentType, {
|
|
103
|
+
timestamp: new Date().toISOString(),
|
|
104
|
+
session_id: sessionId,
|
|
105
|
+
phase: workflow.currentPhase,
|
|
106
|
+
workflow_id: workflow.id,
|
|
107
|
+
summary: `Agent ${agentType} completed phase ${workflow.currentPhase}${commitHash ? ` (checkpoint: ${commitHash})` : ''}`,
|
|
108
|
+
tags: [`agent:${agentType}`, `phase:${workflow.currentPhase}`],
|
|
109
|
+
});
|
|
110
|
+
log(HOOK, `Observation written for agent=${agentType}`);
|
|
111
|
+
} catch {
|
|
112
|
+
log(HOOK, 'Failed to write observation (fail-open, continuing)');
|
|
113
|
+
}
|
|
55
114
|
}
|
|
56
115
|
|
|
57
116
|
main().catch(() => process.exit(0));
|
package/hooks/user-prompt.ts
CHANGED
|
@@ -12,27 +12,28 @@ import {
|
|
|
12
12
|
getNextAction,
|
|
13
13
|
detectWorkflow,
|
|
14
14
|
outputContext,
|
|
15
|
-
} from
|
|
15
|
+
} from './lib/api-client.js';
|
|
16
|
+
import { readSprintContext } from './lib/sprint-context.js';
|
|
16
17
|
|
|
17
|
-
const HOOK =
|
|
18
|
+
const HOOK = 'USER-PROMPT';
|
|
18
19
|
|
|
19
20
|
async function main(): Promise<void> {
|
|
20
|
-
log(HOOK,
|
|
21
|
+
log(HOOK, 'UserPromptSubmit triggered');
|
|
21
22
|
|
|
22
23
|
const stdin = await readStdin();
|
|
23
24
|
const workflow = await getActiveWorkflow();
|
|
24
25
|
|
|
25
26
|
if (!workflow) {
|
|
26
27
|
// No active workflow — detect and suggest
|
|
27
|
-
const prompt = getField(stdin,
|
|
28
|
+
const prompt = getField(stdin, 'prompt');
|
|
28
29
|
if (!prompt) {
|
|
29
|
-
log(HOOK,
|
|
30
|
+
log(HOOK, 'empty prompt, silence');
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const detection = await detectWorkflow(prompt);
|
|
34
35
|
if (!detection) {
|
|
35
|
-
log(HOOK,
|
|
36
|
+
log(HOOK, 'detect API failed/timeout, silence (fail-open)');
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -40,25 +41,25 @@ async function main(): Promise<void> {
|
|
|
40
41
|
log(HOOK, `detected type=${workflowType} confidence=${confidence} mode=${suggestedMode}`);
|
|
41
42
|
|
|
42
43
|
// Tier 1: interactive_analysis
|
|
43
|
-
if (workflowType ===
|
|
44
|
+
if (workflowType === 'interactive_analysis') {
|
|
44
45
|
outputContext(
|
|
45
|
-
|
|
46
|
+
'UserPromptSubmit',
|
|
46
47
|
[
|
|
47
|
-
|
|
48
|
+
'[ORCHESTRATOR] This looks like an analytical/design request. How would you like to proceed?',
|
|
48
49
|
'(a) Continue as a free conversation (no workflow)',
|
|
49
50
|
'(b) Start an interactive_analysis workflow for structured exploration (cyclic phases: research → discuss → analyze)',
|
|
50
|
-
|
|
51
|
+
'',
|
|
51
52
|
'If you choose (b), I will call: mcp__orchestrator-tools__startWorkflow({ type: "interactive_analysis", prompt: "..." })',
|
|
52
|
-
].join(
|
|
53
|
+
].join('\n')
|
|
53
54
|
);
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
// Tier 2: high confidence (>= 0.7)
|
|
58
59
|
if (confidence >= 0.7) {
|
|
59
|
-
const mode = suggestedMode ||
|
|
60
|
+
const mode = suggestedMode || 'standard';
|
|
60
61
|
outputContext(
|
|
61
|
-
|
|
62
|
+
'UserPromptSubmit',
|
|
62
63
|
`[ORCHESTRATOR] Detected: ${workflowType} workflow (confidence: ${confidence}, suggested mode: ${mode}).\nTo start: mcp__orchestrator-tools__startWorkflow({ type: "${workflowType}", prompt: "...", mode: "${mode}" })\nOr continue without a workflow if this is exploratory.`
|
|
63
64
|
);
|
|
64
65
|
return;
|
|
@@ -67,7 +68,7 @@ async function main(): Promise<void> {
|
|
|
67
68
|
// Tier 3: medium confidence (0.5-0.69)
|
|
68
69
|
if (confidence >= 0.5) {
|
|
69
70
|
outputContext(
|
|
70
|
-
|
|
71
|
+
'UserPromptSubmit',
|
|
71
72
|
`[ORCHESTRATOR] Hint: this might be a ${workflowType} task. Consider starting a workflow if you plan to write code.`
|
|
72
73
|
);
|
|
73
74
|
return;
|
|
@@ -78,6 +79,33 @@ async function main(): Promise<void> {
|
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
// Active workflow — check for brainstorm context injection (RFC-027)
|
|
83
|
+
const prompt = getField(stdin, 'prompt');
|
|
84
|
+
if (/brainstorm/i.test(prompt)) {
|
|
85
|
+
try {
|
|
86
|
+
const sprintCtx = readSprintContext(workflow.id);
|
|
87
|
+
if (sprintCtx) {
|
|
88
|
+
const previousSummary =
|
|
89
|
+
sprintCtx.previousSprints.length === 0
|
|
90
|
+
? 'none'
|
|
91
|
+
: sprintCtx.previousSprints.map((s) => `S${s.index}(${s.status})`).join(', ');
|
|
92
|
+
|
|
93
|
+
// Context format: keep in sync with brainstormContextBranch in user-prompt-brainstorm.test.ts
|
|
94
|
+
const brainstormContext = [
|
|
95
|
+
`[PARENT-DOC-CONTEXT] parentDocument=${sprintCtx.parentDocument} | sprint=${sprintCtx.sprintIndex} | scope=${sprintCtx.sprintScope} | previousSprints=${previousSummary}`,
|
|
96
|
+
`[ORCHESTRATOR] Include parent document context in this brainstorm. Read the RFC for background.`,
|
|
97
|
+
].join('\n');
|
|
98
|
+
|
|
99
|
+
log(HOOK, `brainstorm detected, injecting sprint context for workflow=${workflow.id}`);
|
|
100
|
+
outputContext('UserPromptSubmit', brainstormContext);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// fail-open: if readSprintContext throws unexpectedly, continue to standard injection
|
|
105
|
+
log(HOOK, 'brainstorm context read failed, falling through to standard injection');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
81
109
|
// Active workflow — inject state
|
|
82
110
|
const action = await getNextAction(workflow.id);
|
|
83
111
|
let context: string;
|
|
@@ -89,7 +117,7 @@ async function main(): Promise<void> {
|
|
|
89
117
|
}
|
|
90
118
|
|
|
91
119
|
log(HOOK, `workflow=${workflow.id} hasAction=${action?.hasAction}`);
|
|
92
|
-
outputContext(
|
|
120
|
+
outputContext('UserPromptSubmit', context);
|
|
93
121
|
}
|
|
94
122
|
|
|
95
123
|
main().catch(() => process.exit(0));
|
package/hooks/workflow-guard.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* workflow-guard.ts — PreToolUse[Write|Edit] hook (RFC-022 Phase 2)
|
|
3
3
|
* Replaces workflow-guard.sh
|
|
4
|
+
*
|
|
4
5
|
* Blocks writes to src/ and tests/ without active workflow.
|
|
5
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* Allow paths (in evaluation order):
|
|
8
|
+
* 1. SKIP_WORKFLOW_GUARD=true (explicit bypass, all paths)
|
|
9
|
+
* 2. Non-guarded path (file outside src/ or tests/)
|
|
10
|
+
* 3. Config/doc file extension (.json|.yaml|.yml|.md|.env*)
|
|
11
|
+
* 4. Active workflow + mode='quick' (main agent writes allowed by design)
|
|
12
|
+
* 5. Active workflow + mode='interactive' (writes unexpected, but allowed)
|
|
13
|
+
* 6. Active workflow + mode='direct' (ADR-017 Phase 3 — tracked via startDirectExecution)
|
|
14
|
+
* 7. Active workflow + caller is sub-agent (agent_id present in stdin)
|
|
15
|
+
* 8. Active workflow + SKIP_SUBAGENT_GUARD=true (DEPRECATED — ADR-013 §6.5.3, telemetered to observations.yaml)
|
|
16
|
+
* Deny paths:
|
|
17
|
+
* - Active workflow + standard/full/legacy mode + main agent direct write (no agent_id, no bypass)
|
|
18
|
+
* - No active workflow
|
|
6
19
|
*/
|
|
7
20
|
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
|
|
8
23
|
import {
|
|
9
24
|
log,
|
|
10
25
|
readStdin,
|
|
@@ -12,35 +27,39 @@ import {
|
|
|
12
27
|
getActiveWorkflow,
|
|
13
28
|
getWorkflowMode,
|
|
14
29
|
outputPreToolUse,
|
|
15
|
-
} from
|
|
30
|
+
} from './lib/api-client.js';
|
|
31
|
+
import { appendObservation } from './lib/observations-helper.js';
|
|
16
32
|
|
|
17
|
-
const HOOK =
|
|
33
|
+
const HOOK = 'WORKFLOW-GUARD';
|
|
18
34
|
|
|
19
35
|
async function main(): Promise<void> {
|
|
20
36
|
const stdin = await readStdin();
|
|
21
|
-
const filePath =
|
|
37
|
+
const filePath =
|
|
38
|
+
getField(stdin, 'tool_input.file_path') ||
|
|
39
|
+
getField(stdin, 'file_path') ||
|
|
40
|
+
getField(stdin, 'input.file_path');
|
|
22
41
|
|
|
23
42
|
log(HOOK, `file_path=${filePath}`);
|
|
24
43
|
|
|
25
44
|
// Explicit bypass
|
|
26
|
-
if (process.env.SKIP_WORKFLOW_GUARD ===
|
|
27
|
-
log(HOOK,
|
|
45
|
+
if (process.env.SKIP_WORKFLOW_GUARD === 'true') {
|
|
46
|
+
log(HOOK, 'ALLOW (SKIP_WORKFLOW_GUARD=true)');
|
|
28
47
|
outputPreToolUse({
|
|
29
|
-
decision:
|
|
30
|
-
context:
|
|
48
|
+
decision: 'allow',
|
|
49
|
+
context: 'Workflow guard bypassed via SKIP_WORKFLOW_GUARD=true.',
|
|
31
50
|
});
|
|
32
51
|
return;
|
|
33
52
|
}
|
|
34
53
|
|
|
35
54
|
// Only guard src/ and tests/ paths
|
|
36
|
-
if (!filePath.includes(
|
|
37
|
-
log(HOOK,
|
|
55
|
+
if (!filePath.includes('/src/') && !filePath.includes('/tests/')) {
|
|
56
|
+
log(HOOK, 'ALLOW (non-guarded path)');
|
|
38
57
|
return;
|
|
39
58
|
}
|
|
40
59
|
|
|
41
60
|
// Allow config/doc files
|
|
42
61
|
if (/\.(json|ya?ml|md|env(\..*)?)$/.test(filePath)) {
|
|
43
|
-
log(HOOK,
|
|
62
|
+
log(HOOK, 'ALLOW (config/doc file)');
|
|
44
63
|
return;
|
|
45
64
|
}
|
|
46
65
|
|
|
@@ -49,71 +68,111 @@ async function main(): Promise<void> {
|
|
|
49
68
|
|
|
50
69
|
if (workflow) {
|
|
51
70
|
const mode = await getWorkflowMode(workflow.id);
|
|
52
|
-
log(HOOK, `workflow=${workflow.id} mode=${mode ||
|
|
71
|
+
log(HOOK, `workflow=${workflow.id} mode=${mode || 'legacy'}`);
|
|
53
72
|
|
|
54
|
-
if (mode ===
|
|
73
|
+
if (mode === 'quick') {
|
|
55
74
|
outputPreToolUse({
|
|
56
|
-
decision:
|
|
75
|
+
decision: 'allow',
|
|
57
76
|
context: `Active workflow: ${workflow.id} (mode: quick). Direct writes allowed in quick mode.`,
|
|
58
77
|
});
|
|
59
78
|
return;
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
if (mode ===
|
|
81
|
+
if (mode === 'interactive') {
|
|
63
82
|
outputPreToolUse({
|
|
64
|
-
decision:
|
|
83
|
+
decision: 'allow',
|
|
65
84
|
context: `Active workflow: ${workflow.id} (mode: interactive). Note: code writes are unexpected in interactive/analysis workflows.`,
|
|
66
85
|
});
|
|
67
86
|
return;
|
|
68
87
|
}
|
|
69
88
|
|
|
89
|
+
// ADR-017 Phase 3: direct execution mode — main agent writes without sub-agent required
|
|
90
|
+
if (mode === 'direct') {
|
|
91
|
+
log(HOOK, `ALLOW (executionMode=direct, workflow: ${workflow.id})`);
|
|
92
|
+
outputPreToolUse({
|
|
93
|
+
decision: 'allow',
|
|
94
|
+
context: `Active workflow: ${workflow.id} (executionMode: direct). Direct writes allowed — record will be tracked via startDirectExecution/completeDirectExecution.`,
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
70
99
|
// Check if running inside a sub-agent
|
|
71
|
-
const agentId = getField(stdin,
|
|
100
|
+
const agentId = getField(stdin, 'agent_id');
|
|
72
101
|
if (agentId) {
|
|
73
|
-
const agentType = getField(stdin,
|
|
74
|
-
log(HOOK, `ALLOW (sub-agent: ${agentType ||
|
|
102
|
+
const agentType = getField(stdin, 'agent_type');
|
|
103
|
+
log(HOOK, `ALLOW (sub-agent: ${agentType || 'unknown'}, workflow: ${workflow.id})`);
|
|
75
104
|
outputPreToolUse({
|
|
76
|
-
decision:
|
|
77
|
-
context: `Active workflow: ${workflow.id}. Sub-agent ${agentType ||
|
|
105
|
+
decision: 'allow',
|
|
106
|
+
context: `Active workflow: ${workflow.id}. Sub-agent ${agentType || 'unknown'} write allowed.`,
|
|
78
107
|
});
|
|
79
108
|
return;
|
|
80
109
|
}
|
|
81
110
|
|
|
82
111
|
// Main agent writing directly — check SKIP_SUBAGENT_GUARD
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
// DEPRECATED per ADR-013 §6.5.3 — preferred replacements are mode='quick'
|
|
113
|
+
// (single-phase, main-agent writes) or mode='direct' (ADR-017 Phase 3,
|
|
114
|
+
// multi-phase main-agent writes with startDirectExecution audit trail).
|
|
115
|
+
// Each bypass is recorded to .claude/agent-memory/main/observations.yaml
|
|
116
|
+
// (RFC-027 Sprint 3) so usage can be measured before full removal.
|
|
117
|
+
if (process.env.SKIP_SUBAGENT_GUARD === 'true') {
|
|
118
|
+
log(HOOK, `ALLOW (SKIP_SUBAGENT_GUARD=true, workflow: ${workflow.id}) [DEPRECATED]`);
|
|
119
|
+
|
|
120
|
+
// Fail-open observation for cross-session telemetry
|
|
121
|
+
try {
|
|
122
|
+
const sessionId =
|
|
123
|
+
getField(stdin, 'session_id') || getField(stdin, 'claude_session_id') || '';
|
|
124
|
+
const memoryBaseDir = join(process.cwd(), '.claude', 'agent-memory');
|
|
125
|
+
appendObservation(memoryBaseDir, 'main', {
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
session_id: sessionId,
|
|
128
|
+
phase: workflow.currentPhase || 'unknown',
|
|
129
|
+
workflow_id: workflow.id,
|
|
130
|
+
summary: `SKIP_SUBAGENT_GUARD bypass triggered. file=${filePath} mode=${mode || 'legacy'}. Prefer mode:'quick' or mode:'direct' (ADR-013 §6.5.3).`,
|
|
131
|
+
tags: ['skip_subagent_guard_bypass', 'deprecated_bypass', `mode:${mode || 'legacy'}`],
|
|
132
|
+
});
|
|
133
|
+
} catch {
|
|
134
|
+
// fail-open — never block on telemetry
|
|
135
|
+
}
|
|
136
|
+
|
|
85
137
|
outputPreToolUse({
|
|
86
|
-
decision:
|
|
87
|
-
context:
|
|
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'),
|
|
88
147
|
});
|
|
89
148
|
return;
|
|
90
149
|
}
|
|
91
150
|
|
|
92
151
|
// Main agent direct write — DENY
|
|
93
|
-
log(HOOK, `DENY (main agent direct write, mode=${mode ||
|
|
152
|
+
log(HOOK, `DENY (main agent direct write, mode=${mode || 'legacy'})`);
|
|
94
153
|
outputPreToolUse({
|
|
95
|
-
decision:
|
|
154
|
+
decision: 'deny',
|
|
96
155
|
reason:
|
|
97
|
-
|
|
156
|
+
'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.',
|
|
98
157
|
context:
|
|
99
|
-
|
|
158
|
+
'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).',
|
|
100
159
|
});
|
|
101
160
|
return;
|
|
102
161
|
}
|
|
103
162
|
|
|
104
163
|
// No active workflow — DENY
|
|
105
|
-
log(HOOK,
|
|
164
|
+
log(HOOK, 'DENY (no active workflow)');
|
|
106
165
|
outputPreToolUse({
|
|
107
|
-
decision:
|
|
108
|
-
reason:
|
|
166
|
+
decision: 'deny',
|
|
167
|
+
reason: 'Workflow Guard: No active workflow. Direct implementation is not allowed.',
|
|
109
168
|
context: [
|
|
110
|
-
|
|
169
|
+
'You MUST start a workflow before writing code:',
|
|
111
170
|
'1. mcp__orchestrator-tools__detectWorkflow({ prompt: "..." })',
|
|
112
171
|
'2. mcp__orchestrator-tools__startWorkflow({ workflowType: "...", prompt: "..." })',
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
].join(
|
|
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'),
|
|
117
176
|
});
|
|
118
177
|
}
|
|
119
178
|
|
package/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "orchestrator-definitions/v1",
|
|
3
3
|
"namespace": "orchestrator",
|
|
4
|
-
"version": "3.
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "3.32.0",
|
|
5
|
+
"generatedAt": "2026-06-25T19:16:00.614Z",
|
|
6
6
|
"definitions": {
|
|
7
7
|
"agents": [
|
|
8
8
|
{
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"slug": "docs-guardian",
|
|
66
66
|
"file": "agents/docs-guardian.md",
|
|
67
67
|
"version": "1.0.0",
|
|
68
|
-
"checksum": "sha256:
|
|
68
|
+
"checksum": "sha256:b9d5b1a102a56f7927d43ccf3cb64332789c61a8c53ad1395bf1d6211d617c01",
|
|
69
69
|
"supersedes": null,
|
|
70
70
|
"metadata": {}
|
|
71
71
|
},
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
"slug": "backlog-synthesis",
|
|
212
212
|
"file": "skills/backlog-synthesis/SKILL.md",
|
|
213
213
|
"version": "1.0.0",
|
|
214
|
-
"checksum": "sha256:
|
|
214
|
+
"checksum": "sha256:e077bbf16cfbb6a79fbda114df884066b57b93fff83d5d0bffdace46f62ae964",
|
|
215
215
|
"supersedes": null,
|
|
216
216
|
"metadata": {}
|
|
217
217
|
},
|
|
@@ -403,7 +403,7 @@
|
|
|
403
403
|
"slug": "workflow-status",
|
|
404
404
|
"file": "skills/workflow-status/SKILL.md",
|
|
405
405
|
"version": "1.0.0",
|
|
406
|
-
"checksum": "sha256:
|
|
406
|
+
"checksum": "sha256:7390c8d966110f5e0f8935b61363c6252cde5674423355742a5ebe8c76b790ad",
|
|
407
407
|
"supersedes": null,
|
|
408
408
|
"metadata": {}
|
|
409
409
|
}
|
|
@@ -413,7 +413,7 @@
|
|
|
413
413
|
"slug": "dangling-guard",
|
|
414
414
|
"file": "hooks/dangling-guard.ts",
|
|
415
415
|
"version": "1.0.0",
|
|
416
|
-
"checksum": "sha256:
|
|
416
|
+
"checksum": "sha256:795ab6cb17039f6fd503d484a49a9bfe1b8a4b735098a9499bd679e534296da1",
|
|
417
417
|
"supersedes": null,
|
|
418
418
|
"metadata": {}
|
|
419
419
|
},
|
|
@@ -421,7 +421,7 @@
|
|
|
421
421
|
"slug": "gate-guardian",
|
|
422
422
|
"file": "hooks/gate-guardian.ts",
|
|
423
423
|
"version": "1.0.0",
|
|
424
|
-
"checksum": "sha256:
|
|
424
|
+
"checksum": "sha256:d4098999b3f4cf28f9bc309f6355a00aaa4d9df90b66c17ec32f27bd20d55bf9",
|
|
425
425
|
"supersedes": null,
|
|
426
426
|
"metadata": {}
|
|
427
427
|
},
|
|
@@ -429,7 +429,23 @@
|
|
|
429
429
|
"slug": "api-client",
|
|
430
430
|
"file": "hooks/lib/api-client.ts",
|
|
431
431
|
"version": "1.0.0",
|
|
432
|
-
"checksum": "sha256:
|
|
432
|
+
"checksum": "sha256:fdac374cfabb58865d676e24157d15bc9d81cdabf62a5a6d28c66e9a95fdf69e",
|
|
433
|
+
"supersedes": null,
|
|
434
|
+
"metadata": {}
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
"slug": "capability-scanner",
|
|
438
|
+
"file": "hooks/lib/capability-scanner.ts",
|
|
439
|
+
"version": "1.0.0",
|
|
440
|
+
"checksum": "sha256:f366c0afdcc06224d2e80566b3e5eaf6e06cf9af3b80c10d3c9acae4690f1c97",
|
|
441
|
+
"supersedes": null,
|
|
442
|
+
"metadata": {}
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"slug": "finops-parser",
|
|
446
|
+
"file": "hooks/lib/finops-parser.ts",
|
|
447
|
+
"version": "1.0.0",
|
|
448
|
+
"checksum": "sha256:4acdcd904dd956c0375b11ebfbadca904b51635033c64d10f96f721235247c55",
|
|
433
449
|
"supersedes": null,
|
|
434
450
|
"metadata": {}
|
|
435
451
|
},
|
|
@@ -437,7 +453,7 @@
|
|
|
437
453
|
"slug": "gate-logic",
|
|
438
454
|
"file": "hooks/lib/gate-logic.ts",
|
|
439
455
|
"version": "1.0.0",
|
|
440
|
-
"checksum": "sha256:
|
|
456
|
+
"checksum": "sha256:d3fe80209e5a8267214c314ef7f6fad474764fddb9d398c660e33071ee604a7a",
|
|
441
457
|
"supersedes": null,
|
|
442
458
|
"metadata": {}
|
|
443
459
|
},
|
|
@@ -445,7 +461,23 @@
|
|
|
445
461
|
"slug": "git-checkpoint",
|
|
446
462
|
"file": "hooks/lib/git-checkpoint.ts",
|
|
447
463
|
"version": "1.0.0",
|
|
448
|
-
"checksum": "sha256:
|
|
464
|
+
"checksum": "sha256:9a001448abbb96d974af14db0a93763426a6409aaa68fb338547795f493ac636",
|
|
465
|
+
"supersedes": null,
|
|
466
|
+
"metadata": {}
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"slug": "observations-helper",
|
|
470
|
+
"file": "hooks/lib/observations-helper.ts",
|
|
471
|
+
"version": "1.0.0",
|
|
472
|
+
"checksum": "sha256:76b531c227e616582a0a2989d19e60e3fe86bfc02a2dee12e32a8b7acfe18faf",
|
|
473
|
+
"supersedes": null,
|
|
474
|
+
"metadata": {}
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
"slug": "sprint-context",
|
|
478
|
+
"file": "hooks/lib/sprint-context.ts",
|
|
479
|
+
"version": "1.0.0",
|
|
480
|
+
"checksum": "sha256:fc681215c9a41615d8d902e0a39f18406d6e130780d7408ed264fbfc9eab8f1c",
|
|
449
481
|
"supersedes": null,
|
|
450
482
|
"metadata": {}
|
|
451
483
|
},
|
|
@@ -453,7 +485,7 @@
|
|
|
453
485
|
"slug": "mailbox-listener",
|
|
454
486
|
"file": "hooks/mailbox-listener.ts",
|
|
455
487
|
"version": "1.0.0",
|
|
456
|
-
"checksum": "sha256:
|
|
488
|
+
"checksum": "sha256:8112646bf356b209e986af38d2f9e8f71a01a38947540af76611ccc0725a718d",
|
|
457
489
|
"supersedes": null,
|
|
458
490
|
"metadata": {}
|
|
459
491
|
},
|
|
@@ -461,15 +493,15 @@
|
|
|
461
493
|
"slug": "post-compact",
|
|
462
494
|
"file": "hooks/post-compact.ts",
|
|
463
495
|
"version": "1.0.0",
|
|
464
|
-
"checksum": "sha256:
|
|
496
|
+
"checksum": "sha256:966ca9cf9dc32fc1b31e28fb36fdfc4732cd785bbbef7909de6d10ab1a79aec9",
|
|
465
497
|
"supersedes": null,
|
|
466
498
|
"metadata": {}
|
|
467
499
|
},
|
|
468
500
|
{
|
|
469
|
-
"slug": "
|
|
470
|
-
"file": "hooks/
|
|
501
|
+
"slug": "post-implement",
|
|
502
|
+
"file": "hooks/post-implement.ts",
|
|
471
503
|
"version": "1.0.0",
|
|
472
|
-
"checksum": "sha256:
|
|
504
|
+
"checksum": "sha256:132c474d4e31ff76abaff10032b4389e015b6fe6e44d03becbe98a87bb89bbbd",
|
|
473
505
|
"supersedes": null,
|
|
474
506
|
"metadata": {}
|
|
475
507
|
},
|
|
@@ -477,7 +509,7 @@
|
|
|
477
509
|
"slug": "session-start",
|
|
478
510
|
"file": "hooks/session-start.ts",
|
|
479
511
|
"version": "1.0.0",
|
|
480
|
-
"checksum": "sha256:
|
|
512
|
+
"checksum": "sha256:c8adea2c3cdfd0eebfe488d81260fb0eed26e88a231586b4af6174efebe87781",
|
|
481
513
|
"supersedes": null,
|
|
482
514
|
"metadata": {}
|
|
483
515
|
},
|
|
@@ -485,7 +517,7 @@
|
|
|
485
517
|
"slug": "sprint-registry",
|
|
486
518
|
"file": "hooks/sprint-registry.ts",
|
|
487
519
|
"version": "1.0.0",
|
|
488
|
-
"checksum": "sha256:
|
|
520
|
+
"checksum": "sha256:340e40adc35b75c1b09bdc5b9166272098e52db7a91e6d870ba7cd6ccba8b4ab",
|
|
489
521
|
"supersedes": null,
|
|
490
522
|
"metadata": {}
|
|
491
523
|
},
|
|
@@ -493,7 +525,7 @@
|
|
|
493
525
|
"slug": "subagent-start",
|
|
494
526
|
"file": "hooks/subagent-start.ts",
|
|
495
527
|
"version": "1.0.0",
|
|
496
|
-
"checksum": "sha256:
|
|
528
|
+
"checksum": "sha256:1c9ea66ec85376d7a40b943d3b8b44c602faedad809df0419d85d8e454664a24",
|
|
497
529
|
"supersedes": null,
|
|
498
530
|
"metadata": {}
|
|
499
531
|
},
|
|
@@ -501,7 +533,7 @@
|
|
|
501
533
|
"slug": "subagent-stop",
|
|
502
534
|
"file": "hooks/subagent-stop.ts",
|
|
503
535
|
"version": "1.0.0",
|
|
504
|
-
"checksum": "sha256:
|
|
536
|
+
"checksum": "sha256:569408e36602087ccc8ada1f61de3a365fe92a4705332913094a6160cacee753",
|
|
505
537
|
"supersedes": null,
|
|
506
538
|
"metadata": {}
|
|
507
539
|
},
|
|
@@ -509,15 +541,7 @@
|
|
|
509
541
|
"slug": "user-prompt",
|
|
510
542
|
"file": "hooks/user-prompt.ts",
|
|
511
543
|
"version": "1.0.0",
|
|
512
|
-
"checksum": "sha256:
|
|
513
|
-
"supersedes": null,
|
|
514
|
-
"metadata": {}
|
|
515
|
-
},
|
|
516
|
-
{
|
|
517
|
-
"slug": "verification-before-completion",
|
|
518
|
-
"file": "hooks/verification-before-completion.ts",
|
|
519
|
-
"version": "1.0.0",
|
|
520
|
-
"checksum": "sha256:83fc4baf7a5117cd6c0368592fc1ee254d1b138f0a38f88c554c72aa63713279",
|
|
544
|
+
"checksum": "sha256:93e247dffd5609cf9c179f653bf072be19e4c94d11bf420743046795e68cf9d3",
|
|
521
545
|
"supersedes": null,
|
|
522
546
|
"metadata": {}
|
|
523
547
|
},
|
|
@@ -525,7 +549,7 @@
|
|
|
525
549
|
"slug": "workflow-guard",
|
|
526
550
|
"file": "hooks/workflow-guard.ts",
|
|
527
551
|
"version": "1.0.0",
|
|
528
|
-
"checksum": "sha256:
|
|
552
|
+
"checksum": "sha256:b70edb5d65d370ce230b1666171230c58c9f425e9cbb49687f08d236bc164e2c",
|
|
529
553
|
"supersedes": null,
|
|
530
554
|
"metadata": {}
|
|
531
555
|
}
|
package/package.json
CHANGED