@khoavu882/doflow 2.0.0-beta.4 → 2.0.0-beta.5
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/core/.antigravity-plugin/plugin.json +0 -1
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
- package/core/harnesses/gemini/hooks/session-start.sh +5 -3
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
- package/core/harnesses/kiro/hooks/session-start.sh +11 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
- package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
- package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
- package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
- package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
- package/core/registry/assets.json +19 -0
- package/core/registry/contracts.json +2 -2
- package/core/registry/harnesses.json +196 -10
- package/core/registry/lifecycle.json +2626 -7
- package/core/registry/storage.json +24 -1
- package/core/shared/scripts/doflow/bin/doflow-run +10 -6
- package/core/shared/skills/do-design/SKILL.md +6 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
- package/core/shared/skills/do-implement/SKILL.md +3 -2
- package/core/shared/skills/do-plan/SKILL.md +7 -8
- package/package.json +1 -1
- package/src/adapters/antigravity/index.js +83 -47
- package/src/adapters/claude/index.js +15 -1
- package/src/adapters/codex/hooks.js +40 -5
- package/src/adapters/codex/index.js +52 -9
- package/src/adapters/copy-tree.js +4 -4
- package/src/adapters/gemini/hooks.js +57 -16
- package/src/adapters/gemini/index.js +57 -18
- package/src/adapters/kiro/index.js +15 -1
- package/src/adapters/pi/hooks.js +119 -0
- package/src/adapters/pi/index.js +34 -9
- package/src/cli/commands/install.js +1 -1
- package/src/cli/commands/update.js +1 -1
- package/src/cli/index.js +17 -5
- package/src/cli/runtime-commands.js +51 -12
- package/src/lifecycle/coverage.js +57 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/index.js +28 -14
- package/src/registry/index.js +136 -7
- package/src/runtime/candidate.js +505 -0
- package/src/runtime/claims.js +14 -5
- package/src/runtime/cli-result.js +11 -4
- package/src/runtime/cli.js +35 -268
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +102 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context-pack.js +1029 -244
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/evidence-ledger.js +7 -0
- package/src/runtime/freshness.js +44 -7
- package/src/runtime/outcome.js +1 -1
- package/src/runtime/readiness.js +83 -13
- package/src/runtime/retrieval-plan.js +422 -73
- package/src/runtime/retrieval-record.js +223 -0
- package/src/runtime/scaffold/artifacts.js +3 -0
- package/src/runtime/source-version.js +289 -0
- package/src/runtime/task-attempts.js +1 -1
- package/src/runtime/task-scope.js +28 -3
- package/src/runtime/task-state.js +72 -7
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
|
@@ -2,27 +2,21 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* DoFlow Cross-Harness Hook Runner
|
|
6
|
-
* event (SessionStart, PreToolUse, PostToolUse, Stop, PreInvocation) for
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Policy Library (core/harnesses/shared/hooks/policies/*.sh, Phase A) — it implements no guard
|
|
11
|
-
* policy logic itself.
|
|
5
|
+
* DoFlow Cross-Harness Hook Runner — front door for every in-scope lifecycle
|
|
6
|
+
* event (SessionStart, PreToolUse, PostToolUse, Stop, PreInvocation) for Antigravity. It owns
|
|
7
|
+
* event dispatch and native payload/decision
|
|
8
|
+
* translation (delegated to adapters/antigravity.js) and delegates every policy decision
|
|
9
|
+
* to the shared lifecycle bridge — it implements no guard policy logic itself.
|
|
12
10
|
*
|
|
13
11
|
* Invocation: `node stream-hook-runner.js [EventName]`, JSON payload on stdin, JSON decision on
|
|
14
|
-
* stdout. EventName
|
|
15
|
-
* (older hooks.json wiring that doesn't yet pass it), the event is inferred from the payload's
|
|
16
|
-
* own shape as a fallback, matching this runner's behavior before event names were threaded
|
|
17
|
-
* through argv.
|
|
12
|
+
* stdout. EventName selects which canonical policy applies.
|
|
18
13
|
*/
|
|
19
14
|
|
|
20
|
-
const {
|
|
15
|
+
const { spawnSync } = require('node:child_process');
|
|
21
16
|
const fs = require('node:fs');
|
|
22
17
|
const os = require('node:os');
|
|
23
18
|
const path = require('node:path');
|
|
24
19
|
|
|
25
|
-
const geminiAdapter = require('./adapters/gemini');
|
|
26
20
|
const antigravityAdapter = require('./adapters/antigravity');
|
|
27
21
|
|
|
28
22
|
// Tool-name classification for PreToolUse, generalized to the union every Phase A canonical
|
|
@@ -249,15 +243,6 @@ function captureHookFailure({ command, kind, frame = null }, env = process.env)
|
|
|
249
243
|
}
|
|
250
244
|
}
|
|
251
245
|
|
|
252
|
-
// Canonical script name -> pre-normalization per-harness file name, for the fallback search only.
|
|
253
|
-
const LEGACY_SCRIPT_NAMES = {
|
|
254
|
-
'session-context.sh': 'session-start.sh',
|
|
255
|
-
'pre-implementation-gate.sh': 'pre-implement-gate.sh',
|
|
256
|
-
'mcp-tool-guard.sh': 'mcp-tool-guard.sh',
|
|
257
|
-
'stop-check.sh': 'stop-check.sh',
|
|
258
|
-
'pre-bash-guard.sh': 'pre-bash-guard.sh',
|
|
259
|
-
};
|
|
260
|
-
|
|
261
246
|
// A hook payload is one tool call's JSON — a few KB in the overwhelming majority of cases, a few
|
|
262
247
|
// hundred KB for a large file edit at most. These bounds exist only to stop a misbehaving harness
|
|
263
248
|
// process (stdin that never closes, or streams unboundedly) from hanging this front door forever;
|
|
@@ -334,75 +319,58 @@ function resolveProjectRoot(workspacePaths) {
|
|
|
334
319
|
}
|
|
335
320
|
|
|
336
321
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
* source of truth (design.md C1) that ships alongside this file, so the lookup needs no
|
|
340
|
-
* project-root guessing and works identically in-repo and once installed, as long as the
|
|
341
|
-
* `shared/hooks/` tree is projected together. The per-harness candidate search below is kept only
|
|
342
|
-
* as a fallback for an install that hasn't picked up the canonical policies/ directory yet.
|
|
322
|
+
* Delegate the native payload to the shared lifecycle bridge. The bridge owns encoding,
|
|
323
|
+
* classification, effects and diagnostics; this runner only applies Antigravity's native render.
|
|
343
324
|
*/
|
|
344
|
-
function
|
|
345
|
-
const
|
|
346
|
-
if (fs.existsSync(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
path.join(projectRoot, '.gemini/hooks', legacyName),
|
|
351
|
-
path.join(projectRoot, '.claude/hooks', legacyName),
|
|
352
|
-
path.join(projectRoot, '.codex/hooks', legacyName),
|
|
353
|
-
path.join(projectRoot, '.doflow/scripts', legacyName),
|
|
354
|
-
];
|
|
355
|
-
for (const candidate of candidates) {
|
|
356
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
357
|
-
}
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Delegate one canonical-decision evaluation to a Phase A policy script (design.md's Canonical
|
|
363
|
-
* Policy Script Contract): the canonical payload goes in on stdin, DOFLOW_PROJECT_DIR/DOFLOW_AGENT
|
|
364
|
-
* are set, and the exit code decides allow/deny — exit 0 = allow, non-zero = deny with the reason
|
|
365
|
-
* read from stderr (falling back to stdout if a script has none). Returns the CANONICAL decision
|
|
366
|
-
* shape ({decision:'allow'} | {decision:'deny', reason}); harness-native translation happens one
|
|
367
|
-
* layer up, in the adapter's toNative, not here — this function never speaks a harness-specific
|
|
368
|
-
* decision vocabulary.
|
|
369
|
-
*/
|
|
370
|
-
function delegateToPolicy(projectRoot, agent, scriptName, canonicalPayload) {
|
|
371
|
-
const scriptPath = resolvePolicyScript(projectRoot, scriptName);
|
|
372
|
-
if (!scriptPath) return { decision: 'allow' };
|
|
373
|
-
|
|
374
|
-
try {
|
|
375
|
-
execFileSync('bash', [scriptPath], {
|
|
376
|
-
cwd: projectRoot,
|
|
377
|
-
input: JSON.stringify(canonicalPayload || {}),
|
|
378
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
379
|
-
env: { ...process.env, DOFLOW_PROJECT_DIR: projectRoot, DOFLOW_AGENT: agent },
|
|
380
|
-
});
|
|
381
|
-
return { decision: 'allow' };
|
|
382
|
-
} catch (err) {
|
|
383
|
-
// A real policy deny has a numeric exit status — the script ran and its own canonical exit
|
|
384
|
-
// code (1 = deny) is the contract this delegates to. `err.status` is null/undefined when the
|
|
385
|
-
// process never produced an exit code at all (bash missing from PATH, EACCES, etc.) — that is
|
|
386
|
-
// an execution fault, not a policy decision, and every sourced .sh policy in this library
|
|
387
|
-
// fails open on its own uncertainty (`command -v jq || exit 0`); this delegator matches that
|
|
388
|
-
// posture instead of turning an environment fault into a silent deny.
|
|
389
|
-
if (typeof err.status !== 'number') {
|
|
390
|
-
// A policy killed by a signal is a fault in DoFlow's own install worth recording (IC-018); a
|
|
391
|
-
// missing `bash` or a permission error is the user's environment and is not. Either way the
|
|
392
|
-
// decision is the same as it always was.
|
|
393
|
-
// SIGTERM, SIGINT and SIGPIPE are the user or the harness stopping the process (DEC-031), not
|
|
394
|
-
// a fault. Any other signal is recorded under its own kind, so a SEGV and a KILL do not share a fingerprint.
|
|
395
|
-
if (typeof err.signal === 'string' && err.signal && !['SIGTERM', 'SIGINT', 'SIGPIPE'].includes(err.signal)) {
|
|
396
|
-
captureHookFailure({ command: scriptName.replace(/\.sh$/, ''), kind: `policy-exec-fault:${err.signal}` });
|
|
397
|
-
}
|
|
398
|
-
return { decision: 'allow' };
|
|
325
|
+
function delegateToPolicy(projectRoot, agent, event, policyId, nativeRaw) {
|
|
326
|
+
const bridgeSh = path.join(__dirname, 'bridge', 'bridge.sh');
|
|
327
|
+
if (!fs.existsSync(bridgeSh)) {
|
|
328
|
+
captureHookFailure({ command: policyId, kind: 'bridge-missing' });
|
|
329
|
+
if (policyId === 'stop-check') {
|
|
330
|
+
return { v: 1, harness: agent, event, policy: policyId, policyVersion: 0, outcome: 'unsupported', code: 'bridge-missing', effect: 'proceed' };
|
|
399
331
|
}
|
|
400
|
-
const reason = err.stderr ? err.stderr.toString().trim() : (err.stdout ? err.stdout.toString().trim() : '');
|
|
401
332
|
return {
|
|
402
|
-
|
|
403
|
-
|
|
333
|
+
v: 1, harness: agent, event, policy: policyId, policyVersion: 0,
|
|
334
|
+
outcome: 'deny', code: 'bridge-missing', reason: '[doflow] shared hooks lack the bridge; run: doflow update -t antigravity', effect: 'block',
|
|
404
335
|
};
|
|
405
336
|
}
|
|
337
|
+
const profile = process.env.DOFLOW_FAULT_PROFILE || 'compat-1';
|
|
338
|
+
const effectiveProfile = profile === 'compat-1' || profile === 'strict-1' ? profile : 'compat-1';
|
|
339
|
+
if (profile !== effectiveProfile) captureHookFailure({ command: policyId, kind: 'fault-profile-unknown' });
|
|
340
|
+
const blockingPolicy = ['pre-implementation-gate', 'pre-bash-guard', 'mcp-tool-guard', 'stop-check'].includes(policyId);
|
|
341
|
+
const faultEffect = effectiveProfile === 'strict-1' && blockingPolicy ? 'block' : 'proceed';
|
|
342
|
+
const faultDecision = (code) => ({
|
|
343
|
+
v: 1, harness: agent, event, policy: policyId, policyVersion: 0,
|
|
344
|
+
outcome: 'error', code, reason: code, faultProfile: effectiveProfile, effect: faultEffect,
|
|
345
|
+
});
|
|
346
|
+
const result = spawnSync('bash', [bridgeSh, agent, event, policyId, '--emit', 'decision'], {
|
|
347
|
+
cwd: projectRoot,
|
|
348
|
+
input: nativeRaw,
|
|
349
|
+
encoding: 'utf8',
|
|
350
|
+
timeout: 5000,
|
|
351
|
+
maxBuffer: 1024 * 1024,
|
|
352
|
+
env: { ...process.env, DOFLOW_PROJECT_DIR: projectRoot, DOFLOW_AGENT: agent },
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (result.error && result.error.code === 'ETIMEDOUT') {
|
|
356
|
+
captureHookFailure({ command: policyId, kind: 'bridge-timeout' });
|
|
357
|
+
return faultDecision('bridge-timeout');
|
|
358
|
+
}
|
|
359
|
+
// A bridge that exits before spawnSync finishes writing its stdin reports EPIPE although it ran to
|
|
360
|
+
// completion; its exit status and answer are judged below, like any other run.
|
|
361
|
+
const completed = result.error && result.error.code === 'EPIPE' && result.status !== null;
|
|
362
|
+
if ((result.error && !completed) || result.status === null) {
|
|
363
|
+
if (result.signal && !['SIGTERM', 'SIGINT', 'SIGPIPE'].includes(result.signal)) {
|
|
364
|
+
captureHookFailure({ command: policyId, kind: `bridge-exec-fault:${result.signal}` });
|
|
365
|
+
}
|
|
366
|
+
return faultDecision('bridge-spawn');
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
const decision = JSON.parse(result.stdout.trim());
|
|
370
|
+
if (decision && typeof decision === 'object' && typeof decision.effect === 'string') return decision;
|
|
371
|
+
} catch { /* malformed bridge output is a bridge fault */ }
|
|
372
|
+
captureHookFailure({ command: policyId, kind: 'bridge-answer-invalid' });
|
|
373
|
+
return faultDecision('bridge-answer-invalid');
|
|
406
374
|
}
|
|
407
375
|
|
|
408
376
|
function classifyPreToolUsePolicy(toolName) {
|
|
@@ -412,11 +380,11 @@ function classifyPreToolUsePolicy(toolName) {
|
|
|
412
380
|
return null;
|
|
413
381
|
}
|
|
414
382
|
|
|
415
|
-
function evaluatePreToolUsePolicy(
|
|
416
|
-
const toolName =
|
|
383
|
+
function evaluatePreToolUsePolicy(payload, nativeRaw, projectRoot, agent) {
|
|
384
|
+
const toolName = payload && payload.toolCall && payload.toolCall.name || payload && payload.tool_name || '';
|
|
417
385
|
const scriptName = classifyPreToolUsePolicy(toolName);
|
|
418
386
|
if (!scriptName) return { decision: 'allow' };
|
|
419
|
-
return delegateToPolicy(projectRoot, agent, scriptName,
|
|
387
|
+
return delegateToPolicy(projectRoot, agent, 'PreToolUse', scriptName.replace(/\.sh$/, ''), nativeRaw);
|
|
420
388
|
}
|
|
421
389
|
|
|
422
390
|
/**
|
|
@@ -431,17 +399,17 @@ function evaluatePreToolUsePolicy(canonicalPayload, projectRoot, agent) {
|
|
|
431
399
|
* role is out of FR-001's four-policy scope) — ack only; the adapter's toNative
|
|
432
400
|
* decides the harness-native shape regardless of this placeholder decision.
|
|
433
401
|
*/
|
|
434
|
-
function evaluateEvent(event,
|
|
402
|
+
function evaluateEvent(event, payload, nativeRaw, projectRoot, agent) {
|
|
435
403
|
switch (event) {
|
|
436
404
|
case 'SessionStart':
|
|
437
|
-
delegateToPolicy(projectRoot, agent, 'session-context
|
|
405
|
+
delegateToPolicy(projectRoot, agent, event, 'session-context', nativeRaw);
|
|
438
406
|
return { decision: 'allow' };
|
|
439
407
|
|
|
440
408
|
case 'PreToolUse':
|
|
441
|
-
return evaluatePreToolUsePolicy(
|
|
409
|
+
return evaluatePreToolUsePolicy(payload, nativeRaw, projectRoot, agent);
|
|
442
410
|
|
|
443
411
|
case 'Stop':
|
|
444
|
-
return delegateToPolicy(projectRoot, agent, 'stop-check
|
|
412
|
+
return delegateToPolicy(projectRoot, agent, event, 'stop-check', nativeRaw);
|
|
445
413
|
|
|
446
414
|
case 'PostToolUse':
|
|
447
415
|
case 'PreInvocation':
|
|
@@ -450,82 +418,7 @@ function evaluateEvent(event, canonicalPayload, projectRoot, agent) {
|
|
|
450
418
|
}
|
|
451
419
|
}
|
|
452
420
|
|
|
453
|
-
|
|
454
|
-
// 'gemini', including unset/empty. An unrecognized non-empty value is likely a typo, so it
|
|
455
|
-
// gets a stderr warning even though the fallback still applies.
|
|
456
|
-
function resolveAgent() {
|
|
457
|
-
const raw = process.env.DOFLOW_AGENT || '';
|
|
458
|
-
const agent = raw.toLowerCase();
|
|
459
|
-
if (agent === 'gemini') return 'gemini';
|
|
460
|
-
if (raw && agent !== 'antigravity') {
|
|
461
|
-
process.stderr.write(`stream-hook-runner: unrecognized DOFLOW_AGENT="${raw}", falling back to antigravity\n`);
|
|
462
|
-
}
|
|
463
|
-
return 'antigravity';
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function resolveAdapter(agent) {
|
|
467
|
-
return agent === 'gemini' ? geminiAdapter : antigravityAdapter;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function sniffEventFromPayload(payload) {
|
|
471
|
-
if (!payload || typeof payload !== 'object') return null;
|
|
472
|
-
// 'toolCall' in payload (key presence), not payload.toolCall (truthiness): a real Antigravity
|
|
473
|
-
// PreToolUse payload can carry a null/empty toolCall alongside stepIdx (also present on
|
|
474
|
-
// PostToolUse payloads) — truthiness alone let such a payload fall through to the stepIdx check
|
|
475
|
-
// below and get misclassified as PostToolUse, silently skipping the gate entirely
|
|
476
|
-
// (022-code-review finding). Checked before Stop/PostToolUse since a missed PreToolUse
|
|
477
|
-
// classification fails the gate open, the worse failure mode of the two.
|
|
478
|
-
if ('toolCall' in payload || payload.tool_name !== undefined) return 'PreToolUse';
|
|
479
|
-
if (payload.invocationNum !== undefined) return 'PreInvocation';
|
|
480
|
-
if (payload.terminationReason !== undefined || payload.executionNum !== undefined) return 'Stop';
|
|
481
|
-
if (payload.stepIdx !== undefined || payload.terminationBehavior !== undefined) return 'PostToolUse';
|
|
482
|
-
if (payload.session_id !== undefined && payload.cwd !== undefined) return 'SessionStart';
|
|
483
|
-
return null;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// ── Back-compat helpers ──────────────────────────────────────────────────────
|
|
487
|
-
//
|
|
488
|
-
// evaluateToolCall/evaluatePayload predate the event-dispatcher/adapter split and are kept for
|
|
489
|
-
// callers (including test/hooks/stream-hook-runner.test.js) that evaluate a single Antigravity-
|
|
490
|
-
// shaped payload directly without going through main()'s argv/env-driven dispatch. Both return the
|
|
491
|
-
// CANONICAL decision shape (never harness-native-translated) exactly as they always have.
|
|
492
|
-
|
|
493
|
-
function evaluateToolCall(toolCall, projectRoot) {
|
|
494
|
-
if (!toolCall || typeof toolCall !== 'object') {
|
|
495
|
-
return { decision: 'allow' };
|
|
496
|
-
}
|
|
497
|
-
const canonicalPayload = antigravityAdapter.toCanonical('PreToolUse', { toolCall });
|
|
498
|
-
return evaluatePreToolUsePolicy(canonicalPayload, projectRoot, 'antigravity');
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function evaluatePayload(payload, projectRoot) {
|
|
502
|
-
if (!payload || typeof payload !== 'object') {
|
|
503
|
-
return { decision: 'allow' };
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// PreToolUse: toolCall object present
|
|
507
|
-
if (payload.toolCall) {
|
|
508
|
-
return evaluateToolCall(payload.toolCall, projectRoot);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// PreInvocation: injection hook
|
|
512
|
-
if (payload.invocationNum !== undefined) {
|
|
513
|
-
return { injectSteps: [] };
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Stop hook: delegate to the canonical stop-check policy
|
|
517
|
-
if (payload.terminationReason !== undefined) {
|
|
518
|
-
const canonicalPayload = antigravityAdapter.toCanonical('Stop', payload);
|
|
519
|
-
return delegateToPolicy(projectRoot, 'antigravity', 'stop-check.sh', canonicalPayload);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// PostToolUse / PostInvocation: empty object response
|
|
523
|
-
if (payload.stepIdx !== undefined || payload.terminationBehavior !== undefined) {
|
|
524
|
-
return {};
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return { decision: 'allow' };
|
|
528
|
-
}
|
|
421
|
+
const RUNNER_EVENTS = new Set(['SessionStart', 'PreToolUse', 'PostToolUse', 'Stop', 'PreInvocation']);
|
|
529
422
|
|
|
530
423
|
async function main() {
|
|
531
424
|
try {
|
|
@@ -542,17 +435,17 @@ async function main() {
|
|
|
542
435
|
}
|
|
543
436
|
|
|
544
437
|
const projectRoot = resolveProjectRoot(payload.workspacePaths);
|
|
545
|
-
const agent =
|
|
546
|
-
const adapter =
|
|
547
|
-
const event = explicitEvent
|
|
438
|
+
const agent = 'antigravity';
|
|
439
|
+
const adapter = antigravityAdapter;
|
|
440
|
+
const event = explicitEvent;
|
|
548
441
|
|
|
549
|
-
if (!event) {
|
|
442
|
+
if (!event || !RUNNER_EVENTS.has(event)) {
|
|
443
|
+
captureHookFailure({ command: 'stream-hook-runner', kind: 'runner-event-missing' });
|
|
550
444
|
process.stdout.write(JSON.stringify({ decision: 'allow' }) + '\n');
|
|
551
445
|
return;
|
|
552
446
|
}
|
|
553
447
|
|
|
554
|
-
const
|
|
555
|
-
const decision = evaluateEvent(event, canonicalPayload, projectRoot, agent);
|
|
448
|
+
const decision = evaluateEvent(event, payload, rawInput, projectRoot, agent);
|
|
556
449
|
const native = adapter.toNative(event, decision);
|
|
557
450
|
|
|
558
451
|
// An empty object is a harness's documented "say nothing" answer (e.g. Antigravity's Stop
|
|
@@ -576,15 +469,10 @@ if (require.main === module) {
|
|
|
576
469
|
}
|
|
577
470
|
|
|
578
471
|
module.exports = {
|
|
579
|
-
evaluateToolCall,
|
|
580
|
-
evaluatePayload,
|
|
581
472
|
resolveProjectRoot,
|
|
473
|
+
delegateToPolicy,
|
|
582
474
|
evaluateEvent,
|
|
583
|
-
resolveAgent,
|
|
584
|
-
resolveAdapter,
|
|
585
|
-
resolvePolicyScript,
|
|
586
475
|
classifyPreToolUsePolicy,
|
|
587
|
-
sniffEventFromPayload,
|
|
588
476
|
isProgrammingError,
|
|
589
477
|
captureHookFailure,
|
|
590
478
|
};
|
|
@@ -754,6 +754,7 @@
|
|
|
754
754
|
"claude",
|
|
755
755
|
"codex",
|
|
756
756
|
"gemini",
|
|
757
|
+
"pi",
|
|
757
758
|
"kiro",
|
|
758
759
|
"antigravity"
|
|
759
760
|
],
|
|
@@ -762,6 +763,7 @@
|
|
|
762
763
|
"claude": "../.doflow/shared/hooks",
|
|
763
764
|
"codex": "../.doflow/shared/hooks",
|
|
764
765
|
"gemini": "../.doflow/shared/hooks",
|
|
766
|
+
"pi": "../.doflow/shared/hooks",
|
|
765
767
|
"kiro": "../.doflow/shared/hooks",
|
|
766
768
|
"antigravity": "../.doflow/shared/hooks"
|
|
767
769
|
},
|
|
@@ -778,6 +780,10 @@
|
|
|
778
780
|
"renderer": "copy-tree",
|
|
779
781
|
"capability": "hooks"
|
|
780
782
|
},
|
|
783
|
+
"pi": {
|
|
784
|
+
"renderer": "copy-tree",
|
|
785
|
+
"capability": "hooks"
|
|
786
|
+
},
|
|
781
787
|
"kiro": {
|
|
782
788
|
"renderer": "copy-tree",
|
|
783
789
|
"capability": "hooks"
|
|
@@ -787,6 +793,19 @@
|
|
|
787
793
|
"capability": "hooks"
|
|
788
794
|
}
|
|
789
795
|
}
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
"id": "pi.guard-extension",
|
|
799
|
+
"kind": "hooks",
|
|
800
|
+
"source": "core/harnesses/pi/extensions/doflow-guard.js",
|
|
801
|
+
"appliesTo": ["pi"],
|
|
802
|
+
"projection": {
|
|
803
|
+
"pi": {
|
|
804
|
+
"renderer": "pi-guard-extension",
|
|
805
|
+
"capability": "hooks"
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
"ownership": "ledger-owned-file"
|
|
790
809
|
}
|
|
791
810
|
]
|
|
792
811
|
}
|
|
@@ -134,8 +134,8 @@
|
|
|
134
134
|
{
|
|
135
135
|
"harness": "pi",
|
|
136
136
|
"completeness": "lower-bound",
|
|
137
|
-
"note": "
|
|
138
|
-
"hookEvents": [],
|
|
137
|
+
"note": "DoFlow installs a user- or project-scope extension that registers tool_call through pi.on() and invokes the shared lifecycle bridge. Project scope requires project trust. The handler can block before tools execute; tool_execution_start remains observation-only. See https://pi.dev/docs/latest/extensions.",
|
|
138
|
+
"hookEvents": ["tool_call"],
|
|
139
139
|
"evidence": [
|
|
140
140
|
"https://pi.dev/docs/latest/extensions",
|
|
141
141
|
"https://pi.dev/docs/latest/settings",
|