aiwcli 0.12.0 → 0.12.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/dist/lib/template-installer.js +3 -3
- package/dist/lib/version.js +2 -2
- package/dist/templates/_shared/hooks-ts/session_end.ts +75 -4
- package/dist/templates/_shared/hooks-ts/session_start.ts +10 -1
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +12 -0
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +45 -29
- package/dist/templates/_shared/lib-ts/base/logger.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +1 -1
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +151 -29
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +14 -13
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +3 -2
- package/dist/templates/_shared/scripts/resume_handoff.ts +29 -4
- package/dist/templates/_shared/scripts/save_handoff.ts +7 -7
- package/dist/templates/_shared/scripts/status_line.ts +103 -70
- package/dist/templates/cc-native/.claude/settings.json +11 -12
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +1 -7
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-EVOLUTION.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-PATTERNS.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-STRUCTURE.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ASSUMPTION-TRACER.md +56 -57
- package/dist/templates/cc-native/_cc-native/agents/plan-review/CLARITY-AUDITOR.md +53 -54
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-GAPS.md +70 -71
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-ORDERING.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -73
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -65
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DEVILS-ADVOCATE.md +56 -57
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -87
- package/dist/templates/cc-native/_cc-native/agents/plan-review/HANDOFF-READINESS.md +59 -60
- package/dist/templates/cc-native/_cc-native/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -59
- package/dist/templates/cc-native/_cc-native/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-DEPENDENCY.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-FMEA.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-PREMORTEM.md +71 -72
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-REVERSIBILITY.md +74 -75
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SCOPE-BOUNDARY.md +77 -78
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SKEPTIC.md +68 -69
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -72
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TRADEOFF-COSTS.md +67 -68
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -66
- package/dist/templates/cc-native/_cc-native/agents/plan-review/VERIFY-COVERAGE.md +74 -75
- package/dist/templates/cc-native/_cc-native/agents/plan-review/VERIFY-STRENGTH.md +69 -70
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +19 -2
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +28 -1013
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +24 -8
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +3 -2
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +5 -5
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +4 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +5 -5
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/format.ts +597 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/index.ts +26 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/tracker.ts +107 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/write.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +19 -820
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +77 -5
- package/dist/templates/cc-native/_cc-native/lib-ts/graduation.ts +132 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +7 -8
- package/dist/templates/cc-native/_cc-native/lib-ts/output-builder.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +3 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/review-pipeline.ts +489 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +14 -11
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +108 -108
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +2 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +18 -18
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +75 -74
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +8 -8
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +34 -34
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +4 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +184 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +35 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +48 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +3 -3
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -75,14 +75,14 @@ export async function checkTemplateStatus(templatePath, targetDir, ides, templat
|
|
|
75
75
|
* Patterns to exclude when copying template directories.
|
|
76
76
|
* These are development/test artifacts that shouldn't be packaged.
|
|
77
77
|
*/
|
|
78
|
-
const EXCLUDED_PATTERNS = [
|
|
78
|
+
const EXCLUDED_PATTERNS = new Set([
|
|
79
79
|
'_output',
|
|
80
|
-
];
|
|
80
|
+
]);
|
|
81
81
|
/**
|
|
82
82
|
* Check if a filename should be excluded from copying
|
|
83
83
|
*/
|
|
84
84
|
export function shouldExclude(name) {
|
|
85
|
-
return EXCLUDED_PATTERNS.
|
|
85
|
+
return EXCLUDED_PATTERNS.has(name);
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
88
|
* Copy directory recursively with proper error handling.
|
package/dist/lib/version.js
CHANGED
|
@@ -37,7 +37,7 @@ const MIN_CLAUDE_CODE_VERSION = '0.1.0';
|
|
|
37
37
|
* Known incompatible Claude Code versions.
|
|
38
38
|
* These versions have confirmed issues with AI Workflow CLI.
|
|
39
39
|
*/
|
|
40
|
-
const INCOMPATIBLE_VERSIONS = ['0.0.9'];
|
|
40
|
+
const INCOMPATIBLE_VERSIONS = new Set(['0.0.9']);
|
|
41
41
|
/**
|
|
42
42
|
* Detects Claude Code version by executing `claude --version`.
|
|
43
43
|
*
|
|
@@ -109,7 +109,7 @@ export function checkVersionCompatibility(version) {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
// Check known incompatible versions first
|
|
112
|
-
if (INCOMPATIBLE_VERSIONS.
|
|
112
|
+
if (INCOMPATIBLE_VERSIONS.has(version)) {
|
|
113
113
|
return {
|
|
114
114
|
compatible: false,
|
|
115
115
|
version,
|
|
@@ -5,18 +5,70 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as crypto from "node:crypto";
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
8
9
|
|
|
9
|
-
import { getProjectRoot } from "../lib-ts/base/constants.js";
|
|
10
|
+
import { getProjectRoot, getContextDir } from "../lib-ts/base/constants.js";
|
|
10
11
|
import { getGitState } from "../lib-ts/base/git-state.js";
|
|
11
12
|
import {
|
|
12
|
-
loadHookInput,
|
|
13
|
+
loadHookInput, runHook, logDebug, logInfo, logWarn, logError, logDiagnostic,
|
|
13
14
|
} from "../lib-ts/base/hook-utils.js";
|
|
14
15
|
import { nowIso } from "../lib-ts/base/utils.js";
|
|
15
16
|
import { getContextBySessionId, saveState } from "../lib-ts/context/context-store.js";
|
|
16
17
|
import {
|
|
17
|
-
|
|
18
|
+
findLatestPlan, normalizePlanContent, generatePlanId, extractPlanAnchors,
|
|
18
19
|
} from "../lib-ts/context/plan-manager.js";
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Archive session transcript to context's session-transcripts/ folder.
|
|
23
|
+
* Returns archived path on success, null if skipped or failed.
|
|
24
|
+
*/
|
|
25
|
+
function archiveTranscript(
|
|
26
|
+
transcriptPath: string,
|
|
27
|
+
contextId: string,
|
|
28
|
+
sessionId: string,
|
|
29
|
+
projectRoot: string,
|
|
30
|
+
): string | null {
|
|
31
|
+
// 1. Validate inputs
|
|
32
|
+
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
|
33
|
+
logDebug("session_end", `Transcript not found: ${transcriptPath}`);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Ensure session-transcripts directory exists
|
|
38
|
+
const contextDir = getContextDir(contextId, projectRoot);
|
|
39
|
+
const transcriptsDir = path.join(contextDir, "session-transcripts");
|
|
40
|
+
fs.mkdirSync(transcriptsDir, { recursive: true });
|
|
41
|
+
|
|
42
|
+
// 3. Generate archive filename: YYYY-MM-DD-HHMM-{session_id}.jsonl
|
|
43
|
+
const now = new Date();
|
|
44
|
+
// Format: 2026-02-14-1400 (year-month-day-hourminute)
|
|
45
|
+
// Note: Hours and minutes are concatenated without separator (HHMM)
|
|
46
|
+
const timestamp =
|
|
47
|
+
`${now.getFullYear()}-` +
|
|
48
|
+
`${String(now.getMonth() + 1).padStart(2, "0")}-` +
|
|
49
|
+
`${String(now.getDate()).padStart(2, "0")}-` +
|
|
50
|
+
`${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
51
|
+
|
|
52
|
+
// 4. Handle collisions (rare, but possible with rapid session churn)
|
|
53
|
+
let archiveName = `${timestamp}-${sessionId}.jsonl`;
|
|
54
|
+
let archivePath = path.join(transcriptsDir, archiveName);
|
|
55
|
+
let counter = 2;
|
|
56
|
+
while (fs.existsSync(archivePath)) {
|
|
57
|
+
archiveName = `${timestamp}-${sessionId}-${counter}.jsonl`;
|
|
58
|
+
archivePath = path.join(transcriptsDir, archiveName);
|
|
59
|
+
counter++;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 5. Copy transcript file
|
|
63
|
+
try {
|
|
64
|
+
fs.copyFileSync(transcriptPath, archivePath);
|
|
65
|
+
return archivePath;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logError("session_end", `Failed to copy transcript: ${error}`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
20
72
|
function main(): void {
|
|
21
73
|
const payload = loadHookInput();
|
|
22
74
|
if (!payload) return;
|
|
@@ -50,6 +102,25 @@ function main(): void {
|
|
|
50
102
|
};
|
|
51
103
|
state.last_active = nowIso();
|
|
52
104
|
|
|
105
|
+
// Archive transcript (NEW)
|
|
106
|
+
// Note: state is a ContextState object (from getContextBySessionId on line 33)
|
|
107
|
+
// state.id is the context ID used to construct paths like _output/contexts/{context_id}/
|
|
108
|
+
if (payload.transcript_path) {
|
|
109
|
+
try {
|
|
110
|
+
const archived = archiveTranscript(
|
|
111
|
+
payload.transcript_path,
|
|
112
|
+
state.id, // Context ID, verified by existing code on line 98: saveState(state.id, ...)
|
|
113
|
+
sessionId,
|
|
114
|
+
projectRoot,
|
|
115
|
+
);
|
|
116
|
+
if (archived) {
|
|
117
|
+
logInfo("session_end", `Archived transcript: ${path.basename(archived)}`);
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
logError("session_end", `Transcript archival failed: ${error}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
53
124
|
// Plan fallback assignment (skip in plan mode — rejected plans shouldn't stage)
|
|
54
125
|
if (permissionMode !== "plan") {
|
|
55
126
|
// Step 1: Assign plan fields if missing
|
|
@@ -57,7 +128,7 @@ function main(): void {
|
|
|
57
128
|
const latestPlanPath = findLatestPlan(state.id, projectRoot);
|
|
58
129
|
if (latestPlanPath) {
|
|
59
130
|
try {
|
|
60
|
-
const content = fs.readFileSync(latestPlanPath, "
|
|
131
|
+
const content = fs.readFileSync(latestPlanPath, "utf-8");
|
|
61
132
|
const normalized = normalizePlanContent(content);
|
|
62
133
|
const planHash = crypto.createHash("sha256")
|
|
63
134
|
.update(normalized, "utf-8")
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "../lib-ts/context/context-store.js";
|
|
14
14
|
import {
|
|
15
15
|
buildRestoreSections, formatHandoffContinuation, getModeDisplay,
|
|
16
|
+
buildContextInventory,
|
|
16
17
|
} from "../lib-ts/context/context-formatter.js";
|
|
17
18
|
import type { ContextState } from "../lib-ts/types.js";
|
|
18
19
|
|
|
@@ -39,6 +40,9 @@ function handleCompactRestore(sessionId: string, projectRoot: string): void {
|
|
|
39
40
|
const restore = buildRestoreSections(state, projectRoot, true);
|
|
40
41
|
if (restore) sections.push(restore);
|
|
41
42
|
|
|
43
|
+
const inventory = buildContextInventory(state, projectRoot);
|
|
44
|
+
if (inventory) sections.push("", inventory);
|
|
45
|
+
|
|
42
46
|
sections.push(
|
|
43
47
|
"",
|
|
44
48
|
"---",
|
|
@@ -79,6 +83,9 @@ async function handleClearRestore(sessionId: string, projectRoot: string): Promi
|
|
|
79
83
|
const restore = buildRestoreSections(ctx, projectRoot, false);
|
|
80
84
|
if (restore) sections.push(restore);
|
|
81
85
|
|
|
86
|
+
const inventory = buildContextInventory(ctx, projectRoot);
|
|
87
|
+
if (inventory) sections.push("", inventory);
|
|
88
|
+
|
|
82
89
|
sections.push(
|
|
83
90
|
"",
|
|
84
91
|
"---",
|
|
@@ -100,7 +107,9 @@ async function handleClearRestore(sessionId: string, projectRoot: string): Promi
|
|
|
100
107
|
logInfo("session_start", `Clear restore: ${ctx.id} has_handoff → active (handoff_consumed=true)`);
|
|
101
108
|
|
|
102
109
|
const handoffContent = formatHandoffContinuation(ctx, projectRoot);
|
|
103
|
-
|
|
110
|
+
const handoffInventory = buildContextInventory(ctx, projectRoot);
|
|
111
|
+
const combined = handoffInventory ? handoffContent + "\n\n" + handoffInventory : handoffContent;
|
|
112
|
+
emitContext(combined);
|
|
104
113
|
return;
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getContextBySessionId, bindSession, maybeActivate, saveState,
|
|
15
15
|
} from "../lib-ts/context/context-store.js";
|
|
16
16
|
import { determineContext, BlockRequest } from "../lib-ts/context/context-selector.js";
|
|
17
|
+
import { buildContextInventory } from "../lib-ts/context/context-formatter.js";
|
|
17
18
|
|
|
18
19
|
async function asyncMain(): Promise<void> {
|
|
19
20
|
const payload = loadHookInput();
|
|
@@ -64,6 +65,17 @@ async function asyncMain(): Promise<void> {
|
|
|
64
65
|
if (outputText) {
|
|
65
66
|
outputs.push(outputText);
|
|
66
67
|
}
|
|
68
|
+
|
|
69
|
+
// Append context folder inventory
|
|
70
|
+
try {
|
|
71
|
+
const boundState = getContextBySessionId(sessionId, projectRoot);
|
|
72
|
+
if (boundState) {
|
|
73
|
+
const inventory = buildContextInventory(boundState, projectRoot);
|
|
74
|
+
if (inventory) outputs.push(inventory);
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
logWarn("user_prompt_submit", `Inventory failed (non-critical): ${e}`);
|
|
78
|
+
}
|
|
67
79
|
} catch (e) {
|
|
68
80
|
if (e instanceof BlockRequest) {
|
|
69
81
|
emitBlock((e as Error).message);
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
* See SPEC.md §5
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import * as fs from "node:fs";
|
|
8
|
-
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
|
|
9
9
|
import { getProjectRoot } from "./constants.js";
|
|
10
|
+
import { logDebug, logWarn, hookLog, setSessionId, getContextPath as _getContextPath } from "./logger.js";
|
|
10
11
|
import { getContextBySessionId } from "../context/context-store.js";
|
|
11
12
|
import type { HookInput, HookOutput, PermissionRequestOutput } from "../types.js";
|
|
12
13
|
|
|
13
14
|
// Re-export logger functions for convenience (matches Python hook_utils re-exports)
|
|
14
|
-
export {
|
|
15
|
+
export { setSessionId };
|
|
15
16
|
|
|
16
17
|
// Context window baseline: tokens not visible in hook data §5.9
|
|
17
18
|
export const CONTEXT_BASELINE_TOKENS = 22_600;
|
|
@@ -93,7 +94,7 @@ export function checkSkipPersistence(
|
|
|
93
94
|
const toolInput = getToolInput(payload);
|
|
94
95
|
if (!toolInput) return false;
|
|
95
96
|
|
|
96
|
-
const metadata = toolInput
|
|
97
|
+
const {metadata} = toolInput;
|
|
97
98
|
if (metadata && typeof metadata === "object" && metadata.skip_persistence) {
|
|
98
99
|
logDebug(hookName, "Skipping persistence (skip_persistence flag set)");
|
|
99
100
|
return true;
|
|
@@ -105,11 +106,23 @@ export function checkSkipPersistence(
|
|
|
105
106
|
* Emit hookSpecificOutput with additionalContext to stdout.
|
|
106
107
|
* hookEventName is required by Claude Code's Zod validator (discriminated union).
|
|
107
108
|
* Auto-detected from stdin payload (set by loadHookInput/runHook).
|
|
109
|
+
*
|
|
110
|
+
* SubagentStop and Stop events use top-level systemMessage field instead of hookSpecificOutput.
|
|
108
111
|
* See SPEC.md §5.5
|
|
109
112
|
*/
|
|
110
113
|
export function emitContext(additionalContext: string): void {
|
|
111
114
|
const eventName = _lastHookEvent ?? undefined;
|
|
112
115
|
const tool = _lastToolName;
|
|
116
|
+
|
|
117
|
+
// SubagentStop and Stop use top-level systemMessage field
|
|
118
|
+
if (eventName === "SubagentStop" || eventName === "Stop") {
|
|
119
|
+
const out = { systemMessage: additionalContext };
|
|
120
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
121
|
+
_logEmit("systemMessage", additionalContext.length, { event: eventName ?? "unknown", systemMessage: additionalContext });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// All other events use hookSpecificOutput
|
|
113
126
|
const out: HookOutput = {
|
|
114
127
|
hookSpecificOutput: {
|
|
115
128
|
...(eventName ? { hookEventName: eventName } : {}),
|
|
@@ -153,8 +166,8 @@ export function emitContextAndBlock(
|
|
|
153
166
|
process.stdout.write(json + "\n");
|
|
154
167
|
}
|
|
155
168
|
|
|
156
|
-
/** Log hook output (context or block) to hook-log.jsonl for visibility. */
|
|
157
|
-
function _logEmit(type: "context" | "block", chars: number, payload: Record<string, any>): void {
|
|
169
|
+
/** Log hook output (context, systemMessage, or block) to hook-log.jsonl for visibility. */
|
|
170
|
+
function _logEmit(type: "context" | "systemMessage" | "block", chars: number, payload: Record<string, any>): void {
|
|
158
171
|
const hook = _cachedHookName ?? "unknown";
|
|
159
172
|
const event = payload.event ?? "unknown";
|
|
160
173
|
const mechanism = payload.mechanism ? ` via ${payload.mechanism}` : "";
|
|
@@ -268,27 +281,28 @@ export function emitPermissionDecision(
|
|
|
268
281
|
export function emitBlock(reason: string, context?: string): void {
|
|
269
282
|
const event = _lastHookEvent;
|
|
270
283
|
switch (event) {
|
|
271
|
-
case "
|
|
272
|
-
|
|
273
|
-
break;
|
|
274
|
-
case "UserPromptSubmit":
|
|
275
|
-
emitBlockPrompt(reason, context);
|
|
284
|
+
case "PermissionRequest":
|
|
285
|
+
emitPermissionDecision("deny", { message: reason });
|
|
276
286
|
break;
|
|
277
287
|
case "PostToolUse":
|
|
278
288
|
case "PostToolUseFailure":
|
|
279
289
|
emitBlockViaExit(reason, context);
|
|
280
290
|
break;
|
|
291
|
+
case "PreToolUse":
|
|
292
|
+
emitContextAndBlock(context ?? reason, reason);
|
|
293
|
+
break;
|
|
281
294
|
case "Stop":
|
|
282
295
|
case "SubagentStop":
|
|
283
296
|
emitBlockTopLevel(reason);
|
|
284
297
|
break;
|
|
285
|
-
case "
|
|
286
|
-
|
|
298
|
+
case "UserPromptSubmit":
|
|
299
|
+
emitBlockPrompt(reason, context);
|
|
287
300
|
break;
|
|
288
|
-
default:
|
|
301
|
+
default: {
|
|
289
302
|
logWarn(_cachedHookName ?? "unknown",
|
|
290
303
|
`emitBlock() called from ${event ?? "unknown"} — no blocking mechanism exists for this event type, ignoring`);
|
|
291
|
-
break;
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
292
306
|
}
|
|
293
307
|
}
|
|
294
308
|
|
|
@@ -296,7 +310,7 @@ export function emitBlock(reason: string, context?: string): void {
|
|
|
296
310
|
* Auto-detect template origin from the hook script path.
|
|
297
311
|
*/
|
|
298
312
|
function detectTemplate(scriptPath = ""): string {
|
|
299
|
-
const p = (scriptPath || (process.argv[1] ?? "")).
|
|
313
|
+
const p = (scriptPath || (process.argv[1] ?? "")).replaceAll('\\', "/");
|
|
300
314
|
if (p.includes("/_shared/hooks/") || p.startsWith("_shared/hooks/")) {
|
|
301
315
|
return "shared";
|
|
302
316
|
}
|
|
@@ -438,16 +452,16 @@ export function runHook(
|
|
|
438
452
|
const result = mainFunc();
|
|
439
453
|
exitCode = typeof result === "number" ? result : 0;
|
|
440
454
|
status = exitCode !== 0 ? "blocked" : "success";
|
|
441
|
-
} catch (
|
|
442
|
-
if (
|
|
443
|
-
const code = parseInt(
|
|
444
|
-
exitCode = isNaN(code) ? (
|
|
455
|
+
} catch (error: any) {
|
|
456
|
+
if (error instanceof Error && error.message.startsWith("SystemExit:")) {
|
|
457
|
+
const code = parseInt(error.message.slice(11), 10);
|
|
458
|
+
exitCode = isNaN(code) ? (error.message.slice(11) ? 1 : 0) : code;
|
|
445
459
|
status = exitCode !== 0 ? "blocked" : "success";
|
|
446
460
|
} else {
|
|
447
461
|
exitCode = 0; // Non-blocking
|
|
448
462
|
status = "error";
|
|
449
|
-
const stack =
|
|
450
|
-
errorInfo = [
|
|
463
|
+
const stack = error instanceof Error ? error.stack ?? "" : "";
|
|
464
|
+
errorInfo = [error instanceof Error ? error : new Error(String(error)), stack];
|
|
451
465
|
}
|
|
452
466
|
}
|
|
453
467
|
|
|
@@ -487,19 +501,19 @@ export function runHookAsync(
|
|
|
487
501
|
_emitHookEnd(hookName, startTime, exitCode, exitCode !== 0 ? "blocked" : "success", null, startData, event, tool, template);
|
|
488
502
|
_drainAndExit(exitCode);
|
|
489
503
|
})
|
|
490
|
-
.catch((
|
|
504
|
+
.catch((error: any) => {
|
|
491
505
|
let exitCode = 0;
|
|
492
506
|
let status = "error";
|
|
493
507
|
let errorInfo: [Error, string] | null = null;
|
|
494
508
|
|
|
495
|
-
if (
|
|
496
|
-
const code = parseInt(
|
|
497
|
-
exitCode = isNaN(code) ? (
|
|
509
|
+
if (error instanceof Error && error.message.startsWith("SystemExit:")) {
|
|
510
|
+
const code = parseInt(error.message.slice(11), 10);
|
|
511
|
+
exitCode = isNaN(code) ? (error.message.slice(11) ? 1 : 0) : code;
|
|
498
512
|
status = exitCode !== 0 ? "blocked" : "success";
|
|
499
513
|
} else {
|
|
500
514
|
exitCode = 0; // Non-blocking (fail open)
|
|
501
|
-
const stack =
|
|
502
|
-
errorInfo = [
|
|
515
|
+
const stack = error instanceof Error ? error.stack ?? "" : "";
|
|
516
|
+
errorInfo = [error instanceof Error ? error : new Error(String(error)), stack];
|
|
503
517
|
}
|
|
504
518
|
|
|
505
519
|
_emitHookEnd(hookName, startTime, exitCode, status, errorInfo, startData, event, tool, template);
|
|
@@ -541,7 +555,7 @@ function _emitHookEnd(
|
|
|
541
555
|
if (errorInfo) {
|
|
542
556
|
const [err, tb] = errorInfo;
|
|
543
557
|
endData.error_type = err.constructor.name;
|
|
544
|
-
hookLog("error", hookName, `[${endEvent}] ${err.constructor.name}: ${String(err).
|
|
558
|
+
hookLog("error", hookName, `[${endEvent}] ${err.constructor.name}: ${String(err).replaceAll(/[\n\r]/g, " ").slice(0, 200)}`, { traceback_str: tb });
|
|
545
559
|
hookLog("error", hookName, `HOOK_END: ${err}`, { data: endData, traceback_str: tb });
|
|
546
560
|
} else if (status === "blocked") {
|
|
547
561
|
hookLog("warn", hookName, "HOOK_END", { data: endData });
|
|
@@ -568,3 +582,5 @@ function _drainAndExit(code: number): void {
|
|
|
568
582
|
process.exit(code);
|
|
569
583
|
});
|
|
570
584
|
}
|
|
585
|
+
|
|
586
|
+
export {logInfo, logError, logBlocking, logHookError, logDiagnostic, setContextPath, hookLog, logDebug, logWarn} from "./logger.js";
|
|
@@ -235,7 +235,7 @@ export function logHookError(
|
|
|
235
235
|
tracebackStr = "",
|
|
236
236
|
): void {
|
|
237
237
|
const errStr = typeof error === "string" ? error : String(error);
|
|
238
|
-
const msg = errStr.
|
|
238
|
+
const msg = errStr.replaceAll(/[\n\r]/g, " ").slice(0, 200);
|
|
239
239
|
const errType =
|
|
240
240
|
typeof error === "object" && error !== null
|
|
241
241
|
? error.constructor.name
|
|
@@ -92,7 +92,7 @@ export function isExecSyncError(e: unknown): e is ExecSyncError {
|
|
|
92
92
|
*/
|
|
93
93
|
export function shellQuoteWin(arg: string): string {
|
|
94
94
|
if (process.platform !== "win32") return arg;
|
|
95
|
-
return '"' + arg.
|
|
95
|
+
return '"' + arg.replaceAll('"', '""') + '"';
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// ---------------------------------------------------------------------------
|