@orchestrator-claude/definitions 3.31.0 → 3.31.2
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/adversarial-reviewer.md +1 -1
- package/agents/reviewer.md +3 -1
- 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/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 +52 -36
- package/package.json +1 -1
- package/skills/backlog-synthesis/SKILL.md +1 -1
- package/skills/orchestrator/SKILL.md +17 -0
- package/skills/release/SKILL.md +41 -0
- package/skills/workflow-status/SKILL.md +1 -1
- package/hooks/pre-agent-dispatch.ts +0 -57
- package/hooks/verification-before-completion.ts +0 -97
|
@@ -20,15 +20,24 @@
|
|
|
20
20
|
* - Backward compat: if process.stdin.isTTY → skip stdin read (PostToolUse path)
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
import {
|
|
24
|
+
readdirSync,
|
|
25
|
+
readFileSync,
|
|
26
|
+
unlinkSync,
|
|
27
|
+
renameSync,
|
|
28
|
+
existsSync,
|
|
29
|
+
mkdirSync,
|
|
30
|
+
writeFileSync,
|
|
31
|
+
} from 'node:fs';
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
import { randomUUID } from 'node:crypto';
|
|
34
|
+
import { log, outputContext } from './lib/api-client.js';
|
|
35
|
+
|
|
36
|
+
const HOOK = 'MAILBOX-LISTENER';
|
|
28
37
|
|
|
29
38
|
// --- Types (minimal, no Zod dependency in hooks) ---
|
|
30
39
|
|
|
31
|
-
interface MailboxMessage {
|
|
40
|
+
export interface MailboxMessage {
|
|
32
41
|
id: string;
|
|
33
42
|
from: { role: string; index: number };
|
|
34
43
|
to: { role: string; index: number };
|
|
@@ -53,7 +62,7 @@ async function readStdinPayload(): Promise<Record<string, unknown>> {
|
|
|
53
62
|
for await (const chunk of process.stdin) {
|
|
54
63
|
chunks.push(chunk as Buffer);
|
|
55
64
|
}
|
|
56
|
-
const raw = Buffer.concat(chunks).toString(
|
|
65
|
+
const raw = Buffer.concat(chunks).toString('utf-8').trim();
|
|
57
66
|
if (!raw) return {};
|
|
58
67
|
try {
|
|
59
68
|
return JSON.parse(raw) as Record<string, unknown>;
|
|
@@ -67,13 +76,13 @@ async function readStdinPayload(): Promise<Record<string, unknown>> {
|
|
|
67
76
|
function consumeInbox(
|
|
68
77
|
inboxPath: string,
|
|
69
78
|
quarantinePath: string,
|
|
70
|
-
paneRole: string
|
|
79
|
+
paneRole: string
|
|
71
80
|
): { consumed: MailboxMessage[]; errors: string[] } {
|
|
72
81
|
// List .json files (exclude .tmp- in-flight writes)
|
|
73
82
|
let files: string[];
|
|
74
83
|
try {
|
|
75
84
|
files = readdirSync(inboxPath)
|
|
76
|
-
.filter((f) => f.endsWith(
|
|
85
|
+
.filter((f) => f.endsWith('.json') && !f.startsWith('.tmp-'))
|
|
77
86
|
.sort(); // FIFO by filename (UUID-based, but sorted for determinism)
|
|
78
87
|
} catch {
|
|
79
88
|
return { consumed: [], errors: [] }; // Directory read error — fail silently
|
|
@@ -91,7 +100,7 @@ function consumeInbox(
|
|
|
91
100
|
for (const file of files) {
|
|
92
101
|
const filePath = join(inboxPath, file);
|
|
93
102
|
try {
|
|
94
|
-
const raw = readFileSync(filePath,
|
|
103
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
95
104
|
const parsed = JSON.parse(raw) as MailboxMessage;
|
|
96
105
|
|
|
97
106
|
// Basic validation (no Zod in hooks — keep deps minimal)
|
|
@@ -102,7 +111,10 @@ function consumeInbox(
|
|
|
102
111
|
// Consume: delete after successful parse
|
|
103
112
|
unlinkSync(filePath);
|
|
104
113
|
consumed.push(parsed);
|
|
105
|
-
log(
|
|
114
|
+
log(
|
|
115
|
+
HOOK,
|
|
116
|
+
`Consumed: ${parsed.id} (${parsed.body.type} from ${parsed.from.role}-${parsed.from.index})`
|
|
117
|
+
);
|
|
106
118
|
} catch (err) {
|
|
107
119
|
// Quarantine malformed message
|
|
108
120
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -125,23 +137,21 @@ function consumeInbox(
|
|
|
125
137
|
function buildContext(
|
|
126
138
|
consumed: MailboxMessage[],
|
|
127
139
|
errors: string[],
|
|
128
|
-
paneRole: string
|
|
140
|
+
paneRole: string
|
|
129
141
|
): string | null {
|
|
130
142
|
if (consumed.length === 0 && errors.length === 0) {
|
|
131
143
|
return null;
|
|
132
144
|
}
|
|
133
145
|
|
|
134
|
-
const parts: string[] = [
|
|
135
|
-
`[MAILBOX] ${consumed.length} new message(s) for ${paneRole}:`,
|
|
136
|
-
];
|
|
146
|
+
const parts: string[] = [`[MAILBOX] ${consumed.length} new message(s) for ${paneRole}:`];
|
|
137
147
|
|
|
138
148
|
for (const msg of consumed) {
|
|
139
149
|
const from = `${msg.from.role}-${msg.from.index}`;
|
|
140
|
-
parts.push(
|
|
150
|
+
parts.push('');
|
|
141
151
|
parts.push(`--- Message from ${from} (${msg.body.type}) ---`);
|
|
142
152
|
|
|
143
153
|
switch (msg.body.type) {
|
|
144
|
-
case
|
|
154
|
+
case 'task_assignment':
|
|
145
155
|
parts.push(`Task: ${msg.body.taskId}`);
|
|
146
156
|
parts.push(`Title: ${msg.body.title}`);
|
|
147
157
|
if (msg.body.description) parts.push(`Description: ${msg.body.description}`);
|
|
@@ -153,28 +163,28 @@ function buildContext(
|
|
|
153
163
|
}
|
|
154
164
|
break;
|
|
155
165
|
|
|
156
|
-
case
|
|
166
|
+
case 'task_complete':
|
|
157
167
|
parts.push(`Task: ${msg.body.taskId}`);
|
|
158
168
|
parts.push(`Summary: ${msg.body.summary}`);
|
|
159
169
|
break;
|
|
160
170
|
|
|
161
|
-
case
|
|
171
|
+
case 'status_update':
|
|
162
172
|
parts.push(`Task: ${msg.body.taskId}`);
|
|
163
173
|
parts.push(`Progress: ${msg.body.progress}%`);
|
|
164
174
|
parts.push(`Status: ${msg.body.message}`);
|
|
165
175
|
break;
|
|
166
176
|
|
|
167
|
-
case
|
|
177
|
+
case 'error_report':
|
|
168
178
|
parts.push(`Error: ${msg.body.errorCode}`);
|
|
169
179
|
parts.push(`Message: ${msg.body.message}`);
|
|
170
180
|
if (msg.body.taskId) parts.push(`Task: ${msg.body.taskId}`);
|
|
171
181
|
break;
|
|
172
182
|
|
|
173
|
-
case
|
|
183
|
+
case 'ping':
|
|
174
184
|
parts.push(`Ping received — respond with pong if appropriate.`);
|
|
175
185
|
break;
|
|
176
186
|
|
|
177
|
-
case
|
|
187
|
+
case 'pong':
|
|
178
188
|
parts.push(`Pong received — heartbeat confirmed.`);
|
|
179
189
|
break;
|
|
180
190
|
|
|
@@ -185,14 +195,113 @@ function buildContext(
|
|
|
185
195
|
}
|
|
186
196
|
|
|
187
197
|
if (errors.length > 0) {
|
|
188
|
-
parts.push(
|
|
198
|
+
parts.push('');
|
|
189
199
|
parts.push(`[MAILBOX-WARN] ${errors.length} malformed message(s) quarantined:`);
|
|
190
200
|
for (const e of errors) {
|
|
191
201
|
parts.push(` - ${e}`);
|
|
192
202
|
}
|
|
193
203
|
}
|
|
194
204
|
|
|
195
|
-
return parts.join(
|
|
205
|
+
return parts.join('\n');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// --- Sidecar relay helpers (exported for testability, raw node:fs only) ---
|
|
209
|
+
|
|
210
|
+
/** Injectable deps interface for enumerateSidecarAddresses (enables unit testing without real fs). */
|
|
211
|
+
export interface SidecarRelayDeps {
|
|
212
|
+
readdirSync: (path: string) => string[];
|
|
213
|
+
writeFileSync: (path: string, data: string, encoding: BufferEncoding) => void;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Known sidecar roles (RFC-025 RoleSchema). Used as allowlist in enumerateSidecarAddresses
|
|
218
|
+
* to prevent path-traversal from crafted registry filenames and to guarantee valid role values.
|
|
219
|
+
*/
|
|
220
|
+
const SIDECAR_ROLE_ALLOWLIST: ReadonlySet<string> = new Set([
|
|
221
|
+
'debug-sidecar',
|
|
222
|
+
'test-sidecar',
|
|
223
|
+
'doc-sidecar',
|
|
224
|
+
]);
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Reads registry filenames and returns sidecar addresses (role + index).
|
|
228
|
+
* Reads filenames only — never parses registry JSON content.
|
|
229
|
+
* Returns [] on ENOENT or any read error (best-effort).
|
|
230
|
+
* Only returns entries whose role is in SIDECAR_ROLE_ALLOWLIST (path-traversal defense).
|
|
231
|
+
*/
|
|
232
|
+
export function enumerateSidecarAddresses(
|
|
233
|
+
sprintId: string,
|
|
234
|
+
deps: SidecarRelayDeps = { readdirSync, writeFileSync }
|
|
235
|
+
): { role: string; index: number }[] {
|
|
236
|
+
const registryDir = `/tmp/${sprintId}/registry`;
|
|
237
|
+
let files: string[];
|
|
238
|
+
try {
|
|
239
|
+
files = deps.readdirSync(registryDir);
|
|
240
|
+
} catch {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
const results: { role: string; index: number }[] = [];
|
|
244
|
+
for (const filename of files) {
|
|
245
|
+
if (!filename.endsWith('.json')) continue;
|
|
246
|
+
const base = filename.slice(0, -5); // strip .json
|
|
247
|
+
if (!base.includes('sidecar')) continue;
|
|
248
|
+
const lastDash = base.lastIndexOf('-');
|
|
249
|
+
if (lastDash === -1) continue;
|
|
250
|
+
const indexStr = base.slice(lastDash + 1);
|
|
251
|
+
const index = parseInt(indexStr, 10);
|
|
252
|
+
if (isNaN(index)) continue;
|
|
253
|
+
const role = base.slice(0, lastDash);
|
|
254
|
+
// Allowlist: only accept known sidecar roles (prevents path-traversal, guarantees RoleSchema validity)
|
|
255
|
+
if (!SIDECAR_ROLE_ALLOWLIST.has(role)) continue;
|
|
256
|
+
results.push({ role, index });
|
|
257
|
+
}
|
|
258
|
+
return results;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Relays task_complete messages from consumed list to each sidecar inbox.
|
|
263
|
+
* Best-effort: per-sidecar try/catch — one failure does not block others.
|
|
264
|
+
* Only task_complete messages trigger relay (RF-001).
|
|
265
|
+
* Only runs when paneRole belongs to the orchestrator (role component === 'orchestrator').
|
|
266
|
+
*/
|
|
267
|
+
export function relaySidecarMessages(
|
|
268
|
+
consumed: MailboxMessage[],
|
|
269
|
+
sprintId: string,
|
|
270
|
+
paneRole: string,
|
|
271
|
+
deps: SidecarRelayDeps = { readdirSync, writeFileSync }
|
|
272
|
+
): void {
|
|
273
|
+
// Guard: only the orchestrator pane relays messages (prevents duplicate relay from other panes)
|
|
274
|
+
const roleComponent = paneRole.slice(0, paneRole.lastIndexOf('-'));
|
|
275
|
+
if (roleComponent !== 'orchestrator') return;
|
|
276
|
+
|
|
277
|
+
const taskCompletes = consumed.filter((m) => m.body.type === 'task_complete');
|
|
278
|
+
if (taskCompletes.length === 0) return;
|
|
279
|
+
|
|
280
|
+
const sidecars = enumerateSidecarAddresses(sprintId, deps);
|
|
281
|
+
if (sidecars.length === 0) return;
|
|
282
|
+
|
|
283
|
+
for (const msg of taskCompletes) {
|
|
284
|
+
for (const sidecar of sidecars) {
|
|
285
|
+
try {
|
|
286
|
+
const envelope = {
|
|
287
|
+
id: randomUUID(),
|
|
288
|
+
from: { role: 'orchestrator', index: 0 },
|
|
289
|
+
to: { role: sidecar.role, index: sidecar.index },
|
|
290
|
+
timestamp: new Date().toISOString(),
|
|
291
|
+
body: msg.body,
|
|
292
|
+
};
|
|
293
|
+
const sidecarAddr = `${sidecar.role}-${String(sidecar.index)}`;
|
|
294
|
+
const targetInbox = `/tmp/${sprintId}/mailbox/${sidecarAddr}/inbox`;
|
|
295
|
+
const targetPath = join(targetInbox, `${envelope.id}.json`);
|
|
296
|
+
deps.writeFileSync(targetPath, JSON.stringify(envelope), 'utf-8');
|
|
297
|
+
log(HOOK, `Relayed task_complete to ${sidecarAddr}: ${envelope.id}`);
|
|
298
|
+
} catch (err) {
|
|
299
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
300
|
+
log(HOOK, `Relay failed for ${sidecar.role}-${String(sidecar.index)}: ${errMsg}`);
|
|
301
|
+
// Best-effort: continue to next sidecar
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
196
305
|
}
|
|
197
306
|
|
|
198
307
|
// --- Main ---
|
|
@@ -232,6 +341,9 @@ async function main(): Promise<void> {
|
|
|
232
341
|
return;
|
|
233
342
|
}
|
|
234
343
|
|
|
344
|
+
// Relay task_complete to sidecar inboxes (best-effort, RFC-025 TD-135)
|
|
345
|
+
relaySidecarMessages(consumed, sprintId, paneRole);
|
|
346
|
+
|
|
235
347
|
// Build context string
|
|
236
348
|
const context = buildContext(consumed, errors, paneRole);
|
|
237
349
|
if (!context) {
|
|
@@ -239,8 +351,10 @@ async function main(): Promise<void> {
|
|
|
239
351
|
}
|
|
240
352
|
|
|
241
353
|
// Emit context with the appropriate hook event name
|
|
242
|
-
const effectiveEvent = hookEventName ===
|
|
354
|
+
const effectiveEvent = hookEventName === 'SessionStart' ? 'SessionStart' : 'PostToolUse';
|
|
243
355
|
outputContext(effectiveEvent, context);
|
|
244
356
|
}
|
|
245
357
|
|
|
358
|
+
// main() exits silently when SPRINT_ID/PANE_ROLE are unset (test/import context).
|
|
359
|
+
// No guard needed: the env-var check at the top of main() is the safe gate.
|
|
246
360
|
void main();
|
package/hooks/post-compact.ts
CHANGED
|
@@ -2,23 +2,142 @@
|
|
|
2
2
|
* post-compact.ts — PostCompact hook (RFC-022 Phase 2)
|
|
3
3
|
* NEW hook: re-injects workflow state after context compaction
|
|
4
4
|
* so Claude doesn't lose track of active workflow.
|
|
5
|
+
* Enhanced (RFC-027 S3): also re-injects capability matrix + memory context.
|
|
5
6
|
*/
|
|
6
7
|
|
|
8
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
9
|
+
import { join, dirname } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { parse as parseYaml } from 'yaml';
|
|
12
|
+
import { log, getActiveWorkflow, getNextAction, outputContext } from './lib/api-client.js';
|
|
7
13
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
needsRescan,
|
|
15
|
+
buildCapabilityIndex,
|
|
16
|
+
writeCapabilitiesToVault,
|
|
17
|
+
readCapabilitiesFromVault,
|
|
18
|
+
scanAgentDefinitions,
|
|
19
|
+
formatCapabilityMatrix,
|
|
20
|
+
readMemoryIndex,
|
|
21
|
+
filterRelevantMemories,
|
|
22
|
+
formatMemoryContext,
|
|
23
|
+
} from './lib/capability-scanner.js';
|
|
13
24
|
|
|
14
|
-
const HOOK =
|
|
25
|
+
const HOOK = 'POST-COMPACT';
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = dirname(__filename);
|
|
28
|
+
const PROJECT_ROOT = join(__dirname, '..', '..');
|
|
29
|
+
|
|
30
|
+
// ─── Capability Matrix paths (mirror session-start.ts constants) ────────────
|
|
31
|
+
const HOME = process.env.HOME ?? process.env.USERPROFILE ?? '/home/user';
|
|
32
|
+
const INSTALLED_PLUGINS_PATH = join(HOME, '.claude', 'plugins', 'installed_plugins.json');
|
|
33
|
+
const AGENTS_DIR = join(PROJECT_ROOT, '.claude', 'agents');
|
|
34
|
+
const VAULT_CONFIG_PATH = join(PROJECT_ROOT, 'project-guidelines', '.vault-config.yaml');
|
|
35
|
+
const PROFILES_PATH = join(PROJECT_ROOT, '.claude', 'plugin-profiles.yaml');
|
|
36
|
+
const MEMORY_INDEX_PATH = join(
|
|
37
|
+
PROJECT_ROOT,
|
|
38
|
+
'.claude',
|
|
39
|
+
'agent-memory',
|
|
40
|
+
'orchestrator',
|
|
41
|
+
'memory-index.yaml'
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build [CAPABILITY-MATRIX] + [MEMORY-CONTEXT] lines for injection.
|
|
46
|
+
* Fail-open: returns empty string on any error.
|
|
47
|
+
* Pattern copied from session-start.ts buildCapabilityMatrixContext().
|
|
48
|
+
*/
|
|
49
|
+
function buildCapabilityMatrixContext(): string {
|
|
50
|
+
try {
|
|
51
|
+
let index = readCapabilitiesFromVault(VAULT_CONFIG_PATH);
|
|
52
|
+
const cachedMtime = index?.installedPluginsMtime ?? '';
|
|
53
|
+
|
|
54
|
+
if (!index || needsRescan(INSTALLED_PLUGINS_PATH, cachedMtime)) {
|
|
55
|
+
log(HOOK, 'Capability rescan triggered');
|
|
56
|
+
index = buildCapabilityIndex({
|
|
57
|
+
installedPluginsPath: INSTALLED_PLUGINS_PATH,
|
|
58
|
+
agentsDir: AGENTS_DIR,
|
|
59
|
+
vaultConfigPath: VAULT_CONFIG_PATH,
|
|
60
|
+
profilesPath: PROFILES_PATH,
|
|
61
|
+
});
|
|
62
|
+
writeCapabilitiesToVault(VAULT_CONFIG_PATH, index);
|
|
63
|
+
} else {
|
|
64
|
+
// Agents are always scanned fresh (fast, <10ms)
|
|
65
|
+
index.agents = scanAgentDefinitions(AGENTS_DIR);
|
|
66
|
+
log(HOOK, 'Using cached capability index');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const matrixLine = formatCapabilityMatrix(index);
|
|
70
|
+
|
|
71
|
+
// Memory context
|
|
72
|
+
const memoryEntries = readMemoryIndex(MEMORY_INDEX_PATH);
|
|
73
|
+
let memoryLine = '';
|
|
74
|
+
if (memoryEntries) {
|
|
75
|
+
const filtered = filterRelevantMemories(memoryEntries);
|
|
76
|
+
memoryLine = formatMemoryContext(filtered);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return [matrixLine, memoryLine].filter(Boolean).join('\n');
|
|
80
|
+
} catch (err) {
|
|
81
|
+
log(HOOK, `Capability matrix failed: ${err}`);
|
|
82
|
+
return '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Read version — try project-state.yaml first (RFC-033 Phase 1),
|
|
88
|
+
* fall back to legacy BACKLOG.md regex on any failure.
|
|
89
|
+
*/
|
|
90
|
+
function readVersion(): string {
|
|
91
|
+
// Primary: project-state.yaml
|
|
92
|
+
try {
|
|
93
|
+
const statePath = join(PROJECT_ROOT, 'project-guidelines', 'project-state.yaml');
|
|
94
|
+
if (existsSync(statePath)) {
|
|
95
|
+
const raw = readFileSync(statePath, 'utf-8');
|
|
96
|
+
const doc = parseYaml(raw) as Record<string, unknown>;
|
|
97
|
+
const version = doc?.version as string | undefined;
|
|
98
|
+
if (version && typeof version === 'string' && /^v\d+\.\d+\.\d+/.test(version)) {
|
|
99
|
+
log(HOOK, `project-state.yaml version=${version}`);
|
|
100
|
+
return version;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// fall through to BACKLOG.md
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Fallback: BACKLOG.md regex (original logic, preserved)
|
|
108
|
+
try {
|
|
109
|
+
const backlogPath = join(PROJECT_ROOT, 'project-guidelines', 'BACKLOG.md');
|
|
110
|
+
if (!existsSync(backlogPath)) return 'v?.?.?';
|
|
111
|
+
const content = readFileSync(backlogPath, 'utf-8');
|
|
112
|
+
const lines = content.split('\n').slice(0, 40);
|
|
113
|
+
const versionLine = lines.find((l) => /release atual|current release/i.test(l));
|
|
114
|
+
const match = versionLine?.match(/v(\d+\.\d+\.\d+)/);
|
|
115
|
+
log(HOOK, 'BACKLOG.md fallback version');
|
|
116
|
+
return match ? `v${match[1]}` : 'v?.?.?';
|
|
117
|
+
} catch {
|
|
118
|
+
return 'v?.?.?';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
15
121
|
|
|
16
122
|
async function main(): Promise<void> {
|
|
17
|
-
log(HOOK,
|
|
123
|
+
log(HOOK, 'PostCompact hook triggered');
|
|
18
124
|
|
|
125
|
+
const version = readVersion();
|
|
126
|
+
const capabilityContext = buildCapabilityMatrixContext();
|
|
19
127
|
const workflow = await getActiveWorkflow();
|
|
128
|
+
|
|
20
129
|
if (!workflow) {
|
|
21
|
-
log(HOOK,
|
|
130
|
+
log(HOOK, 'No active workflow — injecting capability context only');
|
|
131
|
+
outputContext(
|
|
132
|
+
'PostCompact',
|
|
133
|
+
[
|
|
134
|
+
`[ORCHESTRATOR] Context was compacted. version=${version} | workflow=none`,
|
|
135
|
+
`Remember: YOU are the orchestrator (RFC-022). Dispatch sub-agents directly via Agent tool.`,
|
|
136
|
+
capabilityContext,
|
|
137
|
+
]
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
.join('\n')
|
|
140
|
+
);
|
|
22
141
|
return;
|
|
23
142
|
}
|
|
24
143
|
|
|
@@ -35,10 +154,17 @@ async function main(): Promise<void> {
|
|
|
35
154
|
parts.push(`Next: invoke '${action.pendingAction.agent}' (${action.pendingAction.status})`);
|
|
36
155
|
}
|
|
37
156
|
|
|
38
|
-
parts.push(
|
|
157
|
+
parts.push(
|
|
158
|
+
'',
|
|
159
|
+
'Remember: YOU are the orchestrator (RFC-022). Dispatch sub-agents directly via Agent tool.'
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (capabilityContext) {
|
|
163
|
+
parts.push(capabilityContext);
|
|
164
|
+
}
|
|
39
165
|
|
|
40
|
-
log(HOOK, `Re-injecting workflow state (wf=${workflow.id})`);
|
|
41
|
-
outputContext(
|
|
166
|
+
log(HOOK, `Re-injecting workflow state + capability context (wf=${workflow.id})`);
|
|
167
|
+
outputContext('PostCompact', parts.join('\n'));
|
|
42
168
|
}
|
|
43
169
|
|
|
44
170
|
main().catch(() => process.exit(0));
|