aiwcli 0.11.0 → 0.12.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.
Files changed (118) hide show
  1. package/dist/commands/clear.d.ts +8 -0
  2. package/dist/commands/clear.js +86 -0
  3. package/dist/lib/bmad-installer.d.ts +2 -27
  4. package/dist/lib/bmad-installer.js +3 -43
  5. package/dist/lib/claude-settings-types.d.ts +2 -1
  6. package/dist/lib/env-compat.d.ts +0 -8
  7. package/dist/lib/env-compat.js +0 -12
  8. package/dist/lib/git/index.d.ts +0 -1
  9. package/dist/lib/gitignore-manager.d.ts +0 -2
  10. package/dist/lib/gitignore-manager.js +1 -1
  11. package/dist/lib/hooks-merger.d.ts +1 -15
  12. package/dist/lib/hooks-merger.js +1 -1
  13. package/dist/lib/index.d.ts +3 -7
  14. package/dist/lib/index.js +3 -11
  15. package/dist/lib/output.d.ts +2 -1
  16. package/dist/lib/settings-hierarchy.d.ts +1 -13
  17. package/dist/lib/settings-hierarchy.js +1 -1
  18. package/dist/lib/template-installer.d.ts +5 -9
  19. package/dist/lib/template-installer.js +2 -12
  20. package/dist/lib/template-linter.d.ts +3 -10
  21. package/dist/lib/template-linter.js +2 -2
  22. package/dist/lib/template-resolver.d.ts +6 -0
  23. package/dist/lib/template-resolver.js +10 -0
  24. package/dist/lib/template-settings-reconstructor.d.ts +1 -1
  25. package/dist/lib/template-settings-reconstructor.js +17 -24
  26. package/dist/lib/terminal.d.ts +3 -14
  27. package/dist/lib/terminal.js +0 -4
  28. package/dist/lib/version.d.ts +2 -11
  29. package/dist/lib/version.js +2 -2
  30. package/dist/lib/windsurf-hooks-merger.d.ts +1 -15
  31. package/dist/lib/windsurf-hooks-merger.js +1 -1
  32. package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
  33. package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
  34. package/dist/templates/_shared/hooks-ts/session_end.ts +4 -1
  35. package/dist/templates/_shared/hooks-ts/session_start.ts +15 -20
  36. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +12 -14
  37. package/dist/templates/_shared/lib-ts/CLAUDE.md +56 -7
  38. package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
  39. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +174 -43
  40. package/dist/templates/_shared/lib-ts/base/logger.ts +15 -18
  41. package/dist/templates/_shared/lib-ts/base/state-io.ts +11 -2
  42. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +181 -162
  43. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +26 -30
  44. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +12 -13
  45. package/dist/templates/_shared/lib-ts/package.json +1 -2
  46. package/dist/templates/_shared/lib-ts/templates/plan-context.ts +27 -34
  47. package/dist/templates/_shared/lib-ts/types.ts +17 -2
  48. package/dist/templates/_shared/scripts/resume_handoff.ts +62 -38
  49. package/dist/templates/_shared/scripts/save_handoff.ts +24 -24
  50. package/dist/templates/_shared/scripts/status_line.ts +102 -148
  51. package/dist/templates/_shared/workflows/handoff.md +1 -1
  52. package/dist/templates/cc-native/.claude/settings.json +183 -175
  53. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +23 -1
  54. package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
  55. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +6 -1
  56. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +316 -176
  57. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +38 -0
  58. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +51 -0
  59. package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -0
  60. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +15 -15
  61. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +11 -12
  62. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +227 -114
  63. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +64 -16
  64. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +23 -3
  65. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +119 -0
  66. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +1 -4
  67. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +7 -1
  68. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +40 -218
  69. package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -0
  70. package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +101 -0
  71. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +27 -111
  72. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -0
  73. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +5 -3
  74. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +65 -0
  75. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +184 -0
  76. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -0
  77. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +195 -0
  78. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -0
  79. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +3 -5
  80. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +30 -33
  81. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +104 -132
  82. package/dist/templates/cc-native/_cc-native/plan-review.config.json +22 -13
  83. package/oclif.manifest.json +1 -1
  84. package/package.json +2 -3
  85. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +0 -119
  86. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +0 -130
  87. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +0 -106
  88. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-EVOLUTION.md → plan-review/ARCH-EVOLUTION.md} +0 -0
  89. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-PATTERNS.md → plan-review/ARCH-PATTERNS.md} +0 -0
  90. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-STRUCTURE.md → plan-review/ARCH-STRUCTURE.md} +0 -0
  91. /package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-TRACER.md → plan-review/ASSUMPTION-TRACER.md} +0 -0
  92. /package/dist/templates/cc-native/_cc-native/agents/{CLARITY-AUDITOR.md → plan-review/CLARITY-AUDITOR.md} +0 -0
  93. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-FEASIBILITY.md → plan-review/COMPLETENESS-FEASIBILITY.md} +0 -0
  94. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-GAPS.md → plan-review/COMPLETENESS-GAPS.md} +0 -0
  95. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-ORDERING.md → plan-review/COMPLETENESS-ORDERING.md} +0 -0
  96. /package/dist/templates/cc-native/_cc-native/agents/{CONSTRAINT-VALIDATOR.md → plan-review/CONSTRAINT-VALIDATOR.md} +0 -0
  97. /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-ADR-VALIDATOR.md → plan-review/DESIGN-ADR-VALIDATOR.md} +0 -0
  98. /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-SCALE-MATCHER.md → plan-review/DESIGN-SCALE-MATCHER.md} +0 -0
  99. /package/dist/templates/cc-native/_cc-native/agents/{DEVILS-ADVOCATE.md → plan-review/DEVILS-ADVOCATE.md} +0 -0
  100. /package/dist/templates/cc-native/_cc-native/agents/{DOCUMENTATION-PHILOSOPHY.md → plan-review/DOCUMENTATION-PHILOSOPHY.md} +0 -0
  101. /package/dist/templates/cc-native/_cc-native/agents/{HANDOFF-READINESS.md → plan-review/HANDOFF-READINESS.md} +0 -0
  102. /package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY.md → plan-review/HIDDEN-COMPLEXITY.md} +0 -0
  103. /package/dist/templates/cc-native/_cc-native/agents/{INCREMENTAL-DELIVERY.md → plan-review/INCREMENTAL-DELIVERY.md} +0 -0
  104. /package/dist/templates/cc-native/_cc-native/agents/{RISK-DEPENDENCY.md → plan-review/RISK-DEPENDENCY.md} +0 -0
  105. /package/dist/templates/cc-native/_cc-native/agents/{RISK-FMEA.md → plan-review/RISK-FMEA.md} +0 -0
  106. /package/dist/templates/cc-native/_cc-native/agents/{RISK-PREMORTEM.md → plan-review/RISK-PREMORTEM.md} +0 -0
  107. /package/dist/templates/cc-native/_cc-native/agents/{RISK-REVERSIBILITY.md → plan-review/RISK-REVERSIBILITY.md} +0 -0
  108. /package/dist/templates/cc-native/_cc-native/agents/{SCOPE-BOUNDARY.md → plan-review/SCOPE-BOUNDARY.md} +0 -0
  109. /package/dist/templates/cc-native/_cc-native/agents/{SIMPLICITY-GUARDIAN.md → plan-review/SIMPLICITY-GUARDIAN.md} +0 -0
  110. /package/dist/templates/cc-native/_cc-native/agents/{SKEPTIC.md → plan-review/SKEPTIC.md} +0 -0
  111. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-BEHAVIOR-AUDITOR.md → plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md} +0 -0
  112. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-CHARACTERIZATION.md → plan-review/TESTDRIVEN-CHARACTERIZATION.md} +0 -0
  113. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-FIRST-VALIDATOR.md → plan-review/TESTDRIVEN-FIRST-VALIDATOR.md} +0 -0
  114. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-PYRAMID-ANALYZER.md → plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md} +0 -0
  115. /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-COSTS.md → plan-review/TRADEOFF-COSTS.md} +0 -0
  116. /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-STAKEHOLDERS.md → plan-review/TRADEOFF-STAKEHOLDERS.md} +0 -0
  117. /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-COVERAGE.md → plan-review/VERIFY-COVERAGE.md} +0 -0
  118. /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-STRENGTH.md → plan-review/VERIFY-STRENGTH.md} +0 -0
@@ -13,31 +13,20 @@
13
13
  *
14
14
  * @module lib/template-settings-reconstructor
15
15
  */
16
- import { dirname, join } from 'node:path';
17
- import { fileURLToPath } from 'node:url';
16
+ import { join } from 'node:path';
18
17
  import { mergeClaudeSettings } from './hooks-merger.js';
19
18
  import { IdePathResolver } from './ide-path-resolver.js';
20
19
  import { readClaudeSettings, writeClaudeSettings } from './settings-hierarchy.js';
21
- import { getTemplatePath } from './template-resolver.js';
20
+ import { getSharedTemplatePath, getTemplatePath } from './template-resolver.js';
22
21
  import { getTargetHooksFile, readWindsurfHooks, writeWindsurfHooks } from './windsurf-hooks-hierarchy.js';
23
22
  import { mergeWindsurfHooks } from './windsurf-hooks-merger.js';
24
- /**
25
- * Get the path to the _shared template directory.
26
- *
27
- * @returns Absolute path to the _shared template
28
- */
29
- function getSharedTemplatePath() {
30
- const currentFilePath = fileURLToPath(import.meta.url);
31
- const currentDir = dirname(currentFilePath);
32
- return join(currentDir, '..', 'templates', '_shared');
33
- }
34
23
  /**
35
24
  * Reconstruct .claude/settings.json and .windsurf/hooks.json from the union
36
25
  * of all specified templates.
37
26
  *
38
27
  * The function:
39
28
  * 1. Starts with empty settings
40
- * 2. Merges _shared template settings (always included)
29
+ * 2. Merges _shared template settings (when active templates exist)
41
30
  * 3. For each active template, merges its template-source settings
42
31
  * 4. Writes the result to the IDE settings file
43
32
  *
@@ -71,11 +60,13 @@ async function reconstructClaudeSettings(targetDir, activeTemplates, sharedTempl
71
60
  const methodsTracking = existingSettings?.methods;
72
61
  // Start from empty and merge all template settings
73
62
  let reconstructed = {};
74
- // 1. Merge _shared template settings
75
- const sharedSettingsPath = join(sharedTemplatePath, '.claude', 'settings.json');
76
- const sharedSettings = await readClaudeSettings(sharedSettingsPath);
77
- if (sharedSettings) {
78
- reconstructed = mergeClaudeSettings(reconstructed, sharedSettings);
63
+ // 1. Merge _shared template settings (only when active templates exist that need them)
64
+ if (activeTemplates.length > 0) {
65
+ const sharedSettingsPath = join(sharedTemplatePath, '.claude', 'settings.json');
66
+ const sharedSettings = await readClaudeSettings(sharedSettingsPath);
67
+ if (sharedSettings) {
68
+ reconstructed = mergeClaudeSettings(reconstructed, sharedSettings);
69
+ }
79
70
  }
80
71
  // 2. Merge each active template's settings (sequential for deterministic merge order)
81
72
  for (const template of activeTemplates) {
@@ -105,11 +96,13 @@ async function reconstructWindsurfHooks(targetDir, activeTemplates, sharedTempla
105
96
  const hooksPath = getTargetHooksFile(targetDir);
106
97
  // Start from empty
107
98
  let reconstructed = { hooks: {} };
108
- // 1. Merge _shared template hooks
109
- const sharedHooksPath = join(sharedTemplatePath, '.windsurf', 'hooks.json');
110
- const sharedHooks = await readWindsurfHooks(sharedHooksPath);
111
- if (sharedHooks) {
112
- reconstructed = mergeWindsurfHooks(reconstructed, sharedHooks);
99
+ // 1. Merge _shared template hooks (only when active templates exist that need them)
100
+ if (activeTemplates.length > 0) {
101
+ const sharedHooksPath = join(sharedTemplatePath, '.windsurf', 'hooks.json');
102
+ const sharedHooks = await readWindsurfHooks(sharedHooksPath);
103
+ if (sharedHooks) {
104
+ reconstructed = mergeWindsurfHooks(reconstructed, sharedHooks);
105
+ }
113
106
  }
114
107
  // 2. Merge each active template's hooks (sequential for deterministic merge order)
115
108
  for (const template of activeTemplates) {
@@ -29,7 +29,7 @@
29
29
  /**
30
30
  * Options for launching a new terminal window.
31
31
  */
32
- export interface TerminalLaunchOptions {
32
+ interface TerminalLaunchOptions {
33
33
  /**
34
34
  * Command to execute in the new terminal.
35
35
  */
@@ -47,7 +47,7 @@ export interface TerminalLaunchOptions {
47
47
  /**
48
48
  * Result of a terminal launch operation.
49
49
  */
50
- export interface TerminalLaunchResult {
50
+ interface TerminalLaunchResult {
51
51
  /**
52
52
  * Error message if launch failed.
53
53
  */
@@ -57,14 +57,6 @@ export interface TerminalLaunchResult {
57
57
  */
58
58
  success: boolean;
59
59
  }
60
- /**
61
- * Escape a shell argument for safe execution.
62
- * Wraps path in double quotes and escapes internal quotes.
63
- *
64
- * @param arg - Argument to escape
65
- * @returns Escaped argument safe for shell execution
66
- */
67
- declare function escapeShellArg(arg: string): string;
68
60
  /**
69
61
  * Launch a new terminal window with the specified command.
70
62
  *
@@ -96,7 +88,4 @@ declare function escapeShellArg(arg: string): string;
96
88
  * ```
97
89
  */
98
90
  export declare function launchTerminal(options: TerminalLaunchOptions): Promise<TerminalLaunchResult>;
99
- /**
100
- * Escape shell argument utility - exported for use in command construction.
101
- */
102
- export { escapeShellArg };
91
+ export {};
@@ -239,7 +239,3 @@ export async function launchTerminal(options) {
239
239
  // Linux/Unix
240
240
  return launchLinuxTerminal(cwd, command, debugLog);
241
241
  }
242
- /**
243
- * Escape shell argument utility - exported for use in command construction.
244
- */
245
- export { escapeShellArg };
@@ -25,20 +25,10 @@
25
25
  *
26
26
  * @module lib/version
27
27
  */
28
- /**
29
- * Minimum supported Claude Code version.
30
- * Versions below this will trigger a compatibility warning.
31
- */
32
- export declare const MIN_CLAUDE_CODE_VERSION = "0.1.0";
33
- /**
34
- * Known incompatible Claude Code versions.
35
- * These versions have confirmed issues with AI Workflow CLI.
36
- */
37
- export declare const INCOMPATIBLE_VERSIONS: string[];
38
28
  /**
39
29
  * Result of version compatibility check.
40
30
  */
41
- export interface VersionCheckResult {
31
+ interface VersionCheckResult {
42
32
  /**
43
33
  * Whether the version is compatible with AI Workflow CLI.
44
34
  * If version is unknown, assumes compatible (graceful degradation).
@@ -97,3 +87,4 @@ export declare function getClaudeCodeVersion(): Promise<null | string>;
97
87
  * ```
98
88
  */
99
89
  export declare function checkVersionCompatibility(version: null | string | undefined): VersionCheckResult;
90
+ export {};
@@ -32,12 +32,12 @@ const execAsync = promisify(exec);
32
32
  * Minimum supported Claude Code version.
33
33
  * Versions below this will trigger a compatibility warning.
34
34
  */
35
- export const MIN_CLAUDE_CODE_VERSION = '0.1.0';
35
+ const MIN_CLAUDE_CODE_VERSION = '0.1.0';
36
36
  /**
37
37
  * Known incompatible Claude Code versions.
38
38
  * These versions have confirmed issues with AI Workflow CLI.
39
39
  */
40
- export const INCOMPATIBLE_VERSIONS = ['0.0.9'];
40
+ const INCOMPATIBLE_VERSIONS = ['0.0.9'];
41
41
  /**
42
42
  * Detects Claude Code version by executing `claude --version`.
43
43
  *
@@ -1,18 +1,4 @@
1
- import type { WindsurfHooks, WindsurfHooksConfig } from './windsurf-hooks-types.js';
2
- /**
3
- * Merge hooks configurations from template into existing
4
- *
5
- * Strategy:
6
- * - For each event type in template hooks
7
- * - Concatenate with existing hooks for that event
8
- * - Deduplicate based on command configuration
9
- * - Maintain order: existing hooks first, then template hooks
10
- *
11
- * @param existing - Existing hooks configuration (will not be modified)
12
- * @param template - Template hooks configuration to merge
13
- * @returns New merged hooks configuration
14
- */
15
- export declare function mergeWindsurfHooksConfig(existing: undefined | WindsurfHooksConfig, template: undefined | WindsurfHooksConfig): WindsurfHooksConfig;
1
+ import type { WindsurfHooks } from './windsurf-hooks-types.js';
16
2
  /**
17
3
  * Merge complete Windsurf hooks configurations
18
4
  *
@@ -21,7 +21,7 @@ function areHookCommandsEqual(a, b) {
21
21
  * @param template - Template hooks configuration to merge
22
22
  * @returns New merged hooks configuration
23
23
  */
24
- export function mergeWindsurfHooksConfig(existing, template) {
24
+ function mergeWindsurfHooksConfig(existing, template) {
25
25
  return mergeConfigByEventType(existing, template, (existingCommands, templateCommands) => mergeArraysWithDedup(existingCommands, templateCommands, areHookCommandsEqual));
26
26
  }
27
27
  /**
@@ -150,7 +150,7 @@ If a plan document path was provided in `$ARGUMENTS`:
150
150
  Instead of writing the file directly, pipe your handoff content to the save script:
151
151
 
152
152
  ```bash
153
- python .aiwcli/_shared/scripts/save_handoff.py "{context_id}" <<'EOF'
153
+ bun .aiwcli/_shared/scripts/save_handoff.ts "{context_id}" <<'EOF'
154
154
  {Your complete handoff markdown content from Step 3}
155
155
  EOF
156
156
  ```
@@ -150,7 +150,7 @@ If a plan document path was provided in `$ARGUMENTS`:
150
150
  Instead of writing the file directly, pipe your handoff content to the save script:
151
151
 
152
152
  ```bash
153
- python .aiwcli/_shared/scripts/save_handoff.py "{context_id}" <<'EOF'
153
+ bun .aiwcli/_shared/scripts/save_handoff.ts "{context_id}" <<'EOF'
154
154
  {Your complete handoff markdown content from Step 3}
155
155
  EOF
156
156
  ```
@@ -69,7 +69,10 @@ function main(): void {
69
69
  state.plan_signature = content.slice(0, 200);
70
70
  state.plan_id = generatePlanId();
71
71
  state.plan_anchors = extractPlanAnchors(content);
72
- state.plan_consumed = false;
72
+ // Preserve plan_consumed if already true (plan was implemented) —
73
+ // resetting it would re-stage the plan and block handoff staging.
74
+ // Only set to false when no prior consumption has occurred.
75
+ state.plan_consumed = state.plan_consumed || false;
73
76
 
74
77
  logInfo("session_end", `Assigned plan fallback: hash=${planHash}, path=${latestPlanPath}`);
75
78
  } catch (error) {
@@ -3,18 +3,18 @@
3
3
  * SessionStart hook: Restore context after /clear (plan/handoff) or compaction.
4
4
  * Routes by source field to appropriate handler.
5
5
  */
6
- import { getProjectRoot } from "../lib-ts/base/constants.js";
7
6
  import {
8
- emitContext, loadHookInput, logDebug,
9
- logDiagnostic, logError as _logError, logInfo, runHook,
7
+ loadHookInput, emitContext, runHook, runHookAsync,
8
+ logDebug, logInfo, logError, logDiagnostic,
10
9
  } from "../lib-ts/base/hook-utils.js";
10
+ import { getProjectRoot } from "../lib-ts/base/constants.js";
11
+ import {
12
+ getContextBySessionId, getAllContexts, bindSession, updateMode,
13
+ } from "../lib-ts/context/context-store.js";
11
14
  import {
12
15
  buildRestoreSections, formatHandoffContinuation, getModeDisplay,
13
16
  } from "../lib-ts/context/context-formatter.js";
14
- import {
15
- bindSession, getAllContexts, getContextBySessionId, updateMode,
16
- } from "../lib-ts/context/context-store.js";
17
- import type { ContextState as _ContextState } from "../lib-ts/types.js";
17
+ import type { ContextState } from "../lib-ts/types.js";
18
18
 
19
19
  /**
20
20
  * Handle post-compaction restore: re-inject context that was lost during compaction.
@@ -53,7 +53,7 @@ function handleCompactRestore(sessionId: string, projectRoot: string): void {
53
53
  * Handle post-clear restore: find staged has_plan or has_handoff context,
54
54
  * bind session, transition to active, inject context.
55
55
  */
56
- function handleClearRestore(sessionId: string, projectRoot: string): void {
56
+ async function handleClearRestore(sessionId: string, projectRoot: string): Promise<void> {
57
57
  const allContexts = getAllContexts("active", projectRoot);
58
58
 
59
59
  // Priority 1: has_plan contexts
@@ -108,7 +108,7 @@ function handleClearRestore(sessionId: string, projectRoot: string): void {
108
108
  logDebug("session_start", "No has_plan or has_handoff contexts found");
109
109
  }
110
110
 
111
- function main(): void {
111
+ async function main(): Promise<void> {
112
112
  const payload = loadHookInput();
113
113
  if (!payload) return;
114
114
 
@@ -124,21 +124,16 @@ function main(): void {
124
124
  logDiagnostic("session_start", "entry", `source=${source}, session=${sessionId}`);
125
125
 
126
126
  switch (source) {
127
- case "clear": {
128
- handleClearRestore(sessionId, projectRoot);
129
- break;
130
- }
131
-
132
- case "compact": {
127
+ case "compact":
133
128
  handleCompactRestore(sessionId, projectRoot);
134
129
  break;
135
- }
136
-
137
- default: {
130
+ case "clear":
131
+ await handleClearRestore(sessionId, projectRoot);
132
+ break;
133
+ default:
138
134
  logDebug("session_start", `Unhandled source: ${source}`);
139
135
  break;
140
- }
141
136
  }
142
137
  }
143
138
 
144
- runHook(main, "session_start");
139
+ runHookAsync(main, "session_start");
@@ -4,16 +4,16 @@
4
4
  * to a tracked context. The most complex shared hook.
5
5
  *
6
6
  * Uses emitContext() for output — context text is passed via hookSpecificOutput JSON.
7
- * Catches BlockRequest and exits with code 2 to block the prompt.
7
+ * Catches BlockRequest and uses emitBlock() to block the prompt.
8
8
  */
9
- import { getProjectRoot } from "../lib-ts/base/constants.js";
10
9
  import {
11
- emitContext, hookLog, loadHookInput, logBlocking, logDebug, logDiagnostic as _logDiagnostic, logInfo, logWarn as _logWarn, runHookAsync,
10
+ loadHookInput, runHookAsync, logDebug, logInfo, logWarn, logBlocking, logDiagnostic, hookLog, emitContext, emitBlock,
12
11
  } from "../lib-ts/base/hook-utils.js";
13
- import { BlockRequest, determineContext } from "../lib-ts/context/context-selector.js";
12
+ import { getProjectRoot } from "../lib-ts/base/constants.js";
14
13
  import {
15
- bindSession, getContextBySessionId, maybeActivate, saveState,
14
+ getContextBySessionId, bindSession, maybeActivate, saveState,
16
15
  } from "../lib-ts/context/context-store.js";
16
+ import { determineContext, BlockRequest } from "../lib-ts/context/context-selector.js";
17
17
 
18
18
  async function asyncMain(): Promise<void> {
19
19
  const payload = loadHookInput();
@@ -38,10 +38,9 @@ async function asyncMain(): Promise<void> {
38
38
  // Returning user — context already bound (stderr: false to avoid "hook error" display)
39
39
  try {
40
40
  maybeActivate(existingCtx.id, permissionMode, projectRoot, "user_prompt_submit");
41
- } catch (error) {
42
- hookLog("warn", "user_prompt_submit", `maybeActivate failed (non-critical): ${error}`, { stderr: false });
41
+ } catch (e) {
42
+ hookLog("warn", "user_prompt_submit", `maybeActivate failed (non-critical): ${e}`, { stderr: false });
43
43
  }
44
-
45
44
  hookLog("debug", "user_prompt_submit", `Session bound to ${existingCtx.id}`, { stderr: false });
46
45
  } else if (prompt) {
47
46
  // First prompt — need to determine context
@@ -65,13 +64,12 @@ async function asyncMain(): Promise<void> {
65
64
  if (outputText) {
66
65
  outputs.push(outputText);
67
66
  }
68
- } catch (error) {
69
- if (error instanceof BlockRequest) {
70
- logBlocking("user_prompt_submit", (error as Error).message);
71
- process.exit(2); // Block the prompt
67
+ } catch (e) {
68
+ if (e instanceof BlockRequest) {
69
+ emitBlock((e as Error).message);
70
+ return;
72
71
  }
73
-
74
- throw error; // Re-throw unexpected errors
72
+ throw e; // Re-throw unexpected errors
75
73
  }
76
74
  }
77
75
 
@@ -83,18 +83,38 @@ logBlocking("my_hook", "Critical: state corrupt"); // shows in UI
83
83
 
84
84
  ---
85
85
 
86
- ## Hook Output — Three Communication Channels
86
+ ## Hook Output — Communication Channels
87
87
 
88
- Hooks have three channels back to the session. Pick the right one:
88
+ Hooks have multiple channels back to the session. Pick the right one:
89
89
 
90
90
  | Want to... | Function | Who sees it |
91
91
  |------------|----------|-------------|
92
- | Block tool + return message | `emitContextAndBlock(context, reason)` | Claude + user (denial reason prominent) |
92
+ | **Block (any event)** | `emitBlock(reason, context?)` | Claude + user auto-dispatches to correct mechanism |
93
+ | Block tool (PreToolUse only) | `emitContextAndBlock(context, reason)` | Claude + user (denial reason prominent) |
93
94
  | Return message, don't block | `emitContext(context)` | Claude + user (in transcript) |
94
95
  | Log only (diagnostics) | `logInfo()` / `logWarn()` / etc. | Nobody in session — file only |
95
96
 
96
97
  **There is no way to show something to the user but hide it from Claude, or vice versa.** Both `emitContext()` and `emitContextAndBlock()` produce output visible to both.
97
98
 
99
+ ### Universal Blocking: `emitBlock()` (RECOMMENDED)
100
+
101
+ ```typescript
102
+ emitBlock("Reason for blocking", "Optional detailed context");
103
+ ```
104
+
105
+ Auto-detects the correct blocking mechanism from the current hook event:
106
+
107
+ | Event | Mechanism | Dispatches to |
108
+ |-------|-----------|---------------|
109
+ | **PreToolUse** | `permissionDecision: "deny"` in hookSpecificOutput | `emitContextAndBlock()` |
110
+ | **UserPromptSubmit** | Top-level `{ decision: "block", reason }` | `emitBlockPrompt()` |
111
+ | **PostToolUse / PostToolUseFailure** | Exit code 2 + stderr | `emitBlockViaExit()` |
112
+ | **Stop / SubagentStop** | Top-level `{ decision: "block", reason }` | `emitBlockTopLevel()` |
113
+ | **PermissionRequest** | `{ decision: { behavior: "deny" } }` | `emitPermissionDecision()` |
114
+ | **SessionStart / Notification / SubagentStart / SessionEnd** | No blocking mechanism — warns and no-ops | — |
115
+
116
+ **Use `emitBlock()` for all new hooks.** The per-pattern functions below exist for advanced use cases only.
117
+
98
118
  ### Channel 1: Block + Context (PreToolUse only)
99
119
 
100
120
  ```typescript
@@ -103,12 +123,41 @@ emitContextAndBlock(
103
123
  "Short reason for the block" // permissionDecisionReason
104
124
  );
105
125
  // No SystemExit needed — permissionDecision:"deny" with exit 0 is sufficient.
106
- // runHookAsync drains stdout before exit to ensure pipe consumers receive the JSON.
126
+ // Warns if called from non-PreToolUse events (output will be silently rejected by Claude Code).
107
127
  ```
108
128
 
109
129
  The tool call is **prevented from executing**. Only works for PreToolUse hooks.
110
130
 
111
- ### Channel 2: Non-blocking Context (any hook event)
131
+ ### Channel 2: Block Prompt Submission (UserPromptSubmit only)
132
+
133
+ ```typescript
134
+ emitBlockPrompt("Reason the prompt was blocked", "Optional context for Claude");
135
+ ```
136
+
137
+ Emits top-level `{ decision: "block", reason }`. The prompt is rejected before Claude processes it.
138
+
139
+ ### Channel 3: Block via Exit Code (PostToolUse / PostToolUseFailure)
140
+
141
+ ```typescript
142
+ emitBlockViaExit("Reason for blocking", "Optional context prepended to stderr");
143
+ // Throws SystemExit:2 — handled by runHook/runHookAsync
144
+ // NOTE: Exit 2 causes Claude Code to ignore all JSON stdout — only stderr matters
145
+ ```
146
+
147
+ ### Channel 4: Block Stop/SubagentStop
148
+
149
+ ```typescript
150
+ emitBlockTopLevel("Reason to prevent stopping");
151
+ ```
152
+
153
+ ### Channel 5: PermissionRequest Decision
154
+
155
+ ```typescript
156
+ emitPermissionDecision("deny", { message: "Why denied" });
157
+ emitPermissionDecision("allow", { updatedInput: { /* modified input */ } });
158
+ ```
159
+
160
+ ### Channel 6: Non-blocking Context (any hook event)
112
161
 
113
162
  ```typescript
114
163
  emitContext("Information added to Claude's context");
@@ -116,7 +165,7 @@ emitContext("Information added to Claude's context");
116
165
 
117
166
  The tool call / session continues normally. Works for PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Notification, SubagentStart.
118
167
 
119
- ### Channel 3: Log-only (diagnostics)
168
+ ### Channel 7: Log-only (diagnostics)
120
169
 
121
170
  ```typescript
122
171
  logInfo("my_hook", "Processing started"); // File only
@@ -252,7 +301,7 @@ Use this table to find the right file. Read the source for full API details.
252
301
 
253
302
  | File | Purpose | Key Exports |
254
303
  |------|---------|-------------|
255
- | `hook-utils.ts` | Hook lifecycle, stdin parsing, output emit, re-exports | `runHook`, `runHookAsync`, `loadHookInput`, `emitContext`, `emitContextAndBlock`, `logDebug`...`logBlocking` |
304
+ | `hook-utils.ts` | Hook lifecycle, stdin parsing, output emit, re-exports | `runHook`, `runHookAsync`, `loadHookInput`, `emitContext`, `emitContextAndBlock`, `emitBlock`, `emitBlockPrompt`, `emitBlockViaExit`, `emitBlockTopLevel`, `emitPermissionDecision`, `logDebug`...`logBlocking` |
256
305
  | `logger.ts` | JSONL logging engine | `hookLog`, `logDebug`, `logInfo`, `logWarn`, `logError`, `logBlocking`, `logHookError`, `logDiagnostic` |
257
306
  | `constants.ts` | Path resolution, limits | `getProjectRoot()`, `getContextDir()`, `MAX_FILE_SIZE` |
258
307
  | `atomic-write.ts` | Crash-safe file writes | `atomicWriteFileSync()` |
@@ -23,7 +23,7 @@ export function getGitState(projectRoot: string): Record<string, any> {
23
23
  try {
24
24
  const status = execFileSync("git", ["status", "--short"], opts);
25
25
  if (status) {
26
- const files = status.trim().split("\n")
26
+ const files = status.trim().split(/\r?\n/)
27
27
  .filter(Boolean)
28
28
  .slice(0, 10)
29
29
  .map(line => line.trimStart().split(/\s+/).slice(1).join(" "));