@orchestrator-claude/definitions 3.32.1 → 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/dangling-guard.ts
CHANGED
|
@@ -12,9 +12,15 @@ import {
|
|
|
12
12
|
getWorkflowStatus,
|
|
13
13
|
getLastCompletedWorkflow,
|
|
14
14
|
consumeLastCompletedWorkflow,
|
|
15
|
+
getNextAction,
|
|
16
|
+
getEffectiveProjectId,
|
|
17
|
+
listInvocationStatesForWorkflow,
|
|
18
|
+
isInvocationStateStale,
|
|
19
|
+
hasFreshDispatchIntent,
|
|
15
20
|
outputBlock,
|
|
16
21
|
postTelemetry,
|
|
17
22
|
type TelemetryPayload,
|
|
23
|
+
type InvocationStatePayload,
|
|
18
24
|
} from './lib/api-client.js';
|
|
19
25
|
import {
|
|
20
26
|
parseJsonlSession,
|
|
@@ -116,6 +122,191 @@ async function emitFinopsTelemetry(
|
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// TD-203: async-aware + cwd-scoped block-decision (pure, unit-testable)
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
export type DanglingAction = 'allow' | 'block' | 'info-allow';
|
|
130
|
+
|
|
131
|
+
export interface DanglingDecision {
|
|
132
|
+
action: DanglingAction;
|
|
133
|
+
reason: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Aggregated signal from listInvocationStatesForWorkflow() + isInvocationStateStale(). */
|
|
137
|
+
export type AgentInvocationSignal = 'fresh' | 'stale' | 'none';
|
|
138
|
+
|
|
139
|
+
export interface DanglingWorkflowInput {
|
|
140
|
+
id: string;
|
|
141
|
+
status: string;
|
|
142
|
+
phase: string;
|
|
143
|
+
/**
|
|
144
|
+
* TD-203 Class-B cwd-scope (TD-204): the workflow's OWN project, when the
|
|
145
|
+
* API surfaces it.
|
|
146
|
+
*
|
|
147
|
+
* VERIFIED (S365, Verify-Before-Assert): WorkflowStatus (api-client.ts)
|
|
148
|
+
* currently has NO projectId field. This is NOT an unaddressed gap — GET
|
|
149
|
+
* /api/v1/workflows (list) and GET /api/v1/workflows/:id/status are
|
|
150
|
+
* ALREADY server-side scoped by the X-Project-ID header sent on every
|
|
151
|
+
* apiGet call: WorkflowsController.getAll -> listWorkflows ->
|
|
152
|
+
* findByProjectId ("RF-002 (TD-183): always scoped via findByProjectId —
|
|
153
|
+
* never falls back to findAll()"), and WorkflowsController.getStatus ->
|
|
154
|
+
* row-scoped getStatus(id, projectId) -> 404 on cross-tenant (TD-184
|
|
155
|
+
* RF-001, Camada 1). Since BOTH calls this hook makes are already
|
|
156
|
+
* project-scoped server-side, it is structurally impossible, under the
|
|
157
|
+
* current architecture, for getActiveWorkflow() to return a workflow
|
|
158
|
+
* belonging to a project other than this hook's own resolved PROJECT_ID —
|
|
159
|
+
* this field is always undefined in production wiring today.
|
|
160
|
+
*
|
|
161
|
+
* This branch is kept (dormant, forward-compatible) and fully
|
|
162
|
+
* unit-tested via synthetic inputs, so it activates automatically the
|
|
163
|
+
* moment a projectId passthrough is added to the API surface, without
|
|
164
|
+
* requiring any further change to this decider.
|
|
165
|
+
*/
|
|
166
|
+
projectId?: string | null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface DanglingDecisionInput {
|
|
170
|
+
workflow: DanglingWorkflowInput | null;
|
|
171
|
+
/** This hook's own resolved project (getEffectiveProjectId()), or null when unresolved. */
|
|
172
|
+
currentProjectId: string | null;
|
|
173
|
+
agentInvocationState: AgentInvocationSignal;
|
|
174
|
+
hasPendingAction: boolean;
|
|
175
|
+
/**
|
|
176
|
+
* TD-205 bg-dispatch race: true when a dispatch-intent marker exists for
|
|
177
|
+
* this workflow AND is still within the short grace window (computed by
|
|
178
|
+
* `hasFreshDispatchIntent()` in api-client.ts — seconds, NOT the 4h
|
|
179
|
+
* INVOCATION_STATE_TTL_MS). The marker is stamped SYNCHRONOUSLY by
|
|
180
|
+
* pre-agent-dispatch.ts at PreToolUse[Agent] — which fires before the
|
|
181
|
+
* sub-agent spawns / before the turn yields — so it is guaranteed present
|
|
182
|
+
* even when a background dispatch + immediate yield lets this Stop hook
|
|
183
|
+
* race ahead of subagent-start.ts's async startInvocation()+
|
|
184
|
+
* writeInvocationState(). Optional (defaults to false) so pre-existing
|
|
185
|
+
* callers/tests that predate TD-205 are unaffected.
|
|
186
|
+
*/
|
|
187
|
+
recentlyDispatchedWithinGraceWindow?: boolean;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const ACTIVE_WORKFLOW_STATUSES = ['in_progress', 'awaiting_agent', 'awaiting_approval'];
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Pure decision function for the dangling-guard Stop hook (TD-203).
|
|
194
|
+
*
|
|
195
|
+
* Evaluates precedence rules 2-8 top-down, first match wins (rule 1 — the
|
|
196
|
+
* stop_hook_active loop guard — is handled in main() before this is called,
|
|
197
|
+
* unchanged from the pre-TD-203 behavior). Takes only injected inputs — no
|
|
198
|
+
* fs/API calls — so every branch is independently testable and
|
|
199
|
+
* deterministic.
|
|
200
|
+
*/
|
|
201
|
+
export function decideDanglingAction(input: DanglingDecisionInput): DanglingDecision {
|
|
202
|
+
const {
|
|
203
|
+
workflow,
|
|
204
|
+
currentProjectId,
|
|
205
|
+
agentInvocationState,
|
|
206
|
+
hasPendingAction,
|
|
207
|
+
recentlyDispatchedWithinGraceWindow = false,
|
|
208
|
+
} = input;
|
|
209
|
+
|
|
210
|
+
// Rule 2: no active workflow -> allow
|
|
211
|
+
if (!workflow) {
|
|
212
|
+
return { action: 'allow', reason: 'No active workflow' };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Rule 3: Class-B cwd-scope (TD-204) — the workflow belongs to a
|
|
216
|
+
// DIFFERENT project than this session's resolved project -> info-allow
|
|
217
|
+
// (empty domain, never a bypass; see DanglingWorkflowInput.projectId doc).
|
|
218
|
+
if (
|
|
219
|
+
workflow.projectId != null &&
|
|
220
|
+
currentProjectId != null &&
|
|
221
|
+
workflow.projectId !== currentProjectId
|
|
222
|
+
) {
|
|
223
|
+
return {
|
|
224
|
+
action: 'info-allow',
|
|
225
|
+
reason: `Workflow ${workflow.id} belongs to project ${workflow.projectId}, not this session's project (${currentProjectId}) — empty domain, no-op (Class-B cwd-scope, TD-204)`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Rule 4: workflow status not in the active set -> allow
|
|
230
|
+
if (!ACTIVE_WORKFLOW_STATUSES.includes(workflow.status)) {
|
|
231
|
+
return {
|
|
232
|
+
action: 'allow',
|
|
233
|
+
reason: `Workflow ${workflow.id} status ${workflow.status} is not active`,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Rule 5: a fresh in-session sub-agent invocation is in-flight -> info-allow.
|
|
238
|
+
// Re-invocation on agent completion advances the workflow — no abandonment.
|
|
239
|
+
if (agentInvocationState === 'fresh') {
|
|
240
|
+
return {
|
|
241
|
+
action: 'info-allow',
|
|
242
|
+
reason: `Workflow ${workflow.id} active, awaiting agent completion (fresh invocation signal); allowing stop, will resume on re-invocation`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Rule 6 (TD-205): the invocation-state signal is 'none' (subagent-start.ts
|
|
247
|
+
// has not yet written its file) BUT a dispatch-intent marker is still fresh
|
|
248
|
+
// -> info-allow. This is the bg-dispatch-race fix: pre-agent-dispatch.ts
|
|
249
|
+
// stamps the marker SYNCHRONOUSLY at PreToolUse[Agent] (fires before the
|
|
250
|
+
// sub-agent spawns / before the turn yields), so it is guaranteed present
|
|
251
|
+
// even when a background dispatch + immediate yield lets this Stop hook
|
|
252
|
+
// race ahead of subagent-start.ts's async write. Scoped strictly to 'none'
|
|
253
|
+
// — a 'stale' signal (rule 7 below) always takes precedence over any
|
|
254
|
+
// dispatch-intent, and the grace window is short (seconds, computed by
|
|
255
|
+
// hasFreshDispatchIntent()) so a genuinely-dead agent whose marker outlives
|
|
256
|
+
// the window still falls through to rule 9 (safety net intact — the window
|
|
257
|
+
// never becomes a permanent hole).
|
|
258
|
+
if (agentInvocationState === 'none' && recentlyDispatchedWithinGraceWindow) {
|
|
259
|
+
return {
|
|
260
|
+
action: 'info-allow',
|
|
261
|
+
reason: `Workflow ${workflow.id} active, no invocation-state file yet but a fresh dispatch-intent marker exists (bg-dispatch race, TD-205); allowing stop, will resume once subagent-start.ts lands or on the next Stop`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Rule 7: the ONLY "alive" signal is a STALE invocation (agent crashed) ->
|
|
266
|
+
// block. Safety net: without this fallback, async-awareness (rule 5)
|
|
267
|
+
// becomes a hole that lets a dead agent hold the workflow dangling forever.
|
|
268
|
+
if (agentInvocationState === 'stale') {
|
|
269
|
+
return {
|
|
270
|
+
action: 'block',
|
|
271
|
+
reason: `[DANGLING-GUARD] Workflow ${workflow.id} has a STALE agent invocation (agent likely crashed/died) and is still ${workflow.status}. You must call mcp__orchestrator-extended__completeWorkflow({ workflowId: '${workflow.id}' }) before ending the session.`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Rule 8: the workflow is explicitly waiting on a human (a pending action
|
|
276
|
+
// is parked, or the phase gate itself is awaiting_approval) -> info-allow.
|
|
277
|
+
// Ending the session here is expected; the human will resume.
|
|
278
|
+
if (hasPendingAction || workflow.status === 'awaiting_approval') {
|
|
279
|
+
return {
|
|
280
|
+
action: 'info-allow',
|
|
281
|
+
reason: `Workflow ${workflow.id} active, awaiting human (${hasPendingAction ? 'pending action parked' : 'awaiting_approval'}); allowing stop, will resume on re-invocation`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Rule 9: genuine dangling — active in THIS project, no fresh agent, not a
|
|
286
|
+
// human-wait. This is the case the guard exists for.
|
|
287
|
+
return {
|
|
288
|
+
action: 'block',
|
|
289
|
+
reason: `[DANGLING-GUARD] Workflow ${workflow.id} is still ${workflow.status} (phase: ${workflow.phase}). You must call mcp__orchestrator-extended__completeWorkflow({ workflowId: '${workflow.id}' }) before ending the session.`,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Aggregates raw invocation-state payloads (from listInvocationStatesForWorkflow)
|
|
295
|
+
* into the AgentInvocationSignal consumed by decideDanglingAction. ANY fresh
|
|
296
|
+
* invocation wins over a stale one (a live agent legitimizes the wait even if
|
|
297
|
+
* a stale file left behind by a different, earlier crashed agent also exists
|
|
298
|
+
* for the same workflow). Reuses isInvocationStateStale's default TTL
|
|
299
|
+
* (INVOCATION_STATE_TTL_MS, 4h) — the SAME staleness definition subagent-stop.ts
|
|
300
|
+
* already uses for its own zombie-completion safety net on the identical
|
|
301
|
+
* state files, so both hooks agree on when an invocation record is dead.
|
|
302
|
+
*/
|
|
303
|
+
export function summarizeAgentInvocations(
|
|
304
|
+
invocations: InvocationStatePayload[]
|
|
305
|
+
): AgentInvocationSignal {
|
|
306
|
+
if (invocations.length === 0) return 'none';
|
|
307
|
+
return invocations.some((inv) => !isInvocationStateStale(inv)) ? 'fresh' : 'stale';
|
|
308
|
+
}
|
|
309
|
+
|
|
119
310
|
async function main(): Promise<void> {
|
|
120
311
|
const stdin = await readStdin();
|
|
121
312
|
log(HOOK, `Stop hook triggered — stdin keys: [${Object.keys(stdin).join(', ')}]`);
|
|
@@ -176,12 +367,38 @@ async function main(): Promise<void> {
|
|
|
176
367
|
|
|
177
368
|
log(HOOK, `workflow=${workflow.id} status=${wfStatus} phase=${phase}`);
|
|
178
369
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
370
|
+
// TD-203: gather async-awareness + cwd-scope signals, then delegate the
|
|
371
|
+
// block/allow/info-allow decision to the pure decider.
|
|
372
|
+
const invocations = listInvocationStatesForWorkflow(workflow.id);
|
|
373
|
+
const agentInvocationState = summarizeAgentInvocations(invocations);
|
|
374
|
+
// TD-205: bg-dispatch race signal — a dispatch-intent marker stamped
|
|
375
|
+
// synchronously by pre-agent-dispatch.ts, still within its short grace
|
|
376
|
+
// window (api-client.ts's DISPATCH_INTENT_GRACE_WINDOW_MS).
|
|
377
|
+
const recentlyDispatchedWithinGraceWindow = hasFreshDispatchIntent(workflow.id);
|
|
378
|
+
const nextAction = await getNextAction(workflow.id);
|
|
379
|
+
const hasPendingAction = nextAction?.hasAction === true;
|
|
380
|
+
const currentProjectId = getEffectiveProjectId() || null;
|
|
381
|
+
|
|
382
|
+
const decision = decideDanglingAction({
|
|
383
|
+
// workflow.projectId intentionally omitted (undefined) — see
|
|
384
|
+
// DanglingWorkflowInput.projectId doc: not yet surfaced by the API,
|
|
385
|
+
// already covered transitively by server-side X-Project-ID scoping.
|
|
386
|
+
workflow: { id: workflow.id, status: wfStatus, phase },
|
|
387
|
+
currentProjectId,
|
|
388
|
+
agentInvocationState,
|
|
389
|
+
hasPendingAction,
|
|
390
|
+
recentlyDispatchedWithinGraceWindow,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
log(HOOK, `decision=${decision.action} — ${decision.reason}`);
|
|
394
|
+
|
|
395
|
+
if (decision.action === 'block') {
|
|
396
|
+
outputBlock(decision.reason);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (decision.action === 'info-allow') {
|
|
401
|
+
console.error(`[DANGLING-GUARD] ${decision.reason}`);
|
|
185
402
|
return;
|
|
186
403
|
}
|
|
187
404
|
|