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.
- package/bin/run.js +1 -1
- package/dist/commands/clear.js +28 -131
- package/dist/commands/init/index.js +3 -3
- package/dist/lib/gitignore-manager.d.ts +32 -0
- package/dist/lib/gitignore-manager.js +141 -2
- package/dist/templates/CLAUDE.md +8 -8
- package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
- package/dist/templates/_shared/.claude/settings.json +7 -7
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
- package/dist/templates/_shared/hooks-ts/session_end.ts +107 -0
- package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
- package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
- package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
- package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
- package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
- package/dist/templates/_shared/lib-ts/base/logger.ts +15 -2
- package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +142 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
- package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +43 -23
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +158 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
- package/dist/templates/_shared/lib-ts/types.ts +68 -55
- package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
- package/dist/templates/_shared/scripts/resume_handoff.ts +345 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +3 -3
- package/dist/templates/_shared/scripts/status_line.ts +687 -0
- package/dist/templates/cc-native/.claude/settings.json +175 -185
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +1027 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +792 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +120 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +250 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +275 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +107 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +240 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +385 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +14 -1
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
- package/dist/templates/_shared/hooks/__init__.py +0 -16
- package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +0 -177
- package/dist/templates/_shared/hooks/context_monitor.py +0 -270
- package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
- package/dist/templates/_shared/hooks/pre_compact.py +0 -104
- package/dist/templates/_shared/hooks/session_end.py +0 -173
- package/dist/templates/_shared/hooks/session_start.py +0 -206
- package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
- package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
- package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
- package/dist/templates/_shared/lib/__init__.py +0 -1
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__init__.py +0 -65
- package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
- package/dist/templates/_shared/lib/base/constants.py +0 -358
- package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
- package/dist/templates/_shared/lib/base/inference.py +0 -307
- package/dist/templates/_shared/lib/base/logger.py +0 -305
- package/dist/templates/_shared/lib/base/stop_words.py +0 -221
- package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
- package/dist/templates/_shared/lib/base/utils.py +0 -263
- package/dist/templates/_shared/lib/context/__init__.py +0 -102
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
- package/dist/templates/_shared/lib/context/context_selector.py +0 -508
- package/dist/templates/_shared/lib/context/context_store.py +0 -653
- package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
- package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
- package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
- package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
- package/dist/templates/_shared/lib/templates/README.md +0 -206
- package/dist/templates/_shared/lib/templates/__init__.py +0 -36
- package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/formatters.py +0 -146
- package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
- package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.py +0 -357
- package/dist/templates/_shared/scripts/status_line.py +0 -716
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/MIGRATION.md +0 -86
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
- package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
- package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
- package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
- 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'
|
package/dist/commands/clear.js
CHANGED
|
@@ -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
|
-
//
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
this.logDebug(
|
|
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
|
|
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 (
|
|
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, [
|
|
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 = [
|
|
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
|
+
}
|
package/dist/templates/CLAUDE.md
CHANGED
|
@@ -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/
|
|
26
|
-
│ └── lib/
|
|
27
|
-
│ ├── base/ # Core:
|
|
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": "
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
22
|
+
Resolve the active context ID programmatically:
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Active Context: {context_id}
|
|
24
|
+
```bash
|
|
25
|
+
bun .aiwcli/_shared/scripts/resolve_context.ts
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
- [ ]
|
|
232
|
+
- [ ] State has handoff_path set and handoff_consumed = false
|