aiwcli 0.11.1 → 0.12.1

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 (117) 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 +3 -13
  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 +3 -3
  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 +75 -4
  35. package/dist/templates/_shared/hooks-ts/session_start.ts +11 -13
  36. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +6 -8
  37. package/dist/templates/_shared/lib-ts/CLAUDE.md +56 -7
  38. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +176 -29
  39. package/dist/templates/_shared/lib-ts/base/logger.ts +1 -1
  40. package/dist/templates/_shared/lib-ts/base/state-io.ts +11 -2
  41. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +181 -165
  42. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +14 -13
  43. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +3 -2
  44. package/dist/templates/_shared/lib-ts/package.json +1 -2
  45. package/dist/templates/_shared/lib-ts/templates/plan-context.ts +27 -34
  46. package/dist/templates/_shared/lib-ts/types.ts +17 -2
  47. package/dist/templates/_shared/scripts/resume_handoff.ts +4 -4
  48. package/dist/templates/_shared/scripts/save_handoff.ts +7 -7
  49. package/dist/templates/_shared/scripts/status_line.ts +104 -71
  50. package/dist/templates/_shared/workflows/handoff.md +1 -1
  51. package/dist/templates/cc-native/.claude/settings.json +182 -175
  52. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +23 -1
  53. package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
  54. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +6 -1
  55. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +142 -111
  56. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +54 -0
  57. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +52 -0
  58. package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -0
  59. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +19 -19
  60. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +6 -5
  61. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +114 -83
  62. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +107 -10
  63. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +1 -1
  64. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +6 -2
  65. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +0 -4
  66. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +40 -219
  67. package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -0
  68. package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +102 -0
  69. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +26 -227
  70. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -0
  71. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +4 -2
  72. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +65 -0
  73. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +185 -0
  74. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -0
  75. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +195 -0
  76. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -0
  77. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +2 -2
  78. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +17 -16
  79. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +13 -108
  80. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +3 -3
  81. package/dist/templates/cc-native/_cc-native/plan-review.config.json +2 -14
  82. package/oclif.manifest.json +1 -1
  83. package/package.json +1 -2
  84. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +0 -119
  85. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +0 -130
  86. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +0 -107
  87. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-EVOLUTION.md → plan-review/ARCH-EVOLUTION.md} +0 -0
  88. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-PATTERNS.md → plan-review/ARCH-PATTERNS.md} +0 -0
  89. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-STRUCTURE.md → plan-review/ARCH-STRUCTURE.md} +0 -0
  90. /package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-TRACER.md → plan-review/ASSUMPTION-TRACER.md} +0 -0
  91. /package/dist/templates/cc-native/_cc-native/agents/{CLARITY-AUDITOR.md → plan-review/CLARITY-AUDITOR.md} +0 -0
  92. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-FEASIBILITY.md → plan-review/COMPLETENESS-FEASIBILITY.md} +0 -0
  93. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-GAPS.md → plan-review/COMPLETENESS-GAPS.md} +0 -0
  94. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-ORDERING.md → plan-review/COMPLETENESS-ORDERING.md} +0 -0
  95. /package/dist/templates/cc-native/_cc-native/agents/{CONSTRAINT-VALIDATOR.md → plan-review/CONSTRAINT-VALIDATOR.md} +0 -0
  96. /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-ADR-VALIDATOR.md → plan-review/DESIGN-ADR-VALIDATOR.md} +0 -0
  97. /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-SCALE-MATCHER.md → plan-review/DESIGN-SCALE-MATCHER.md} +0 -0
  98. /package/dist/templates/cc-native/_cc-native/agents/{DEVILS-ADVOCATE.md → plan-review/DEVILS-ADVOCATE.md} +0 -0
  99. /package/dist/templates/cc-native/_cc-native/agents/{DOCUMENTATION-PHILOSOPHY.md → plan-review/DOCUMENTATION-PHILOSOPHY.md} +0 -0
  100. /package/dist/templates/cc-native/_cc-native/agents/{HANDOFF-READINESS.md → plan-review/HANDOFF-READINESS.md} +0 -0
  101. /package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY.md → plan-review/HIDDEN-COMPLEXITY.md} +0 -0
  102. /package/dist/templates/cc-native/_cc-native/agents/{INCREMENTAL-DELIVERY.md → plan-review/INCREMENTAL-DELIVERY.md} +0 -0
  103. /package/dist/templates/cc-native/_cc-native/agents/{RISK-DEPENDENCY.md → plan-review/RISK-DEPENDENCY.md} +0 -0
  104. /package/dist/templates/cc-native/_cc-native/agents/{RISK-FMEA.md → plan-review/RISK-FMEA.md} +0 -0
  105. /package/dist/templates/cc-native/_cc-native/agents/{RISK-PREMORTEM.md → plan-review/RISK-PREMORTEM.md} +0 -0
  106. /package/dist/templates/cc-native/_cc-native/agents/{RISK-REVERSIBILITY.md → plan-review/RISK-REVERSIBILITY.md} +0 -0
  107. /package/dist/templates/cc-native/_cc-native/agents/{SCOPE-BOUNDARY.md → plan-review/SCOPE-BOUNDARY.md} +0 -0
  108. /package/dist/templates/cc-native/_cc-native/agents/{SIMPLICITY-GUARDIAN.md → plan-review/SIMPLICITY-GUARDIAN.md} +0 -0
  109. /package/dist/templates/cc-native/_cc-native/agents/{SKEPTIC.md → plan-review/SKEPTIC.md} +0 -0
  110. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-BEHAVIOR-AUDITOR.md → plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md} +0 -0
  111. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-CHARACTERIZATION.md → plan-review/TESTDRIVEN-CHARACTERIZATION.md} +0 -0
  112. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-FIRST-VALIDATOR.md → plan-review/TESTDRIVEN-FIRST-VALIDATOR.md} +0 -0
  113. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-PYRAMID-ANALYZER.md → plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md} +0 -0
  114. /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-COSTS.md → plan-review/TRADEOFF-COSTS.md} +0 -0
  115. /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-STAKEHOLDERS.md → plan-review/TRADEOFF-STAKEHOLDERS.md} +0 -0
  116. /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-COVERAGE.md → plan-review/VERIFY-COVERAGE.md} +0 -0
  117. /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 = 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.includes(version)) {
112
+ if (INCOMPATIBLE_VERSIONS.has(version)) {
113
113
  return {
114
114
  compatible: false,
115
115
  version,
@@ -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
  ```
@@ -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, logDebug, logDiagnostic, logError, logInfo, logWarn as _logWarn, runHook,
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
- extractPlanAnchors, findLatestPlan, generatePlanId, normalizePlanContent,
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, "utf8");
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")
@@ -5,16 +5,16 @@
5
5
  */
6
6
  import { getProjectRoot } from "../lib-ts/base/constants.js";
7
7
  import {
8
- emitContext, loadHookInput, logDebug,
9
- logDiagnostic, logError as _logError, logInfo, runHook,
8
+ loadHookInput, emitContext, runHook, runHookAsync,
9
+ logDebug, logInfo, logError, logDiagnostic,
10
10
  } from "../lib-ts/base/hook-utils.js";
11
11
  import {
12
12
  buildRestoreSections, formatHandoffContinuation, getModeDisplay,
13
13
  } from "../lib-ts/context/context-formatter.js";
14
14
  import {
15
- bindSession, getAllContexts, getContextBySessionId, updateMode,
15
+ getContextBySessionId, getAllContexts, bindSession, updateMode,
16
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
 
@@ -125,20 +125,18 @@ function main(): void {
125
125
 
126
126
  switch (source) {
127
127
  case "clear": {
128
- handleClearRestore(sessionId, projectRoot);
129
- break;
128
+ await handleClearRestore(sessionId, projectRoot);
129
+ break;
130
130
  }
131
-
132
131
  case "compact": {
133
132
  handleCompactRestore(sessionId, projectRoot);
134
- break;
133
+ break;
135
134
  }
136
-
137
135
  default: {
138
136
  logDebug("session_start", `Unhandled source: ${source}`);
139
- break;
137
+ break;
140
138
  }
141
139
  }
142
140
  }
143
141
 
144
- runHook(main, "session_start");
142
+ runHookAsync(main, "session_start");
@@ -4,15 +4,15 @@
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
9
  import { getProjectRoot } from "../lib-ts/base/constants.js";
10
10
  import {
11
- emitContext, hookLog, loadHookInput, logBlocking, logDebug, logDiagnostic as _logDiagnostic, logInfo, logWarn as _logWarn, runHookAsync,
11
+ loadHookInput, runHookAsync, logDebug, logInfo, logWarn, logBlocking, logDiagnostic, hookLog, emitContext, emitBlock,
12
12
  } from "../lib-ts/base/hook-utils.js";
13
- import { BlockRequest, determineContext } from "../lib-ts/context/context-selector.js";
13
+ import { determineContext, BlockRequest } from "../lib-ts/context/context-selector.js";
14
14
  import {
15
- bindSession, getContextBySessionId, maybeActivate, saveState,
15
+ getContextBySessionId, bindSession, maybeActivate, saveState,
16
16
  } from "../lib-ts/context/context-store.js";
17
17
 
18
18
  async function asyncMain(): Promise<void> {
@@ -41,7 +41,6 @@ async function asyncMain(): Promise<void> {
41
41
  } catch (error) {
42
42
  hookLog("warn", "user_prompt_submit", `maybeActivate failed (non-critical): ${error}`, { 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
@@ -67,10 +66,9 @@ async function asyncMain(): Promise<void> {
67
66
  }
68
67
  } catch (error) {
69
68
  if (error instanceof BlockRequest) {
70
- logBlocking("user_prompt_submit", (error as Error).message);
71
- process.exit(2); // Block the prompt
69
+ emitBlock((error as Error).message);
70
+ return;
72
71
  }
73
-
74
72
  throw error; // Re-throw unexpected errors
75
73
  }
76
74
  }
@@ -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()` |