aiwcli 0.10.3 → 0.11.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 (191) hide show
  1. package/bin/run.js +1 -1
  2. package/dist/commands/clear.js +28 -131
  3. package/dist/commands/init/index.js +3 -3
  4. package/dist/lib/gitignore-manager.d.ts +32 -0
  5. package/dist/lib/gitignore-manager.js +141 -2
  6. package/dist/templates/CLAUDE.md +8 -8
  7. package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
  8. package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
  9. package/dist/templates/_shared/.claude/settings.json +7 -7
  10. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
  11. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
  12. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
  13. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
  14. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
  15. package/dist/templates/_shared/hooks-ts/session_end.ts +107 -0
  16. package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
  17. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
  18. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
  19. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
  20. package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
  21. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
  22. package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
  23. package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
  24. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
  25. package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
  26. package/dist/templates/_shared/lib-ts/base/logger.ts +15 -2
  27. package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
  28. package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
  29. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +142 -0
  30. package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
  31. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
  32. package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
  33. package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
  34. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +43 -23
  35. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
  36. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
  37. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +158 -0
  38. package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
  39. package/dist/templates/_shared/lib-ts/types.ts +68 -55
  40. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  41. package/dist/templates/_shared/scripts/resume_handoff.ts +345 -0
  42. package/dist/templates/_shared/scripts/save_handoff.ts +3 -3
  43. package/dist/templates/_shared/scripts/status_line.ts +687 -0
  44. package/dist/templates/cc-native/.claude/settings.json +175 -185
  45. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  46. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
  47. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  48. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  49. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +1027 -0
  50. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  51. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -0
  52. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +792 -0
  53. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  54. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -0
  55. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  56. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  57. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
  58. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  59. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +120 -0
  60. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -0
  61. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  62. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +250 -0
  63. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +275 -0
  64. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  65. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +107 -0
  66. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  67. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  68. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +240 -0
  69. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  70. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +385 -0
  71. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  72. package/dist/templates/cc-native/_cc-native/plan-review.config.json +14 -1
  73. package/oclif.manifest.json +1 -1
  74. package/package.json +2 -2
  75. package/dist/templates/_shared/hooks/__init__.py +0 -16
  76. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  77. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  78. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  79. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  80. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  81. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  82. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  83. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  84. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  85. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  86. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  87. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  88. package/dist/templates/_shared/hooks/archive_plan.py +0 -177
  89. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  90. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  91. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  92. package/dist/templates/_shared/hooks/session_end.py +0 -173
  93. package/dist/templates/_shared/hooks/session_start.py +0 -206
  94. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  95. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  96. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  97. package/dist/templates/_shared/lib/__init__.py +0 -1
  98. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  99. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  100. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  102. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  103. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  104. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  107. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  108. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  109. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  110. package/dist/templates/_shared/lib/base/constants.py +0 -358
  111. package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
  112. package/dist/templates/_shared/lib/base/inference.py +0 -307
  113. package/dist/templates/_shared/lib/base/logger.py +0 -305
  114. package/dist/templates/_shared/lib/base/stop_words.py +0 -221
  115. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  116. package/dist/templates/_shared/lib/base/utils.py +0 -263
  117. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  118. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  130. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  131. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  132. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  133. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  134. package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
  135. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  136. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  137. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  138. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  139. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  140. package/dist/templates/_shared/lib/templates/README.md +0 -206
  141. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  142. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  145. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  146. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  147. package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
  148. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  149. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  150. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  151. package/dist/templates/_shared/scripts/status_line.py +0 -716
  152. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  153. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  154. package/dist/templates/cc-native/MIGRATION.md +0 -86
  155. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  156. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  157. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  158. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  159. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  160. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  161. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  162. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
  163. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  164. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  165. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  166. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  167. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  171. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  172. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  173. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  174. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  175. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  176. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  177. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  178. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  179. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  180. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  181. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  185. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  186. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  187. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  188. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
  189. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  190. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  191. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
package/bin/run.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // Load environment variable compatibility layer first
4
- const {loadEnvWithCompatibility} = await import('../dist/lib/env-compat.js')
4
+ const {loadEnvWithCompatibility} = await import('../dist/lib/env-compat.js')
5
5
  loadEnvWithCompatibility()
6
6
 
7
7
  import {execute} from '@oclif/core'
@@ -3,7 +3,7 @@ import { join } from 'node:path';
3
3
  import { confirm } from '@inquirer/prompts';
4
4
  import { Flags } from '@oclif/core';
5
5
  import BaseCommand from '../lib/base-command.js';
6
- import { pruneGitignoreStaleEntries } from '../lib/gitignore-manager.js';
6
+ import { computeGitignoreRemovals, pruneGitignoreStaleEntries, removeGitignoreEntries } from '../lib/gitignore-manager.js';
7
7
  import { pathExists } from '../lib/paths.js';
8
8
  import { reconstructIdeSettings } from '../lib/template-settings-reconstructor.js';
9
9
  import { EXIT_CODES } from '../types/exit-codes.js';
@@ -44,6 +44,7 @@ async function getInstalledMethodNames(targetDir) {
44
44
  for (const ide of Object.values(IDE_FOLDERS)) {
45
45
  const settingsPath = join(targetDir, ide.root, ide.settingsFile);
46
46
  try {
47
+ // eslint-disable-next-line no-await-in-loop
47
48
  const content = await fs.readFile(settingsPath, 'utf8');
48
49
  const settings = JSON.parse(content);
49
50
  if (settings.methods && typeof settings.methods === 'object') {
@@ -71,10 +72,6 @@ async function getInstalledMethodNames(targetDir) {
71
72
  }
72
73
  return methods;
73
74
  }
74
- /**
75
- * AIW gitignore section header marker
76
- */
77
- const AIW_GITIGNORE_HEADER = '# AIW Installation';
78
75
  /**
79
76
  * Check if a directory is empty.
80
77
  *
@@ -190,123 +187,6 @@ async function shouldDeleteIdeFolder(targetDir, ideFolder) {
190
187
  async function removeDirectory(dir) {
191
188
  await fs.rm(dir, { force: true, recursive: true });
192
189
  }
193
- /**
194
- * Update .gitignore to remove patterns for cleared folders.
195
- * Removes patterns from the AIW Installation section.
196
- *
197
- * @param targetDir - Directory containing .gitignore
198
- * @param foldersToRemove - Folder patterns to remove (without trailing slash)
199
- */
200
- async function updateGitignoreAfterClear(targetDir, foldersToRemove) {
201
- const gitignorePath = join(targetDir, '.gitignore');
202
- try {
203
- const content = await fs.readFile(gitignorePath, 'utf8');
204
- // Check if AIW Installation section exists
205
- if (!content.includes(AIW_GITIGNORE_HEADER)) {
206
- return;
207
- }
208
- // Split content into lines
209
- const lines = content.split('\n');
210
- const newLines = [];
211
- let inAiwSection = false;
212
- const aiwSectionLines = [];
213
- // Parse lines and identify AIW section
214
- for (const line of lines) {
215
- if (line === AIW_GITIGNORE_HEADER) {
216
- inAiwSection = true;
217
- aiwSectionLines.push(line);
218
- continue;
219
- }
220
- if (inAiwSection) {
221
- // AIW section ends at empty line or another comment header
222
- if (line === '' || (line.startsWith('#') && line !== AIW_GITIGNORE_HEADER)) {
223
- inAiwSection = false;
224
- // Process AIW section lines now
225
- const filteredAiwLines = filterAiwSection(aiwSectionLines, foldersToRemove);
226
- newLines.push(...filteredAiwLines, line);
227
- }
228
- else {
229
- aiwSectionLines.push(line);
230
- }
231
- }
232
- else {
233
- newLines.push(line);
234
- }
235
- }
236
- // Handle case where AIW section is at end of file
237
- if (inAiwSection) {
238
- const filteredAiwLines = filterAiwSection(aiwSectionLines, foldersToRemove);
239
- newLines.push(...filteredAiwLines);
240
- }
241
- // Clean up: remove AIW section entirely if only header remains
242
- const finalContent = cleanupGitignoreContent(newLines.join('\n'));
243
- await fs.writeFile(gitignorePath, finalContent, 'utf8');
244
- }
245
- catch {
246
- // .gitignore doesn't exist or can't be read
247
- }
248
- }
249
- /**
250
- * Filter AIW section lines to remove specified folders.
251
- *
252
- * @param aiwLines - Lines from AIW section (including header)
253
- * @param foldersToRemove - Folder names to remove
254
- * @returns Filtered lines
255
- */
256
- function filterAiwSection(aiwLines, foldersToRemove) {
257
- const patternsToRemove = new Set(foldersToRemove.map((f) => `${f}/`));
258
- return aiwLines.filter((line) => {
259
- // Always keep the header
260
- if (line === AIW_GITIGNORE_HEADER) {
261
- return true;
262
- }
263
- // Remove matching patterns
264
- return !patternsToRemove.has(line);
265
- });
266
- }
267
- /**
268
- * Clean up gitignore content after filtering.
269
- * Removes AIW section if empty, cleans up extra newlines.
270
- *
271
- * @param content - Gitignore content
272
- * @returns Cleaned content
273
- */
274
- function cleanupGitignoreContent(content) {
275
- const lines = content.split('\n');
276
- const newLines = [];
277
- let i = 0;
278
- while (i < lines.length) {
279
- const line = lines[i];
280
- // Check if this is an AIW header with nothing following
281
- if (line === AIW_GITIGNORE_HEADER) {
282
- // Look ahead to see if there are any patterns
283
- let hasPatterns = false;
284
- const nextLine = lines[i + 1];
285
- if (nextLine !== undefined && nextLine !== '' && !nextLine.startsWith('#')) {
286
- hasPatterns = true;
287
- }
288
- if (!hasPatterns) {
289
- // Skip the AIW header since it has no patterns
290
- i++;
291
- // Also skip any trailing empty lines that were before AIW section
292
- while (newLines.length > 0 && newLines.at(-1) === '') {
293
- newLines.pop();
294
- }
295
- continue;
296
- }
297
- }
298
- newLines.push(line);
299
- i++;
300
- }
301
- // Ensure file ends with newline but not multiple
302
- let result = newLines.join('\n');
303
- result = result.replace(/\n+$/, '\n');
304
- // Handle empty file case
305
- if (result.trim() === '') {
306
- return '';
307
- }
308
- return result;
309
- }
310
190
  /**
311
191
  * Clear workflow folders, output folders, IDE method folders, and update configurations.
312
192
  */
@@ -498,6 +378,18 @@ export default class ClearCommand extends BaseCommand {
498
378
  this.logInfo(`${IDE_FOLDERS.windsurf.root}/ folder will be removed (will be empty)`);
499
379
  this.log('');
500
380
  }
381
+ // Compute gitignore changes for dry-run display
382
+ const gitignoreSimulation = await computeGitignoreRemovals(targetDir);
383
+ if (gitignoreSimulation.toRemove.length > 0 || gitignoreSimulation.toKeep.length > 0) {
384
+ this.logInfo('Gitignore changes:');
385
+ for (const { entry, reason } of gitignoreSimulation.toKeep) {
386
+ this.log(` keep ${entry}/ (${reason})`);
387
+ }
388
+ for (const entry of gitignoreSimulation.toRemove) {
389
+ this.log(` remove ${entry}/`);
390
+ }
391
+ this.log('');
392
+ }
501
393
  // Dry run - just show what would happen
502
394
  if (flags['dry-run']) {
503
395
  this.logInfo('Dry run complete. No files or folders were deleted.');
@@ -561,12 +453,16 @@ export default class ClearCommand extends BaseCommand {
561
453
  catch {
562
454
  // .aiwcli doesn't exist or can't be accessed
563
455
  }
564
- // Update .gitignore to remove .aiwcli if the container was deleted
565
- if (removedAiwcliContainer) {
566
- await updateGitignoreAfterClear(targetDir, [AIWCLI_CONTAINER]);
567
- this.logDebug('Updated .gitignore');
456
+ // Smart gitignore removal: compute what should be removed based on disk state
457
+ const { toRemove, toKeep } = await computeGitignoreRemovals(targetDir);
458
+ for (const { entry, reason } of toKeep) {
459
+ this.logDebug(`Keeping ${entry}/ in .gitignore (${reason})`);
460
+ }
461
+ if (toRemove.length > 0) {
462
+ await removeGitignoreEntries(targetDir, toRemove);
463
+ this.logDebug(`Removed from .gitignore: ${toRemove.join(', ')}`);
568
464
  }
569
- // Prune stale gitignore entries (paths that no longer exist on disk)
465
+ // Prune stale gitignore entries as safety net
570
466
  const pruned = await pruneGitignoreStaleEntries(targetDir);
571
467
  if (pruned) {
572
468
  this.logDebug('Pruned stale .gitignore entries');
@@ -655,7 +551,7 @@ export default class ClearCommand extends BaseCommand {
655
551
  parts.push(`${IDE_FOLDERS.windsurf.root}/ folder`);
656
552
  }
657
553
  this.logSuccess(`Cleared: ${parts.join(', ')}.`);
658
- if (removedAiwcliContainer || pruned) {
554
+ if (toRemove.length > 0 || pruned) {
659
555
  this.logSuccess('Updated .gitignore.');
660
556
  }
661
557
  if (updatedClaudeSettings) {
@@ -684,6 +580,7 @@ export default class ClearCommand extends BaseCommand {
684
580
  * @param targetDir - Project root directory
685
581
  * @param flags - Command flags (dry-run, force)
686
582
  */
583
+ // eslint-disable-next-line complexity
687
584
  async cleanRuntimeOutput(targetDir, flags) {
688
585
  const outputDir = join(targetDir, '_output');
689
586
  if (!(await pathExists(outputDir))) {
@@ -711,7 +608,7 @@ export default class ClearCommand extends BaseCommand {
711
608
  // Log rotation: hook-log.jsonl > 1MB
712
609
  if (entry.isFile() && entry.name === 'hook-log.jsonl') {
713
610
  try {
714
- const stat = await fs.stat(entryPath);
611
+ const stat = await fs.stat(entryPath); // eslint-disable-line no-await-in-loop
715
612
  if (stat.size > 1_048_576) {
716
613
  logAction = { path: entryPath, sizeBytes: stat.size };
717
614
  }
@@ -725,7 +622,7 @@ export default class ClearCommand extends BaseCommand {
725
622
  if (entry.isDirectory() && entry.name === 'contexts') {
726
623
  const archivePath = join(entryPath, '_archive');
727
624
  try {
728
- const archiveEntries = await fs.readdir(archivePath);
625
+ const archiveEntries = await fs.readdir(archivePath); // eslint-disable-line no-await-in-loop
729
626
  if (archiveEntries.length > 0) {
730
627
  archiveDir = archivePath;
731
628
  archiveCount = archiveEntries.length;
@@ -785,7 +682,7 @@ export default class ClearCommand extends BaseCommand {
785
682
  let deletedCount = 0;
786
683
  for (const item of toDelete) {
787
684
  try {
788
- await fs.unlink(item.path);
685
+ await fs.unlink(item.path); // eslint-disable-line no-await-in-loop
789
686
  deletedCount++;
790
687
  }
791
688
  catch (error) {
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
  import { checkbox, confirm, input, select } from '@inquirer/prompts';
5
5
  import { Flags } from '@oclif/core';
6
6
  import BaseCommand from '../../lib/base-command.js';
7
- import { updateGitignore } from '../../lib/gitignore-manager.js';
7
+ import { AIW_GITIGNORE_ENTRIES, updateGitignore } from '../../lib/gitignore-manager.js';
8
8
  import { IdePathResolver } from '../../lib/ide-path-resolver.js';
9
9
  import { pathExists } from '../../lib/paths.js';
10
10
  import { getTargetSettingsFile, readClaudeSettings, writeClaudeSettings } from '../../lib/settings-hierarchy.js';
@@ -111,7 +111,7 @@ export default class Init extends BaseCommand {
111
111
  await reconstructIdeSettings(targetDir, [], ['claude']);
112
112
  // Update .gitignore if git repository exists
113
113
  if (hasGit) {
114
- await updateGitignore(targetDir, ['.aiwcli']);
114
+ await updateGitignore(targetDir, [...AIW_GITIGNORE_ENTRIES]);
115
115
  this.logSuccess('✓ .gitignore updated');
116
116
  }
117
117
  this.log('');
@@ -178,7 +178,7 @@ export default class Init extends BaseCommand {
178
178
  });
179
179
  // Collect all folders that need gitignore entries
180
180
  // The .aiwcli/ container holds all template infrastructure and runtime data
181
- const foldersForGitignore = ['.aiwcli'];
181
+ const foldersForGitignore = [...AIW_GITIGNORE_ENTRIES];
182
182
  // Report installation results
183
183
  if (result.installedFolders.length > 0) {
184
184
  this.logSuccess(`✓ Installed: ${result.installedFolders.join(', ')}`);
@@ -1,3 +1,7 @@
1
+ /** Standard gitignore entries managed by AIW */
2
+ export declare const AIW_GITIGNORE_ENTRIES: string[];
3
+ /** Entries that should NEVER be removed from gitignore, even on clear */
4
+ export declare const AIW_PERMANENT_ENTRIES: string[];
1
5
  /**
2
6
  * Prune stale entries from the AIW Installation section in .gitignore.
3
7
  * Checks each entry against disk existence and removes entries whose paths don't exist.
@@ -17,3 +21,31 @@ export declare function pruneGitignoreStaleEntries(targetDir: string): Promise<b
17
21
  * @param folders - List of folder names to add as gitignore patterns (e.g., ['_bmad', '.claude'])
18
22
  */
19
23
  export declare function updateGitignore(targetDir: string, folders: string[]): Promise<void>;
24
+ /**
25
+ * Compute which AIW gitignore entries should be removed during clear.
26
+ * Returns a simulation result — the caller decides whether to apply.
27
+ *
28
+ * Logic per entry:
29
+ * - If in permanentEntries → keep (reason: "permanent")
30
+ * - If directory exists and is non-empty → keep (reason: "directory has content")
31
+ * - Otherwise → mark for removal
32
+ *
33
+ * @param targetDir - Directory containing .gitignore
34
+ * @param permanentEntries - Entries that should never be removed (defaults to AIW_PERMANENT_ENTRIES)
35
+ * @returns Lists of entries to remove and entries to keep with reasons
36
+ */
37
+ export declare function computeGitignoreRemovals(targetDir: string, permanentEntries?: string[]): Promise<{
38
+ toKeep: Array<{
39
+ entry: string;
40
+ reason: string;
41
+ }>;
42
+ toRemove: string[];
43
+ }>;
44
+ /**
45
+ * Remove specific entries from the AIW section in .gitignore.
46
+ * Cleans up the section header if no entries remain.
47
+ *
48
+ * @param targetDir - Directory containing .gitignore
49
+ * @param entriesToRemove - Entry names to remove (without trailing slash)
50
+ */
51
+ export declare function removeGitignoreEntries(targetDir: string, entriesToRemove: string[]): Promise<void>;
@@ -5,6 +5,10 @@ import { pathExists } from './paths.js';
5
5
  * AIW gitignore section header marker
6
6
  */
7
7
  const AIW_GITIGNORE_HEADER = '# AIW Installation';
8
+ /** Standard gitignore entries managed by AIW */
9
+ export const AIW_GITIGNORE_ENTRIES = ['.aiwcli', '_output', '.claude', '.windsurf'];
10
+ /** Entries that should NEVER be removed from gitignore, even on clear */
11
+ export const AIW_PERMANENT_ENTRIES = ['_output'];
8
12
  /**
9
13
  * Prune stale entries from the AIW Installation section in .gitignore.
10
14
  * Checks each entry against disk existence and removes entries whose paths don't exist.
@@ -35,7 +39,7 @@ export async function pruneGitignoreStaleEntries(targetDir) {
35
39
  // AIW section ends at empty line or another comment header
36
40
  if (line === '' || (line.startsWith('#') && line !== AIW_GITIGNORE_HEADER)) {
37
41
  inAiwSection = false;
38
- const { lines: filtered, pruned: sectionPruned } = await pruneSection(aiwSectionLines, targetDir);
42
+ const { lines: filtered, pruned: sectionPruned } = await pruneSection(aiwSectionLines, targetDir); // eslint-disable-line no-await-in-loop
39
43
  if (sectionPruned)
40
44
  pruned = true;
41
45
  newLines.push(...filtered, line);
@@ -88,7 +92,7 @@ async function pruneSection(sectionLines, targetDir) {
88
92
  // Check if the path exists on disk
89
93
  const cleanPath = line.replace(/^\//, '').replace(/\/$/, '');
90
94
  const absPath = join(targetDir, cleanPath);
91
- if (await pathExists(absPath)) {
95
+ if (await pathExists(absPath)) { // eslint-disable-line no-await-in-loop
92
96
  filtered.push(line);
93
97
  }
94
98
  else {
@@ -179,3 +183,138 @@ export async function updateGitignore(targetDir, folders) {
179
183
  await fs.writeFile(gitignorePath, patternsBlock + '\n', 'utf8');
180
184
  }
181
185
  }
186
+ /**
187
+ * Compute which AIW gitignore entries should be removed during clear.
188
+ * Returns a simulation result — the caller decides whether to apply.
189
+ *
190
+ * Logic per entry:
191
+ * - If in permanentEntries → keep (reason: "permanent")
192
+ * - If directory exists and is non-empty → keep (reason: "directory has content")
193
+ * - Otherwise → mark for removal
194
+ *
195
+ * @param targetDir - Directory containing .gitignore
196
+ * @param permanentEntries - Entries that should never be removed (defaults to AIW_PERMANENT_ENTRIES)
197
+ * @returns Lists of entries to remove and entries to keep with reasons
198
+ */
199
+ export async function computeGitignoreRemovals(targetDir, permanentEntries = AIW_PERMANENT_ENTRIES) {
200
+ const gitignorePath = join(targetDir, '.gitignore');
201
+ const toRemove = [];
202
+ const toKeep = [];
203
+ // Read AIW section entries from .gitignore
204
+ let content;
205
+ try {
206
+ content = await fs.readFile(gitignorePath, 'utf8');
207
+ }
208
+ catch {
209
+ return { toRemove, toKeep };
210
+ }
211
+ if (!content.includes(AIW_GITIGNORE_HEADER)) {
212
+ return { toRemove, toKeep };
213
+ }
214
+ // Parse entries from the AIW section
215
+ const lines = content.split('\n');
216
+ let inAiwSection = false;
217
+ const aiwEntries = [];
218
+ for (const line of lines) {
219
+ if (line === AIW_GITIGNORE_HEADER) {
220
+ inAiwSection = true;
221
+ continue;
222
+ }
223
+ if (inAiwSection) {
224
+ if (line === '' || (line.startsWith('#') && line !== AIW_GITIGNORE_HEADER)) {
225
+ inAiwSection = false;
226
+ }
227
+ else {
228
+ // Strip trailing slash to get the directory name
229
+ const entry = line.replace(/\/$/, '');
230
+ if (entry) {
231
+ aiwEntries.push(entry);
232
+ }
233
+ }
234
+ }
235
+ }
236
+ const permanentSet = new Set(permanentEntries);
237
+ // Evaluate each entry
238
+ await Promise.all(aiwEntries.map(async (entry) => {
239
+ if (permanentSet.has(entry)) {
240
+ toKeep.push({ entry, reason: 'permanent' });
241
+ return;
242
+ }
243
+ const dirPath = join(targetDir, entry);
244
+ const exists = await pathExists(dirPath);
245
+ if (exists) {
246
+ // Check if non-empty
247
+ try {
248
+ const entries = await fs.readdir(dirPath);
249
+ if (entries.length > 0) {
250
+ toKeep.push({ entry, reason: 'directory has content' });
251
+ return;
252
+ }
253
+ }
254
+ catch {
255
+ // Can't read — be safe, keep it
256
+ toKeep.push({ entry, reason: 'directory has content' });
257
+ return;
258
+ }
259
+ }
260
+ toRemove.push(entry);
261
+ }));
262
+ return { toRemove, toKeep };
263
+ }
264
+ /**
265
+ * Remove specific entries from the AIW section in .gitignore.
266
+ * Cleans up the section header if no entries remain.
267
+ *
268
+ * @param targetDir - Directory containing .gitignore
269
+ * @param entriesToRemove - Entry names to remove (without trailing slash)
270
+ */
271
+ export async function removeGitignoreEntries(targetDir, entriesToRemove) {
272
+ const gitignorePath = join(targetDir, '.gitignore');
273
+ try {
274
+ const content = await fs.readFile(gitignorePath, 'utf8');
275
+ if (!content.includes(AIW_GITIGNORE_HEADER)) {
276
+ return;
277
+ }
278
+ const patternsToRemove = new Set(entriesToRemove.map((e) => `${e}/`));
279
+ const lines = content.split('\n');
280
+ const newLines = [];
281
+ let inAiwSection = false;
282
+ const aiwSectionLines = [];
283
+ for (const line of lines) {
284
+ if (line === AIW_GITIGNORE_HEADER) {
285
+ inAiwSection = true;
286
+ aiwSectionLines.push(line);
287
+ continue;
288
+ }
289
+ if (inAiwSection) {
290
+ if (line === '' || (line.startsWith('#') && line !== AIW_GITIGNORE_HEADER)) {
291
+ inAiwSection = false;
292
+ // Filter the AIW section
293
+ const filtered = aiwSectionLines.filter((l) => l === AIW_GITIGNORE_HEADER || !patternsToRemove.has(l));
294
+ newLines.push(...filtered, line);
295
+ }
296
+ else {
297
+ aiwSectionLines.push(line);
298
+ }
299
+ }
300
+ else {
301
+ newLines.push(line);
302
+ }
303
+ }
304
+ // Handle AIW section at end of file
305
+ if (inAiwSection) {
306
+ const filtered = aiwSectionLines.filter((l) => l === AIW_GITIGNORE_HEADER || !patternsToRemove.has(l));
307
+ newLines.push(...filtered);
308
+ }
309
+ // Clean up empty AIW section
310
+ let result = cleanupEmptySections(newLines.join('\n'));
311
+ result = result.replace(/\n+$/, '\n');
312
+ if (result.trim() === '') {
313
+ result = '';
314
+ }
315
+ await fs.writeFile(gitignorePath, result, 'utf8');
316
+ }
317
+ catch {
318
+ // .gitignore doesn't exist or can't be read
319
+ }
320
+ }
@@ -22,9 +22,9 @@ Each template installs into `.aiwcli/` (method files) and `.{ide}/` (IDE integra
22
22
  ```
23
23
  packages/cli/src/templates/
24
24
  ├── _shared/ # Cross-method infrastructure (installed by all methods)
25
- │ ├── hooks/ # Shared hook scripts (context, tasks, sessions)
26
- │ └── lib/ # Shared Python libraries
27
- │ ├── base/ # Core: atomic_write, constants, inference, utils
25
+ │ ├── hooks-ts/ # Shared TypeScript hook scripts (context, tasks, sessions)
26
+ │ └── lib-ts/ # Shared TypeScript libraries
27
+ │ ├── base/ # Core: atomic-write, constants, inference, utils
28
28
  │ ├── context/ # Context CRUD, selection, formatting, plans, tasks
29
29
  │ ├── handoff/ # Session handoff document generation
30
30
  │ └── templates/ # Output formatters, plan context templates
@@ -85,7 +85,7 @@ When multiple templates install, settings.json files merge:
85
85
 
86
86
  ## Hooks
87
87
 
88
- **Location:** Hooks live in `.aiwcli/_shared/hooks/` (cross-method) and `.aiwcli/_{method}/hooks/` (method-specific). They are configured in `.{ide}/settings.json`, not placed in IDE directories.
88
+ **Location:** Hooks live in `.aiwcli/_shared/hooks-ts/` (cross-method, TypeScript) and `.aiwcli/_{method}/hooks/` (method-specific). They are configured in `.{ide}/settings.json`, not placed in IDE directories.
89
89
 
90
90
  **Configuration:**
91
91
  ```json
@@ -93,14 +93,14 @@ When multiple templates install, settings.json files merge:
93
93
  "hooks": {
94
94
  "PostToolUse": [{
95
95
  "matcher": "Write",
96
- "hooks": [{ "type": "command", "command": "python .aiwcli/_cc-native/hooks/cc-native-plan-review.py", "timeout": 300000 }]
96
+ "hooks": [{ "type": "command", "command": "bun run .aiwcli/_cc-native/hooks/cc-native-plan-review.ts", "timeout": 300000 }]
97
97
  }]
98
98
  }
99
99
  }
100
100
  ```
101
101
 
102
102
  **Requirements:**
103
- - Prefix method-specific hooks with method name (e.g., `cc-native-plan-review.py`)
103
+ - Prefix method-specific hooks with method name (e.g., `cc-native-plan-review.ts`)
104
104
  - Use relative paths from project root
105
105
  - Write outputs to `_output/{method}/`
106
106
  - Specify timeouts
@@ -161,7 +161,7 @@ Load and execute `_{method}/workflows/{name}.md`.
161
161
  | Template file | `UPPERCASE.md.template` | `PROJECT.md.template` |
162
162
  | Workflow file | `kebab-case.md` | `new-project.md` |
163
163
  | Output file | `UPPERCASE.md` | `PROJECT.md` |
164
- | Hook file | `{method}-{name}.{ext}` | `gsd-plan-review.py` |
164
+ | Hook file | `{method}-{name}.{ext}` | `gsd-plan-review.ts` |
165
165
  | Settings key | `{method}` | `"gsd": {}` |
166
166
  | Readme | `{METHOD}-README.md` | `GSD-README.md` |
167
167
 
@@ -202,4 +202,4 @@ Load and execute `_{method}/workflows/{name}.md`.
202
202
  - Full workflows in IDE command files
203
203
  - Hardcoded paths without method namespace
204
204
  - Putting hook scripts directly in IDE directories (`.claude/hooks/`)
205
- - Creating `_shared/` directories inside method templates (e.g., `cc-native/_shared/`). All shared code lives in `packages/cli/src/templates/_shared/`. Method templates reference shared code via sys.path at runtime, not by copying.
205
+ - Creating `_shared/` directories inside method templates (e.g., `cc-native/_shared/`). All shared code lives in `packages/cli/src/templates/_shared/`. Method templates reference shared code via imports at runtime, not by copying.
@@ -0,0 +1,64 @@
1
+ # Resume Handoff
2
+
3
+ Restore session context from a handoff document programmatically, then create actionable ISC tasks so work continues without re-discovering what was already learned.
4
+
5
+ ## Arguments
6
+
7
+ - `$ARGUMENTS` - Optional path to a handoff folder or index.md. If omitted, auto-discovers the most recent handoff from the active context.
8
+
9
+ ## Instructions
10
+
11
+ ### Step 1: Gather Context via Script
12
+
13
+ Run the resume script to collect and format all handoff sections:
14
+
15
+ **If `$ARGUMENTS` is provided:**
16
+ ```bash
17
+ bun .aiwcli/_shared/scripts/resume_handoff.ts "$ARGUMENTS"
18
+ ```
19
+
20
+ **If `$ARGUMENTS` is empty:**
21
+ The script auto-discovers the active context ID programmatically — no manual lookup needed:
22
+ ```bash
23
+ bun .aiwcli/_shared/scripts/resume_handoff.ts
24
+ ```
25
+
26
+ Present the script's output to the conversation. The output is already structured in priority order (dead ends first, then pending items, decisions, git delta, completed work, context notes).
27
+
28
+ If the script exits with an error, show the error message and stop.
29
+
30
+ ### Step 2: Create ISC Tasks
31
+
32
+ Convert each actionable item from the script output into a task via `TaskCreate`:
33
+ - Each pending issue from the **Pending Items** section
34
+ - Each remaining plan item from the **Plan — Remaining Items** section
35
+
36
+ Each task follows ISC format — ~8 words, states a desired end-state (not an action), and is binary testable in 2 seconds.
37
+
38
+ **Example:**
39
+
40
+ | Source | ISC Task |
41
+ |--------|----------|
42
+ | Pending: "Fix race condition in SessionStore" | "SessionStore handles concurrent access without race conditions" |
43
+ | Plan remaining: "Add retry logic to API client" | "API client retries failed requests with exponential backoff" |
44
+ | Next step: "Write tests for auth flow" | "Auth flow has passing integration test coverage" |
45
+
46
+ ### Step 3: Confirm Ready
47
+
48
+ After creating tasks, run `TaskList` and confirm ready to continue.
49
+
50
+ ## Constraints
51
+
52
+ - Dead ends are presented verbatim from the script output — never summarize or omit entries
53
+ - ISC tasks use ~8-word end-state format, not action descriptions
54
+ - Skip missing files gracefully (the script handles this)
55
+ - If the script warns about staleness (>7 days), surface the warning prominently
56
+
57
+ ## Success Criteria
58
+
59
+ - [ ] Script ran successfully and output the structured briefing
60
+ - [ ] Dead ends visible in conversation (not summarized)
61
+ - [ ] Pending items converted to ISC tasks via TaskCreate
62
+ - [ ] Plan completion percentage visible (if plan exists)
63
+ - [ ] Git delta visible
64
+ - [ ] Tasks confirmed via TaskList
@@ -19,14 +19,15 @@ Generate a handoff document summarizing the current session's work, decisions, a
19
19
 
20
20
  ### Step 1: Get Context ID
21
21
 
22
- Extract the `context_id` from the system reminder injected by the context enforcer hook.
22
+ Resolve the active context ID programmatically:
23
23
 
24
- Look for the pattern in the system reminder:
25
- ```
26
- Active Context: {context_id}
24
+ ```bash
25
+ bun .aiwcli/_shared/scripts/resolve_context.ts
27
26
  ```
28
27
 
29
- If no active context is found, inform the user and stop - handoffs require an active context.
28
+ This prints the active context ID to stdout. Use its output as `{context_id}` in subsequent steps.
29
+
30
+ If the script exits with an error (no active context found), inform the user and stop — handoffs require an active context.
30
31
 
31
32
  ### Step 2: Gather Information
32
33
 
@@ -150,7 +151,7 @@ If a plan document path was provided in `$ARGUMENTS`:
150
151
  Instead of writing the file directly, pipe your handoff content to the save script:
151
152
 
152
153
  ```bash
153
- python .aiwcli/_shared/scripts/save_handoff.py "{context_id}" <<'EOF'
154
+ bun .aiwcli/_shared/scripts/save_handoff.ts "{context_id}" <<'EOF'
154
155
  {Your complete handoff markdown content from Step 3}
155
156
  EOF
156
157
  ```
@@ -159,9 +160,13 @@ This script:
159
160
  1. Creates a folder at `_output/contexts/{context_id}/handoffs/{YYYY-MM-DD-HHMM}/`
160
161
  2. Parses sections and writes sharded files (index.md, completed-work.md, dead-ends.md, etc.)
161
162
  3. Copies the current plan (if any) to plan.md
162
- 4. Records the event in the context's event log (informational only)
163
+ 4. Sets `handoff_path` to the index.md path and `handoff_consumed = false` in state.json
164
+ 5. Mode stays `active` — staging happens later via session_end hook
163
165
 
164
- Use the handoff folder for context in the next session.
166
+ When the session ends, `session_end.ts` stages `active has_handoff` (if handoff_path
167
+ exists and handoff_consumed is false). On next `/clear`, `session_start.ts` picks up the
168
+ `has_handoff` state, binds the new session, transitions to `active`, and injects the
169
+ handoff content via `formatHandoffContinuation()`.
165
170
 
166
171
  ## Dead Ends Section Guidelines
167
172
 
@@ -198,7 +203,8 @@ After creating file, output:
198
203
  - plan.md (copy of current plan, if any)
199
204
 
200
205
  To continue next session:
201
- The index.md will be automatically suggested when you start a new session.
206
+ Automatic: Handoff restored on next /clear via session_start hook.
207
+ Manual: Use /handoff-resume to explicitly load handoff context at any time.
202
208
  Read dead-ends.md first to avoid repeating failed approaches.
203
209
 
204
210
  ⚠️ {N} dead ends documented — avoid re-attempting these approaches
@@ -223,4 +229,4 @@ If plan was updated:
223
229
  - [ ] Git status included in index.md
224
230
  - [ ] If plan provided: checkboxes updated to reflect completion status
225
231
  - [ ] If plan provided: Session Progress Log appended
226
- - [ ] Context state updated to indicate handoff pending
232
+ - [ ] State has handoff_path set and handoff_consumed = false