@orchestrator-claude/definitions 3.32.2 → 3.33.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 +113 -0
- package/agents/debug-sidecar.md +1 -1
- package/agents/doc-sidecar.md +1 -1
- package/agents/test-sidecar.md +1 -1
- 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/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 +54 -68
- 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/hooks/session-start.ts
CHANGED
|
@@ -14,6 +14,12 @@ import {
|
|
|
14
14
|
getNextAction,
|
|
15
15
|
outputContext,
|
|
16
16
|
getToken,
|
|
17
|
+
allocateSessionNumber,
|
|
18
|
+
resolveHookProjectRoot,
|
|
19
|
+
getEffectiveProjectId,
|
|
20
|
+
setEffectiveProjectId,
|
|
21
|
+
listRegisteredProjects,
|
|
22
|
+
type AllocateSessionResult,
|
|
17
23
|
} from './lib/api-client.js';
|
|
18
24
|
import {
|
|
19
25
|
needsRescan,
|
|
@@ -26,26 +32,45 @@ import {
|
|
|
26
32
|
filterRelevantMemories,
|
|
27
33
|
formatMemoryContext,
|
|
28
34
|
} from './lib/capability-scanner.js';
|
|
35
|
+
import { readSessionSelection } from './lib/session-selection.js';
|
|
29
36
|
|
|
30
37
|
const HOOK = 'SESSION-START';
|
|
31
38
|
const __filename = fileURLToPath(import.meta.url);
|
|
32
39
|
const __dirname = dirname(__filename);
|
|
33
40
|
const GRAPHRAG_TIMEOUT_MS = 2000;
|
|
34
|
-
|
|
41
|
+
// RFC-040 Operator Multi-Project — Slice 5 (C5), S358/S361: cwd-aware
|
|
42
|
+
// resolution (walks up for the .orchestrator sentinel) replaces the
|
|
43
|
+
// __dirname-hardcoded path. `null` means an unregistered cwd — deliberately
|
|
44
|
+
// NO __dirname fallback (would leak the Orchestrator's own vault into an
|
|
45
|
+
// unregistered project). All PROJECT_ROOT-derived paths below degrade to
|
|
46
|
+
// `null` and their consumers skip gracefully (AC-C5-2).
|
|
47
|
+
const PROJECT_ROOT = resolveHookProjectRoot(process.cwd());
|
|
48
|
+
// RFC-040 Operator Multi-Project — Slice 2C (C9), S358, TASK-C9-03
|
|
49
|
+
const PROJECT_ID = process.env.ORCHESTRATOR_PROJECT_ID ?? '';
|
|
35
50
|
|
|
36
51
|
// ─── Capability Matrix paths (RFC-027 Sprint 2) ─────────
|
|
37
52
|
const HOME = process.env.HOME ?? process.env.USERPROFILE ?? '/home/user';
|
|
38
53
|
const INSTALLED_PLUGINS_PATH = join(HOME, '.claude', 'plugins', 'installed_plugins.json');
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
|
|
55
|
+
// ─── 2-Phase Selection (RFC-040 TD-204 ⑤-C) ─────────────────────────────
|
|
56
|
+
// Feature-flagged, default OFF — the kill-switch for the whole phase-1
|
|
57
|
+
// prompt + persisted-selection-read path. With the flag unset/false,
|
|
58
|
+
// `selection` stays permanently undefined (byte-for-byte the pre-⑤-C
|
|
59
|
+
// single-phase behaviour tested by session-start-project-binding.test.ts).
|
|
60
|
+
const PROJECT_SELECTION_ENABLED = process.env.ORCHESTRATOR_ENABLE_PROJECT_SELECTION === 'true';
|
|
61
|
+
// Deliberately NOT under ~/.claude (TD-204 hazard boundary — this hook
|
|
62
|
+
// never reads/writes that tree) and NOT under PROJECT_ROOT (a selection
|
|
63
|
+
// must be persistable even when cwd has no .orchestrator sentinel yet —
|
|
64
|
+
// that is exactly the "no binding" state phase-1 prompts for).
|
|
65
|
+
const SESSION_SELECTION_STATE_DIR = join(HOME, '.orchestrator', 'session-selection');
|
|
66
|
+
const AGENTS_DIR = PROJECT_ROOT ? join(PROJECT_ROOT, '.claude', 'agents') : null;
|
|
67
|
+
const VAULT_CONFIG_PATH = PROJECT_ROOT
|
|
68
|
+
? join(PROJECT_ROOT, 'project-guidelines', '.vault-config.yaml')
|
|
69
|
+
: null;
|
|
70
|
+
const PROFILES_PATH = PROJECT_ROOT ? join(PROJECT_ROOT, '.claude', 'plugin-profiles.yaml') : null;
|
|
71
|
+
const MEMORY_INDEX_PATH = PROJECT_ROOT
|
|
72
|
+
? join(PROJECT_ROOT, '.claude', 'agent-memory', 'orchestrator', 'memory-index.yaml')
|
|
73
|
+
: null;
|
|
49
74
|
|
|
50
75
|
/**
|
|
51
76
|
* Read session_id from SessionStart stdin payload.
|
|
@@ -64,7 +89,9 @@ function readSessionId(): string | null {
|
|
|
64
89
|
|
|
65
90
|
// ─── RFC-033 Phase 1: project-state.yaml reader ─────────────────────────────
|
|
66
91
|
|
|
67
|
-
const PROJECT_STATE_PATH =
|
|
92
|
+
const PROJECT_STATE_PATH = PROJECT_ROOT
|
|
93
|
+
? join(PROJECT_ROOT, 'project-guidelines', 'project-state.yaml')
|
|
94
|
+
: null;
|
|
68
95
|
|
|
69
96
|
interface PendingCounts {
|
|
70
97
|
critical: number;
|
|
@@ -136,6 +163,11 @@ export function readProjectState(filePath: string): BacklogSummary | null {
|
|
|
136
163
|
*/
|
|
137
164
|
function readBacklogFallback(): BacklogSummary {
|
|
138
165
|
try {
|
|
166
|
+
if (PROJECT_ROOT === null) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
'PROJECT_ROOT unresolved (unregistered cwd) — no .orchestrator sentinel found'
|
|
169
|
+
);
|
|
170
|
+
}
|
|
139
171
|
const backlogPath = join(PROJECT_ROOT, 'project-guidelines', 'BACKLOG.md');
|
|
140
172
|
const content = readFileSync(backlogPath, 'utf-8');
|
|
141
173
|
const allLines = content.split('\n');
|
|
@@ -195,13 +227,15 @@ function readBacklogFallback(): BacklogSummary {
|
|
|
195
227
|
* Return shape is IDENTICAL to the original function.
|
|
196
228
|
*/
|
|
197
229
|
function getBacklogSummary(): BacklogSummary {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
230
|
+
if (PROJECT_STATE_PATH !== null) {
|
|
231
|
+
const structured = readProjectState(PROJECT_STATE_PATH);
|
|
232
|
+
if (structured !== null) {
|
|
233
|
+
log(
|
|
234
|
+
HOOK,
|
|
235
|
+
`project-state.yaml: version=${structured.version} next=${structured.next.slice(0, 50)}`
|
|
236
|
+
);
|
|
237
|
+
return structured;
|
|
238
|
+
}
|
|
205
239
|
}
|
|
206
240
|
log(HOOK, 'project-state.yaml absent/invalid — using BACKLOG.md fallback');
|
|
207
241
|
return readBacklogFallback();
|
|
@@ -216,6 +250,11 @@ type TierInfo = { onboardingNeeded: true } | { tier: number; source: string } |
|
|
|
216
250
|
*/
|
|
217
251
|
async function checkTier(): Promise<TierInfo> {
|
|
218
252
|
try {
|
|
253
|
+
// RFC-040 C5: unregistered cwd — no vault to check tier against, skip.
|
|
254
|
+
if (VAULT_CONFIG_PATH === null) {
|
|
255
|
+
log(HOOK, 'checkTier: PROJECT_ROOT unresolved (unregistered cwd) — skipping tier check');
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
219
258
|
// Use module-level VAULT_CONFIG_PATH constant (IMP-003)
|
|
220
259
|
if (!existsSync(VAULT_CONFIG_PATH)) {
|
|
221
260
|
return { onboardingNeeded: true };
|
|
@@ -245,12 +284,138 @@ export function formatTierSegment(info: TierInfo): string {
|
|
|
245
284
|
return ` | tier=${info.tier} (${info.source})`;
|
|
246
285
|
}
|
|
247
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Format the result of allocateSessionNumber() as a segment string for the
|
|
289
|
+
* ORCHESTRATOR-DATA line. Fail-open: returns "" when allocation failed
|
|
290
|
+
* (API down, no token, non-2xx) — RFC-040 Operator Multi-Project, Slice 2C
|
|
291
|
+
* (C9), S358, TASK-C9-03.
|
|
292
|
+
*/
|
|
293
|
+
export function formatSessionNumberSegment(result: AllocateSessionResult | null): string {
|
|
294
|
+
if (result === null) return '';
|
|
295
|
+
return ` | session_number=${result.number}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Session Project Binding Precedence (RFC-040 TD-204 §⑥-c) ──────────────
|
|
299
|
+
// Slice 5 (C5) resolves a project ROOT for filesystem access
|
|
300
|
+
// (resolveHookProjectRoot(cwd), walk-up for the .orchestrator sentinel).
|
|
301
|
+
// This primitive instead resolves the project IDENTITY (projectId) a
|
|
302
|
+
// session is BOUND to, applying a strict first-match precedence:
|
|
303
|
+
// 1. env-override (ORCHESTRATOR_PROJECT_ID) — an IMMUTABLE pin. If set,
|
|
304
|
+
// the session is DEFINITIVELY that project; no other signal may
|
|
305
|
+
// override it.
|
|
306
|
+
// 2. explicit selection — forward-compat for Slice 5-C (⑤-C, NOT YET
|
|
307
|
+
// IMPLEMENTED). `selection` is always undefined until ⑤-C lands, so
|
|
308
|
+
// this tier is dormant-but-tested (mirrors the dormant-but-tested
|
|
309
|
+
// branches of decideWorkflowGuardAction in workflow-guard.ts, TD-204
|
|
310
|
+
// ⑤-A).
|
|
311
|
+
// 3. cwd-hint — the project resolved lazily from cwd via
|
|
312
|
+
// ensureProjectIdResolved/getEffectiveProjectId() (RFC-040 C5/C9).
|
|
313
|
+
// 4. none — no signal available; caller must force selection/registration.
|
|
314
|
+
// The `pinned` flag drives formatProjectBindingSegment's hard surfacing
|
|
315
|
+
// requirement below: an env-override pin is NEVER a silent surprise.
|
|
316
|
+
|
|
317
|
+
export type SessionProjectBindingSource = 'env-override' | 'selection' | 'cwd-hint' | 'none';
|
|
318
|
+
|
|
319
|
+
export interface SessionProjectBinding {
|
|
320
|
+
projectId: string;
|
|
321
|
+
source: SessionProjectBindingSource;
|
|
322
|
+
pinned: boolean;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export interface SessionProjectBindingInput {
|
|
326
|
+
envOverride: string;
|
|
327
|
+
/** Explicit project selection (RFC-040 Slice 5-C, ⑤-C — NOT YET IMPLEMENTED). Always undefined until ⑤-C lands. */
|
|
328
|
+
selection?: string;
|
|
329
|
+
cwdHintProjectId: string;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Pure precedence resolver for session project binding (RFC-040 TD-204
|
|
334
|
+
* ⑥-c). Zero fs/API calls — takes only injected inputs, so every tier
|
|
335
|
+
* (including the dormant `selection` tier) is independently testable.
|
|
336
|
+
*/
|
|
337
|
+
export function resolveSessionProjectBinding(
|
|
338
|
+
input: SessionProjectBindingInput
|
|
339
|
+
): SessionProjectBinding {
|
|
340
|
+
const { envOverride, selection, cwdHintProjectId } = input;
|
|
341
|
+
|
|
342
|
+
if (envOverride) {
|
|
343
|
+
return { projectId: envOverride, source: 'env-override', pinned: true };
|
|
344
|
+
}
|
|
345
|
+
if (selection) {
|
|
346
|
+
return { projectId: selection, source: 'selection', pinned: false };
|
|
347
|
+
}
|
|
348
|
+
if (cwdHintProjectId) {
|
|
349
|
+
return { projectId: cwdHintProjectId, source: 'cwd-hint', pinned: false };
|
|
350
|
+
}
|
|
351
|
+
return { projectId: '', source: 'none', pinned: false };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Formats a SessionProjectBinding as an ADDITIVE segment for the
|
|
356
|
+
* [ORCHESTRATOR-DATA] line (RFC-040 TD-204 ⑥-c). Hard requirement: when
|
|
357
|
+
* `pinned` (env-override), the pin MUST be surfaced explicitly — zero
|
|
358
|
+
* silent surprise.
|
|
359
|
+
*/
|
|
360
|
+
export function formatProjectBindingSegment(binding: SessionProjectBinding): string {
|
|
361
|
+
if (binding.source === 'none') return ' | project=none';
|
|
362
|
+
if (binding.pinned) return ` | project=${binding.projectId} (pinned via env-override)`;
|
|
363
|
+
return ` | project=${binding.projectId} (${binding.source})`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Phase 1 of the 2-phase selection flow (RFC-040 TD-204 ⑤-C, feature-
|
|
368
|
+
* flagged): builds the [ORCHESTRATOR-PROJECT-SELECTION] prompt emitted
|
|
369
|
+
* when NO binding resolved (env unset, no persisted selection, cwd
|
|
370
|
+
* unregistered). Instructs the assistant to ask (AskUserQuestion) which
|
|
371
|
+
* registered project this session belongs to, listing every registered
|
|
372
|
+
* project by name/id/rootPath.
|
|
373
|
+
*
|
|
374
|
+
* Fail-open (Class C benign degrade, mirrors buildCapabilityMatrixContext
|
|
375
|
+
* / getGraphContext): returns "" on any error — a failed project-list
|
|
376
|
+
* fetch must never block or crash the hook.
|
|
377
|
+
*/
|
|
378
|
+
export async function buildProjectSelectionPrompt(): Promise<string> {
|
|
379
|
+
try {
|
|
380
|
+
const projects = await listRegisteredProjects();
|
|
381
|
+
|
|
382
|
+
if (projects.length === 0) {
|
|
383
|
+
return [
|
|
384
|
+
'[ORCHESTRATOR-PROJECT-SELECTION] No project is bound to this session (env unset, cwd unregistered, no prior selection) and no projects are registered yet.',
|
|
385
|
+
'[ORCHESTRATOR] Ask the user (AskUserQuestion) whether to register this cwd as a new project, or proceed unmanaged for this session.',
|
|
386
|
+
].join('\n');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const options = projects.map((p) => `${p.name} (${p.id}) — ${p.rootPath}`).join('; ');
|
|
390
|
+
return [
|
|
391
|
+
'[ORCHESTRATOR-PROJECT-SELECTION] No project is bound to this session (env unset, cwd unregistered, no prior selection).',
|
|
392
|
+
`[ORCHESTRATOR] Ask the user (AskUserQuestion) which project this session belongs to. Registered projects: ${options}`,
|
|
393
|
+
].join('\n');
|
|
394
|
+
} catch (err) {
|
|
395
|
+
log(HOOK, `buildProjectSelectionPrompt failed: ${String(err)}`);
|
|
396
|
+
return '';
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
248
400
|
/**
|
|
249
401
|
* Build [CAPABILITY-MATRIX] + [MEMORY-CONTEXT] lines for injection.
|
|
250
402
|
* Fail-open: returns empty string on any error.
|
|
251
403
|
*/
|
|
252
404
|
function buildCapabilityMatrixContext(): string {
|
|
253
405
|
try {
|
|
406
|
+
// RFC-040 C5: unregistered cwd — no vault/agents/memory-index to read, skip.
|
|
407
|
+
if (
|
|
408
|
+
VAULT_CONFIG_PATH === null ||
|
|
409
|
+
AGENTS_DIR === null ||
|
|
410
|
+
PROFILES_PATH === null ||
|
|
411
|
+
MEMORY_INDEX_PATH === null
|
|
412
|
+
) {
|
|
413
|
+
log(
|
|
414
|
+
HOOK,
|
|
415
|
+
'buildCapabilityMatrixContext: PROJECT_ROOT unresolved (unregistered cwd) — skipping'
|
|
416
|
+
);
|
|
417
|
+
return '';
|
|
418
|
+
}
|
|
254
419
|
let index = readCapabilitiesFromVault(VAULT_CONFIG_PATH);
|
|
255
420
|
const cachedMtime = index?.installedPluginsMtime ?? '';
|
|
256
421
|
|
|
@@ -402,6 +567,13 @@ export async function getGraphContext(
|
|
|
402
567
|
async function main(): Promise<void> {
|
|
403
568
|
log(HOOK, 'SessionStart hook triggered');
|
|
404
569
|
|
|
570
|
+
if (PROJECT_ROOT === null) {
|
|
571
|
+
log(
|
|
572
|
+
HOOK,
|
|
573
|
+
'PROJECT_ROOT unresolved: cwd has no .orchestrator sentinel up to the filesystem root — vault-dependent sections (tier, capability matrix, BACKLOG fallback) will be skipped this session (RFC-040 C5, AC-C5-2).'
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
405
577
|
const sessionId = readSessionId();
|
|
406
578
|
const sessionSegment = sessionId ? ` | session=${sessionId}` : '';
|
|
407
579
|
if (sessionId) log(HOOK, `Claude session: ${sessionId}`);
|
|
@@ -410,21 +582,88 @@ async function main(): Promise<void> {
|
|
|
410
582
|
const tierInfo = await checkTier();
|
|
411
583
|
const tierSegment = formatTierSegment(tierInfo);
|
|
412
584
|
const capabilityContext = buildCapabilityMatrixContext();
|
|
413
|
-
const workflow = await getActiveWorkflow();
|
|
414
585
|
|
|
415
586
|
// Sprint inbox check (RFC-027 S4 — only for sprint panes)
|
|
416
587
|
const sprintInboxContext = checkSprintInbox();
|
|
417
588
|
|
|
418
|
-
// Graph context (RFC-021 T-G4-06) — fail-open, 2s timeout
|
|
589
|
+
// Graph context (RFC-021 T-G4-06) — fail-open, 2s timeout. RFC-040 TD-206
|
|
590
|
+
// (S372): this getToken() call MUST stay HERE, before
|
|
591
|
+
// resolveSessionProjectBinding() below — it is what bootstraps the
|
|
592
|
+
// cwd-hint tier (getToken -> ensureProjectIdResolved ->
|
|
593
|
+
// getEffectiveProjectId() reads the resolved value just below). Moving
|
|
594
|
+
// getActiveWorkflow()/getNextAction() later (see below) must not remove
|
|
595
|
+
// this bootstrap, or the cwd-hint tier would regress to reading '' .
|
|
419
596
|
const token = await getToken();
|
|
420
597
|
const graphContext = await getGraphContext(token);
|
|
421
598
|
|
|
599
|
+
// RFC-040 TD-204 ⑤-C: read a previously-persisted selection for THIS
|
|
600
|
+
// sessionId — feature-flagged (default OFF, kill-switch). With the flag
|
|
601
|
+
// off, `selection` stays undefined and the tier is permanently dormant,
|
|
602
|
+
// exactly as before ⑤-C (byte-for-byte pre-existing single-phase
|
|
603
|
+
// behaviour). readSessionSelection is Class C benign-degrade — never
|
|
604
|
+
// throws, so no try/catch needed here.
|
|
605
|
+
const selection =
|
|
606
|
+
PROJECT_SELECTION_ENABLED && sessionId
|
|
607
|
+
? readSessionSelection(SESSION_SELECTION_STATE_DIR, sessionId)
|
|
608
|
+
: undefined;
|
|
609
|
+
|
|
610
|
+
// RFC-040 TD-204 ⑥-c: resolve the session's project binding via strict
|
|
611
|
+
// precedence (env-override pin > selection [⑤-C, feature-flagged] >
|
|
612
|
+
// cwd-hint > none) — centralizes what used to be the inline `PROJECT_ID
|
|
613
|
+
// || getEffectiveProjectId()` fallback into a single, pure, testable
|
|
614
|
+
// source of truth, and drives the mandatory pin-surfacing segment below.
|
|
615
|
+
const projectBinding = resolveSessionProjectBinding({
|
|
616
|
+
envOverride: PROJECT_ID,
|
|
617
|
+
selection,
|
|
618
|
+
cwdHintProjectId: getEffectiveProjectId(),
|
|
619
|
+
});
|
|
620
|
+
const projectBindingSegment = formatProjectBindingSegment(projectBinding);
|
|
621
|
+
|
|
622
|
+
// RFC-040 TD-206 (S372): feed the precedence-resolved binding back into
|
|
623
|
+
// the api-client's internal scoping so getActiveWorkflow()/getNextAction()
|
|
624
|
+
// (moved below, AFTER this point — see comment there) query under the
|
|
625
|
+
// SAME projectId the [ORCHESTRATOR-DATA] line above surfaces, instead of
|
|
626
|
+
// whatever cwd-hint ensureProjectIdResolved happened to resolve during the
|
|
627
|
+
// getToken() bootstrap call above. Closes the S371 Case-2 gap (selection
|
|
628
|
+
// displayed correctly but workflow queried under the cwd project).
|
|
629
|
+
// Safe no-op when source is 'env-override' (binding.projectId already
|
|
630
|
+
// equals api-client's own PROJECT_ID, seeded from the same
|
|
631
|
+
// ORCHESTRATOR_PROJECT_ID env var at import time) or 'cwd-hint'
|
|
632
|
+
// (binding.projectId already equals getEffectiveProjectId()).
|
|
633
|
+
setEffectiveProjectId(projectBinding.projectId);
|
|
634
|
+
|
|
635
|
+
// RFC-040 TD-204 ⑤-C, phase 1: when NO binding resolved at all AND the
|
|
636
|
+
// flag is on, prompt the operator to select a project. Computed ONCE and
|
|
637
|
+
// folded as extra lines into whichever outputContext() call below fires
|
|
638
|
+
// — never a SECOND outputContext call, so exactly one [ORCHESTRATOR-DATA]
|
|
639
|
+
// block is ever emitted (double-load regression guard).
|
|
640
|
+
const projectSelectionPrompt =
|
|
641
|
+
PROJECT_SELECTION_ENABLED && projectBinding.source === 'none'
|
|
642
|
+
? await buildProjectSelectionPrompt()
|
|
643
|
+
: '';
|
|
644
|
+
|
|
645
|
+
// RFC-040 Operator Multi-Project — Slice 2C (C9), S358, TASK-C9-03: allocate
|
|
646
|
+
// a globally-numbered session (fail-open — API down never blocks the hook).
|
|
647
|
+
// RFC-040 Slice 5 (C5), S358/S361 / ⑥-c: allocation receives
|
|
648
|
+
// projectBinding.projectId — the same precedence-resolved id surfaced in
|
|
649
|
+
// the ORCHESTRATOR-DATA line below (AC-C5-4, single source of truth).
|
|
650
|
+
const sessionAllocation = await allocateSessionNumber(projectBinding.projectId);
|
|
651
|
+
const sessionNumberSegment = formatSessionNumberSegment(sessionAllocation);
|
|
652
|
+
|
|
653
|
+
// RFC-040 TD-206 (S372): getActiveWorkflow() now runs AFTER
|
|
654
|
+
// setEffectiveProjectId() above (moved from its original position, which
|
|
655
|
+
// ran BEFORE binding resolution — session-start.ts:584 pre-TD-206), so
|
|
656
|
+
// its X-Project-ID header carries the precedence-resolved
|
|
657
|
+
// binding.projectId (selection winner when present), not the raw
|
|
658
|
+
// cwd-resolved value it used to see (S371 Case-2 finding).
|
|
659
|
+
const workflow = await getActiveWorkflow();
|
|
660
|
+
|
|
422
661
|
if (!workflow) {
|
|
423
662
|
log(HOOK, 'No active workflow, injecting greeting data');
|
|
424
663
|
outputContext(
|
|
425
664
|
'SessionStart',
|
|
426
665
|
[
|
|
427
|
-
`[ORCHESTRATOR-DATA] version=${backlog.version}${sessionSegment} | workflow=none${tierSegment} | next=${backlog.next}`,
|
|
666
|
+
`[ORCHESTRATOR-DATA] version=${backlog.version}${sessionSegment} | workflow=none${tierSegment}${sessionNumberSegment}${projectBindingSegment} | next=${backlog.next}`,
|
|
428
667
|
backlog.lastSession ? `[LAST-SESSION] ${backlog.lastSession}` : '',
|
|
429
668
|
backlog.status ? `[PENDENTE] ${backlog.status}` : '',
|
|
430
669
|
`[ORCHESTRATOR] Greet the user via AskUserQuestion using the data above (CLAUDE.md Rule #1).`,
|
|
@@ -433,6 +672,7 @@ async function main(): Promise<void> {
|
|
|
433
672
|
capabilityContext,
|
|
434
673
|
graphContext,
|
|
435
674
|
sprintInboxContext,
|
|
675
|
+
projectSelectionPrompt,
|
|
436
676
|
]
|
|
437
677
|
.filter(Boolean)
|
|
438
678
|
.join('\n')
|
|
@@ -451,12 +691,13 @@ async function main(): Promise<void> {
|
|
|
451
691
|
outputContext(
|
|
452
692
|
'SessionStart',
|
|
453
693
|
[
|
|
454
|
-
`[ORCHESTRATOR-DATA] version=${backlog.version}${sessionSegment} | workflow=${workflow.id} | type=${workflow.type} | phase=${workflow.currentPhase} | status=${workflow.status}${actionHint}${tierSegment}`,
|
|
694
|
+
`[ORCHESTRATOR-DATA] version=${backlog.version}${sessionSegment} | workflow=${workflow.id} | type=${workflow.type} | phase=${workflow.currentPhase} | status=${workflow.status}${actionHint}${tierSegment}${sessionNumberSegment}${projectBindingSegment}`,
|
|
455
695
|
`[ORCHESTRATOR] Active workflow detected. Greet the user via AskUserQuestion showing workflow state (CLAUDE.md Rule #1).`,
|
|
456
696
|
'Do NOT invoke /orchestrator skill for the greeting — use AskUserQuestion directly.',
|
|
457
697
|
'Invoke /orchestrator skill when the user chooses to continue the workflow.',
|
|
458
698
|
capabilityContext,
|
|
459
699
|
graphContext,
|
|
700
|
+
projectSelectionPrompt,
|
|
460
701
|
]
|
|
461
702
|
.filter(Boolean)
|
|
462
703
|
.join('\n')
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createRequire as __createRequireShim } from 'node:module'; const require = __createRequireShim(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// hooks/sprint-registry.ts
|
|
4
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
function log(msg) {
|
|
7
|
+
process.stderr.write(`[sprint-registry] ${msg}
|
|
8
|
+
`);
|
|
9
|
+
}
|
|
10
|
+
async function readStdin() {
|
|
11
|
+
const chunks = [];
|
|
12
|
+
for await (const chunk of process.stdin) {
|
|
13
|
+
chunks.push(chunk);
|
|
14
|
+
}
|
|
15
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
16
|
+
if (!raw) return {};
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(raw);
|
|
19
|
+
} catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function main() {
|
|
24
|
+
const stdin = await readStdin();
|
|
25
|
+
const sessionId = stdin.session_id ?? void 0;
|
|
26
|
+
const sprintId = process.env.SPRINT_ID;
|
|
27
|
+
const paneRole = process.env.PANE_ROLE;
|
|
28
|
+
if (!sprintId || !paneRole) {
|
|
29
|
+
log(`Skipping: SPRINT_ID=${sprintId ?? "unset"} PANE_ROLE=${paneRole ?? "unset"}`);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
const registryDir = `/tmp/${sprintId}/registry`;
|
|
33
|
+
const entryPath = join(registryDir, `${paneRole}.json`);
|
|
34
|
+
const workflowId = process.env.WORKFLOW_ID;
|
|
35
|
+
const entry = {
|
|
36
|
+
role: paneRole,
|
|
37
|
+
sprintId,
|
|
38
|
+
pid: process.pid,
|
|
39
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
40
|
+
worktreePath: process.cwd()
|
|
41
|
+
};
|
|
42
|
+
if (sessionId) {
|
|
43
|
+
entry.sessionId = sessionId;
|
|
44
|
+
}
|
|
45
|
+
if (workflowId) {
|
|
46
|
+
entry.workflowId = workflowId;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
mkdirSync(registryDir, { recursive: true });
|
|
50
|
+
writeFileSync(entryPath, JSON.stringify(entry, null, 2), "utf-8");
|
|
51
|
+
log(
|
|
52
|
+
`Registered ${paneRole} for sprint ${sprintId} (pid=${process.pid}, session=${sessionId ?? "unknown"})`
|
|
53
|
+
);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
log(`Registration failed: ${err}`);
|
|
56
|
+
}
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
main().catch(() => process.exit(0));
|