aiwcli 0.10.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +104 -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/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 +31 -15
- 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 +139 -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 +61 -37
- 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 +159 -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 +321 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +21 -21
- package/dist/templates/_shared/scripts/status_line.ts +733 -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 +921 -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 +157 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -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 +124 -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/debug.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -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 +106 -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 +243 -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 +310 -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 +1 -9
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- 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
|
@@ -9,20 +9,21 @@
|
|
|
9
9
|
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
11
|
import * as path from "node:path";
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
import { atomicWrite } from "../base/atomic-write.js";
|
|
14
14
|
import {
|
|
15
|
+
getArchiveContextDir,
|
|
16
|
+
getArchiveDir,
|
|
17
|
+
getArchiveIndexPath,
|
|
15
18
|
getContextDir,
|
|
16
19
|
getContextsDir,
|
|
17
20
|
getIndexPath,
|
|
18
|
-
getArchiveDir,
|
|
19
|
-
getArchiveContextDir,
|
|
20
|
-
getArchiveIndexPath,
|
|
21
21
|
validateContextId,
|
|
22
22
|
} from "../base/constants.js";
|
|
23
|
-
import { logDebug, logInfo, logWarn,
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
23
|
+
import { logDebug as _logDebug, logError, logInfo, logWarn, setContextPath } from "../base/logger.js";
|
|
24
|
+
import { dictToState as _dictToState, readStateJson, toDict as _toDict, writeStateJson } from "../base/state-io.js";
|
|
25
|
+
import { generateContextId, nowIso } from "../base/utils.js";
|
|
26
|
+
import type { ContextState, IndexEntry, IndexFile, Mode } from "../types.js";
|
|
26
27
|
|
|
27
28
|
const INDEX_VERSION = "3.0";
|
|
28
29
|
|
|
@@ -34,12 +35,13 @@ function loadIndex(projectRoot?: string): IndexFile {
|
|
|
34
35
|
const indexPath = getIndexPath(projectRoot);
|
|
35
36
|
if (fs.existsSync(indexPath)) {
|
|
36
37
|
try {
|
|
37
|
-
const raw = fs.readFileSync(indexPath, "
|
|
38
|
+
const raw = fs.readFileSync(indexPath, "utf8");
|
|
38
39
|
return JSON.parse(raw) as IndexFile;
|
|
39
|
-
} catch (
|
|
40
|
-
logWarn("context_store", `Failed to read index, recreating: ${
|
|
40
|
+
} catch (error: any) {
|
|
41
|
+
logWarn("context_store", `Failed to read index, recreating: ${error}`);
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
|
|
43
45
|
return { version: INDEX_VERSION, updated_at: nowIso(), sessions: {}, contexts: {} };
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -50,6 +52,7 @@ function saveIndex(index: IndexFile, projectRoot?: string): boolean {
|
|
|
50
52
|
if (!success) {
|
|
51
53
|
logWarn("context_store", `Failed to write index: ${error}`);
|
|
52
54
|
}
|
|
55
|
+
|
|
53
56
|
return success;
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -69,7 +72,7 @@ function migrateContextJson(contextId: string, projectRoot?: string): ContextSta
|
|
|
69
72
|
if (!fs.existsSync(legacyPath)) return null;
|
|
70
73
|
|
|
71
74
|
try {
|
|
72
|
-
const data = JSON.parse(fs.readFileSync(legacyPath, "
|
|
75
|
+
const data = JSON.parse(fs.readFileSync(legacyPath, "utf8"));
|
|
73
76
|
const inFlight = data.in_flight ?? {};
|
|
74
77
|
const oldMode = inFlight.mode ?? "none";
|
|
75
78
|
const MODE_MIGRATION: Record<string, string> = {
|
|
@@ -104,8 +107,8 @@ function migrateContextJson(contextId: string, projectRoot?: string): ContextSta
|
|
|
104
107
|
last_session: null,
|
|
105
108
|
tasks: [],
|
|
106
109
|
};
|
|
107
|
-
} catch (
|
|
108
|
-
logWarn("context_store", `Failed to migrate context.json for '${contextId}': ${
|
|
110
|
+
} catch (error: any) {
|
|
111
|
+
logWarn("context_store", `Failed to migrate context.json for '${contextId}': ${error}`);
|
|
109
112
|
return null;
|
|
110
113
|
}
|
|
111
114
|
}
|
|
@@ -134,7 +137,7 @@ export function saveState(
|
|
|
134
137
|
contextId: string,
|
|
135
138
|
state: ContextState,
|
|
136
139
|
projectRoot?: string,
|
|
137
|
-
): [boolean,
|
|
140
|
+
): [boolean, null | string] {
|
|
138
141
|
// Ensure the state ID matches
|
|
139
142
|
state.id = contextId;
|
|
140
143
|
|
|
@@ -152,10 +155,12 @@ export function saveState(
|
|
|
152
155
|
if (!index.sessions) index.sessions = {} as Record<string, string>;
|
|
153
156
|
index.sessions[sid] = contextId;
|
|
154
157
|
}
|
|
158
|
+
|
|
155
159
|
const indexOk = saveIndex(index, projectRoot);
|
|
156
160
|
if (!indexOk) {
|
|
157
161
|
return [true, "state.json saved but index.json update failed"];
|
|
158
162
|
}
|
|
163
|
+
|
|
159
164
|
return [true, null];
|
|
160
165
|
}
|
|
161
166
|
|
|
@@ -165,7 +170,7 @@ export function saveState(
|
|
|
165
170
|
* See SPEC.md §7.4
|
|
166
171
|
*/
|
|
167
172
|
export function createContext(
|
|
168
|
-
contextId:
|
|
173
|
+
contextId: null | string,
|
|
169
174
|
summary: string,
|
|
170
175
|
method = "",
|
|
171
176
|
projectRoot?: string,
|
|
@@ -185,23 +190,8 @@ export function createContext(
|
|
|
185
190
|
} catch { /* ignore */ }
|
|
186
191
|
}
|
|
187
192
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const yy = String(now.getFullYear()).slice(2);
|
|
191
|
-
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
192
|
-
const dd = String(now.getDate()).padStart(2, "0");
|
|
193
|
-
const hh = String(now.getHours()).padStart(2, "0");
|
|
194
|
-
const min = String(now.getMinutes()).padStart(2, "0");
|
|
195
|
-
const timestamp = `${yy}${mm}${dd}-${hh}${min}`;
|
|
196
|
-
const words = summary.toLowerCase().split(/\s+/).filter(w => w.length > 2).slice(0, 6);
|
|
197
|
-
const slug = words.length > 0 ? words.join("-").replace(/[^a-z0-9-]/g, "").slice(0, 50) : "context";
|
|
198
|
-
contextId = `${timestamp}-${slug}`;
|
|
199
|
-
|
|
200
|
-
if (existingIds.has(contextId)) {
|
|
201
|
-
let counter = 2;
|
|
202
|
-
while (existingIds.has(`${contextId}-${counter}`)) counter++;
|
|
203
|
-
contextId = `${contextId}-${counter}`;
|
|
204
|
-
}
|
|
193
|
+
|
|
194
|
+
contextId = generateContextId(summary, existingIds);
|
|
205
195
|
}
|
|
206
196
|
|
|
207
197
|
contextId = validateContextId(contextId);
|
|
@@ -251,6 +241,7 @@ export function getContext(contextId: string, projectRoot?: string): ContextStat
|
|
|
251
241
|
} catch {
|
|
252
242
|
return null;
|
|
253
243
|
}
|
|
244
|
+
|
|
254
245
|
return loadState(contextId, projectRoot);
|
|
255
246
|
}
|
|
256
247
|
|
|
@@ -288,6 +279,7 @@ export function getAllContexts(
|
|
|
288
279
|
try {
|
|
289
280
|
if (!fs.statSync(fullPath).isDirectory()) continue;
|
|
290
281
|
} catch { continue; }
|
|
282
|
+
|
|
291
283
|
const state = loadState(entry, projectRoot);
|
|
292
284
|
if (state && (!status || state.status === status)) {
|
|
293
285
|
results.push(state);
|
|
@@ -306,7 +298,7 @@ export function getAllContexts(
|
|
|
306
298
|
*/
|
|
307
299
|
export function updateContext(
|
|
308
300
|
contextId: string,
|
|
309
|
-
updates: Partial<Pick<ContextState, "
|
|
301
|
+
updates: Partial<Pick<ContextState, "method" | "summary" | "tags">>,
|
|
310
302
|
projectRoot?: string,
|
|
311
303
|
): ContextState | null {
|
|
312
304
|
const state = getContext(contextId, projectRoot);
|
|
@@ -356,6 +348,7 @@ export function getContextBySessionId(
|
|
|
356
348
|
return state;
|
|
357
349
|
}
|
|
358
350
|
}
|
|
351
|
+
|
|
359
352
|
return null;
|
|
360
353
|
}
|
|
361
354
|
|
|
@@ -387,6 +380,7 @@ export function bindSession(
|
|
|
387
380
|
if (!state.session_ids.includes(sessionId)) {
|
|
388
381
|
state.session_ids.push(sessionId);
|
|
389
382
|
}
|
|
383
|
+
|
|
390
384
|
state.last_active = nowIso();
|
|
391
385
|
|
|
392
386
|
const [success] = saveState(contextId, state, projectRoot);
|
|
@@ -402,13 +396,13 @@ export function updateMode(
|
|
|
402
396
|
mode: Mode,
|
|
403
397
|
projectRoot?: string,
|
|
404
398
|
opts?: {
|
|
405
|
-
|
|
406
|
-
plan_hash?: string;
|
|
407
|
-
plan_signature?: string;
|
|
408
|
-
plan_id?: string;
|
|
399
|
+
handoff_consumed?: boolean;
|
|
409
400
|
plan_anchors?: string[];
|
|
410
401
|
plan_consumed?: boolean;
|
|
411
|
-
|
|
402
|
+
plan_hash?: string;
|
|
403
|
+
plan_id?: string;
|
|
404
|
+
plan_path?: string;
|
|
405
|
+
plan_signature?: string;
|
|
412
406
|
},
|
|
413
407
|
): ContextState | null {
|
|
414
408
|
const state = getContext(contextId, projectRoot);
|
|
@@ -506,6 +500,7 @@ export function archiveContext(contextId: string, projectRoot?: string): Context
|
|
|
506
500
|
logWarn("context_store", `Cannot archive: context '${contextId}' not found`);
|
|
507
501
|
return null;
|
|
508
502
|
}
|
|
503
|
+
|
|
509
504
|
if (state.status !== "completed") {
|
|
510
505
|
logWarn("context_store", `Cannot archive: context '${contextId}' not completed`);
|
|
511
506
|
return null;
|
|
@@ -524,8 +519,8 @@ export function archiveContext(contextId: string, projectRoot?: string): Context
|
|
|
524
519
|
|
|
525
520
|
try {
|
|
526
521
|
fs.renameSync(sourceDir, archiveDest);
|
|
527
|
-
} catch (
|
|
528
|
-
logError("context_store", `Failed to move context to archive: ${
|
|
522
|
+
} catch (error: any) {
|
|
523
|
+
logError("context_store", `Failed to move context to archive: ${error}`);
|
|
529
524
|
return null;
|
|
530
525
|
}
|
|
531
526
|
|
|
@@ -536,6 +531,7 @@ export function archiveContext(contextId: string, projectRoot?: string): Context
|
|
|
536
531
|
for (const [sid, cid] of Object.entries(sessions)) {
|
|
537
532
|
if (cid === contextId) delete sessions[sid];
|
|
538
533
|
}
|
|
534
|
+
|
|
539
535
|
saveIndex(index, projectRoot);
|
|
540
536
|
|
|
541
537
|
// Add to archive index
|
|
@@ -555,6 +551,7 @@ export function reopenContext(contextId: string, projectRoot?: string): ContextS
|
|
|
555
551
|
if (!state) {
|
|
556
552
|
state = restoreFromArchive(contextId, projectRoot);
|
|
557
553
|
}
|
|
554
|
+
|
|
558
555
|
if (!state) return null;
|
|
559
556
|
|
|
560
557
|
if (state.status === "active") {
|
|
@@ -595,6 +592,35 @@ export function createContextFromPrompt(
|
|
|
595
592
|
);
|
|
596
593
|
}
|
|
597
594
|
|
|
595
|
+
/**
|
|
596
|
+
* Find the active context ID programmatically.
|
|
597
|
+
* Checks CONTEXT_ID env var first, then searches for the single active context.
|
|
598
|
+
* Returns null if no active context or multiple active contexts found.
|
|
599
|
+
*/
|
|
600
|
+
export function findActiveContextId(projectRoot?: string): null | string {
|
|
601
|
+
// Env var takes priority
|
|
602
|
+
const envId = process.env.CONTEXT_ID;
|
|
603
|
+
if (envId) {
|
|
604
|
+
const ctx = getContext(envId, projectRoot);
|
|
605
|
+
if (ctx) return ctx.id;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Search for active contexts
|
|
609
|
+
const active = getAllContexts("active", projectRoot)
|
|
610
|
+
.filter(c => c.mode === "active" || c.mode === "has_plan" || c.mode === "has_handoff");
|
|
611
|
+
|
|
612
|
+
if (active.length === 1) return active[0]!.id;
|
|
613
|
+
if (active.length > 1) {
|
|
614
|
+
// Multiple active — try to find the most recently active
|
|
615
|
+
const sorted = active.sort((a, b) =>
|
|
616
|
+
(b.last_active ?? "").localeCompare(a.last_active ?? ""),
|
|
617
|
+
);
|
|
618
|
+
return sorted[0]!.id;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
|
|
598
624
|
// ---------------------------------------------------------------------------
|
|
599
625
|
// Archive helpers
|
|
600
626
|
// ---------------------------------------------------------------------------
|
|
@@ -613,9 +639,9 @@ function updateArchiveIndex(state: ContextState, projectRoot?: string): boolean
|
|
|
613
639
|
|
|
614
640
|
if (fs.existsSync(archiveIndexPath)) {
|
|
615
641
|
try {
|
|
616
|
-
archiveIndex = JSON.parse(fs.readFileSync(archiveIndexPath, "
|
|
617
|
-
} catch (
|
|
618
|
-
logWarn("context_store", `Failed to read archive index, recreating: ${
|
|
642
|
+
archiveIndex = JSON.parse(fs.readFileSync(archiveIndexPath, "utf8"));
|
|
643
|
+
} catch (error_: any) {
|
|
644
|
+
logWarn("context_store", `Failed to read archive index, recreating: ${error_}`);
|
|
619
645
|
}
|
|
620
646
|
}
|
|
621
647
|
|
|
@@ -627,6 +653,7 @@ function updateArchiveIndex(state: ContextState, projectRoot?: string): boolean
|
|
|
627
653
|
if (!success) {
|
|
628
654
|
logWarn("context_store", `Failed to write archive index: ${error}`);
|
|
629
655
|
}
|
|
656
|
+
|
|
630
657
|
return success;
|
|
631
658
|
}
|
|
632
659
|
|
|
@@ -642,8 +669,8 @@ function restoreFromArchive(contextId: string, projectRoot?: string): ContextSta
|
|
|
642
669
|
|
|
643
670
|
try {
|
|
644
671
|
fs.renameSync(archiveDir, activeDir);
|
|
645
|
-
} catch (
|
|
646
|
-
logError("context_store", `Failed to restore context from archive: ${
|
|
672
|
+
} catch (error: any) {
|
|
673
|
+
logError("context_store", `Failed to restore context from archive: ${error}`);
|
|
647
674
|
return null;
|
|
648
675
|
}
|
|
649
676
|
|
|
@@ -660,7 +687,7 @@ function removeFromArchiveIndex(contextId: string, projectRoot?: string): boolea
|
|
|
660
687
|
if (!fs.existsSync(archiveIndexPath)) return true;
|
|
661
688
|
|
|
662
689
|
try {
|
|
663
|
-
const archiveIndex = JSON.parse(fs.readFileSync(archiveIndexPath, "
|
|
690
|
+
const archiveIndex = JSON.parse(fs.readFileSync(archiveIndexPath, "utf8")) as IndexFile;
|
|
664
691
|
if (archiveIndex.contexts[contextId]) {
|
|
665
692
|
delete archiveIndex.contexts[contextId];
|
|
666
693
|
archiveIndex.updated_at = nowIso();
|
|
@@ -671,9 +698,10 @@ function removeFromArchiveIndex(contextId: string, projectRoot?: string): boolea
|
|
|
671
698
|
return false;
|
|
672
699
|
}
|
|
673
700
|
}
|
|
701
|
+
|
|
674
702
|
return true;
|
|
675
|
-
} catch (
|
|
676
|
-
logWarn("context_store", `Failed to read archive index: ${
|
|
703
|
+
} catch (error: any) {
|
|
704
|
+
logWarn("context_store", `Failed to read archive index: ${error}`);
|
|
677
705
|
return false;
|
|
678
706
|
}
|
|
679
707
|
}
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
* - extractPlanPathFromResult: parse plan path from ExitPlanMode output
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import * as crypto from "node:crypto";
|
|
11
12
|
import * as fs from "node:fs";
|
|
12
13
|
import * as path from "node:path";
|
|
13
|
-
|
|
14
|
-
import { getContextDir, getContextPlansDir, sanitizeTitle } from "../base/constants.js";
|
|
14
|
+
|
|
15
15
|
import { atomicWrite } from "../base/atomic-write.js";
|
|
16
|
+
import { getContextDir as _getContextDir, getContextPlansDir, sanitizeTitle } from "../base/constants.js";
|
|
17
|
+
import { logDebug, logError, logInfo, logWarn } from "../base/logger.js";
|
|
16
18
|
import { readStateJson } from "../base/state-io.js";
|
|
17
|
-
import {
|
|
18
|
-
import type { ContextState } from "../types.js";
|
|
19
|
+
import { generateSlug } from "../base/utils.js";
|
|
20
|
+
import type { ContextState as _ContextState } from "../types.js";
|
|
19
21
|
|
|
20
22
|
// ---------------------------------------------------------------------------
|
|
21
23
|
// Plan archival
|
|
@@ -34,7 +36,7 @@ export async function archivePlan(
|
|
|
34
36
|
planPath: string,
|
|
35
37
|
contextId: string,
|
|
36
38
|
projectRoot?: string,
|
|
37
|
-
): Promise<[
|
|
39
|
+
): Promise<[null | string, null | string, null | string]> {
|
|
38
40
|
if (!fs.existsSync(planPath)) {
|
|
39
41
|
logWarn("plan_manager", `Plan file not found: ${planPath}`);
|
|
40
42
|
return [null, null, null];
|
|
@@ -42,9 +44,9 @@ export async function archivePlan(
|
|
|
42
44
|
|
|
43
45
|
let content: string;
|
|
44
46
|
try {
|
|
45
|
-
content = fs.readFileSync(planPath, "
|
|
46
|
-
} catch (
|
|
47
|
-
logError("plan_manager", `Failed to read plan: ${
|
|
47
|
+
content = fs.readFileSync(planPath, "utf8");
|
|
48
|
+
} catch (error_: any) {
|
|
49
|
+
logError("plan_manager", `Failed to read plan: ${error_}`);
|
|
48
50
|
return [null, null, null];
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -69,23 +71,12 @@ export async function archivePlan(
|
|
|
69
71
|
String(now.getMinutes()).padStart(2, "0"),
|
|
70
72
|
].join("");
|
|
71
73
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
slug = sanitizeTitle(aiSlug, 60);
|
|
79
|
-
}
|
|
80
|
-
} catch {
|
|
81
|
-
// AI inference unavailable — use filename fallback
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Fallback: use plan filename
|
|
85
|
-
if (!slug) {
|
|
86
|
-
const stem = path.basename(planPath, path.extname(planPath));
|
|
87
|
-
slug = sanitizeTitle(stem, 30);
|
|
88
|
-
}
|
|
74
|
+
// Extract a clean summary from plan content for slug generation.
|
|
75
|
+
// Headings describe the plan's intent better than raw markdown body.
|
|
76
|
+
const summary = extractPlanSummary(content);
|
|
77
|
+
const slug = generateSlug(summary, 60, sanitizeTitle(
|
|
78
|
+
path.basename(planPath, path.extname(planPath)), 30,
|
|
79
|
+
));
|
|
89
80
|
|
|
90
81
|
let archiveName = `${dateStr}-${slug}.md`;
|
|
91
82
|
let archivePath = path.join(plansDir, archiveName);
|
|
@@ -109,6 +100,37 @@ export async function archivePlan(
|
|
|
109
100
|
return [archivePath, planHash, planSignature];
|
|
110
101
|
}
|
|
111
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Extract a human-readable summary from plan markdown content.
|
|
105
|
+
* Pulls headings and the first substantial paragraph, producing
|
|
106
|
+
* text suitable for the AI slug generator (which expects conversational input).
|
|
107
|
+
*/
|
|
108
|
+
function extractPlanSummary(content: string): string {
|
|
109
|
+
const lines = content.split("\n");
|
|
110
|
+
const parts: string[] = [];
|
|
111
|
+
let firstParagraph = "";
|
|
112
|
+
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
const trimmed = line.trim();
|
|
115
|
+
// Collect markdown headings (strip # prefix)
|
|
116
|
+
if (trimmed.startsWith("#")) {
|
|
117
|
+
const heading = trimmed.replace(/^#+\s*/, "");
|
|
118
|
+
if (heading.length > 2) parts.push(heading);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Grab first substantial non-heading line as context
|
|
122
|
+
if (!firstParagraph && !trimmed.startsWith("#") && trimmed.length > 20) {
|
|
123
|
+
firstParagraph = trimmed.slice(0, 120);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Enough material for the AI
|
|
127
|
+
if (parts.length >= 5) break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (firstParagraph) parts.push(firstParagraph);
|
|
131
|
+
return parts.join(" ").slice(0, 500) || content.slice(0, 500);
|
|
132
|
+
}
|
|
133
|
+
|
|
112
134
|
// ---------------------------------------------------------------------------
|
|
113
135
|
// Plan lookup
|
|
114
136
|
// ---------------------------------------------------------------------------
|
|
@@ -121,15 +143,15 @@ export async function archivePlan(
|
|
|
121
143
|
export function findLatestPlan(
|
|
122
144
|
contextId: string,
|
|
123
145
|
projectRoot?: string,
|
|
124
|
-
):
|
|
146
|
+
): null | string {
|
|
125
147
|
// 1. Check state.json plan_path first
|
|
126
148
|
try {
|
|
127
149
|
const state = readStateJson(contextId, projectRoot);
|
|
128
150
|
if (state?.plan_path && fs.existsSync(state.plan_path)) {
|
|
129
151
|
return state.plan_path;
|
|
130
152
|
}
|
|
131
|
-
} catch (
|
|
132
|
-
logWarn("plan_manager", `Failed to check state.json plan_path: ${
|
|
153
|
+
} catch (error: any) {
|
|
154
|
+
logWarn("plan_manager", `Failed to check state.json plan_path: ${error}`);
|
|
133
155
|
}
|
|
134
156
|
|
|
135
157
|
// 2. Fall back to most recent .md in plans/ dir
|
|
@@ -162,7 +184,7 @@ export function findLatestPlan(
|
|
|
162
184
|
* See SPEC.md §9.4
|
|
163
185
|
*/
|
|
164
186
|
export function generatePlanId(): string {
|
|
165
|
-
return crypto.randomUUID().
|
|
187
|
+
return crypto.randomUUID().replaceAll('-', "").slice(0, 8);
|
|
166
188
|
}
|
|
167
189
|
|
|
168
190
|
/**
|
|
@@ -171,8 +193,8 @@ export function generatePlanId(): string {
|
|
|
171
193
|
* See SPEC.md §9.5
|
|
172
194
|
*/
|
|
173
195
|
export function normalizePlanContent(text: string): string {
|
|
174
|
-
let result = text.
|
|
175
|
-
result = result.
|
|
196
|
+
let result = text.replaceAll(/<[^>]+>/g, "");
|
|
197
|
+
result = result.replaceAll(/\s+/g, " ").trim();
|
|
176
198
|
return result;
|
|
177
199
|
}
|
|
178
200
|
|
|
@@ -190,8 +212,10 @@ export function extractPlanAnchors(content: string, maxAnchors = 5): string[] {
|
|
|
190
212
|
} else if (anchors.length === 0 && trimmed.length > 20) {
|
|
191
213
|
anchors.push(trimmed.slice(0, 80));
|
|
192
214
|
}
|
|
215
|
+
|
|
193
216
|
if (anchors.length >= maxAnchors) break;
|
|
194
217
|
}
|
|
218
|
+
|
|
195
219
|
return anchors;
|
|
196
220
|
}
|
|
197
221
|
|
|
@@ -206,7 +230,7 @@ const MAX_TRANSCRIPT_SIZE = 50 * 1024 * 1024; // 50 MB
|
|
|
206
230
|
* Searches in reverse for the most recent Write tool call targeting .claude/plans/.
|
|
207
231
|
* See SPEC.md §9.7
|
|
208
232
|
*/
|
|
209
|
-
export function findPlanPathInTranscript(transcriptPath: string):
|
|
233
|
+
export function findPlanPathInTranscript(transcriptPath: string): null | string {
|
|
210
234
|
if (!transcriptPath) return null;
|
|
211
235
|
|
|
212
236
|
if (!fs.existsSync(transcriptPath)) {
|
|
@@ -228,9 +252,9 @@ export function findPlanPathInTranscript(transcriptPath: string): string | null
|
|
|
228
252
|
|
|
229
253
|
let lines: string[];
|
|
230
254
|
try {
|
|
231
|
-
lines = fs.readFileSync(transcriptPath, "
|
|
232
|
-
} catch (
|
|
233
|
-
logWarn("plan_manager", `Failed to read transcript: ${
|
|
255
|
+
lines = fs.readFileSync(transcriptPath, "utf8").split("\n");
|
|
256
|
+
} catch (error: any) {
|
|
257
|
+
logWarn("plan_manager", `Failed to read transcript: ${error}`);
|
|
234
258
|
return null;
|
|
235
259
|
}
|
|
236
260
|
|
|
@@ -262,7 +286,7 @@ export function findPlanPathInTranscript(transcriptPath: string): string | null
|
|
|
262
286
|
if (!filePath) continue;
|
|
263
287
|
|
|
264
288
|
// Check if path contains .claude/plans/ as consecutive parts
|
|
265
|
-
const parts = filePath.
|
|
289
|
+
const parts = filePath.replaceAll('\\', "/").split("/");
|
|
266
290
|
for (let j = 0; j < parts.length - 1; j++) {
|
|
267
291
|
if (parts[j] === ".claude" && parts[j + 1] === "plans") {
|
|
268
292
|
logInfo("plan_manager", `Extracted plan path from transcript: ${filePath}`);
|
|
@@ -285,7 +309,7 @@ export function findPlanPathInTranscript(transcriptPath: string): string | null
|
|
|
285
309
|
* Parses the pattern: "Your plan has been saved to: <path>"
|
|
286
310
|
* See SPEC.md §9.8
|
|
287
311
|
*/
|
|
288
|
-
export function extractPlanPathFromResult(toolResult: string):
|
|
312
|
+
export function extractPlanPathFromResult(toolResult: string): null | string {
|
|
289
313
|
if (!toolResult) return null;
|
|
290
314
|
const match = toolResult.match(/Your plan has been saved to:\s*(.+\.md)/);
|
|
291
315
|
return match ? match[1]!.trim() : null;
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* Uses state-io for I/O to avoid circular imports with context-store.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { readStateJson, writeStateJson, toDict } from "../base/state-io.js";
|
|
10
9
|
import { logWarn } from "../base/logger.js";
|
|
10
|
+
import { readStateJson, toDict as _toDict, writeStateJson } from "../base/state-io.js";
|
|
11
11
|
import { nowIso } from "../base/utils.js";
|
|
12
|
-
import type { ContextState, Task } from "../types.js";
|
|
12
|
+
import type { ContextState as _ContextState, Task } from "../types.js";
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// Public API
|
|
@@ -27,7 +27,7 @@ export function generateNextTaskId(contextId: string, projectRoot?: string): str
|
|
|
27
27
|
for (const t of tasks) {
|
|
28
28
|
const match = /^aiw-(\d+)$/.exec(t.id);
|
|
29
29
|
if (match) {
|
|
30
|
-
const num = parseInt(match[1]!, 10);
|
|
30
|
+
const num = Number.parseInt(match[1]!, 10);
|
|
31
31
|
if (num > maxNum) maxNum = num;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -46,7 +46,7 @@ export function addTask(
|
|
|
46
46
|
activeForm = "",
|
|
47
47
|
sessionId = "",
|
|
48
48
|
projectRoot?: string,
|
|
49
|
-
):
|
|
49
|
+
): null | Task {
|
|
50
50
|
const state = readStateJson(contextId, projectRoot);
|
|
51
51
|
if (!state) return null;
|
|
52
52
|
|
|
@@ -80,11 +80,11 @@ export function updateTask(
|
|
|
80
80
|
contextId: string,
|
|
81
81
|
taskId: string,
|
|
82
82
|
opts?: {
|
|
83
|
-
status?: string;
|
|
84
83
|
evidence?: string;
|
|
85
|
-
work_summary?: string;
|
|
86
84
|
files_changed?: string[];
|
|
87
85
|
session_id?: string;
|
|
86
|
+
status?: string;
|
|
87
|
+
work_summary?: string;
|
|
88
88
|
},
|
|
89
89
|
projectRoot?: string,
|
|
90
90
|
): boolean {
|
|
@@ -99,6 +99,7 @@ export function updateTask(
|
|
|
99
99
|
task.completed_at = nowIso();
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
+
|
|
102
103
|
if (opts?.evidence) task.evidence = opts.evidence;
|
|
103
104
|
if (opts?.work_summary) task.work_summary = opts.work_summary;
|
|
104
105
|
if (opts?.files_changed !== undefined) task.files_changed = opts.files_changed;
|
|
@@ -167,12 +168,15 @@ export function generateTaskSummary(contextId: string, projectRoot?: string): st
|
|
|
167
168
|
const ws = t.work_summary ? `\n Work: ${t.work_summary}` : "";
|
|
168
169
|
lines.push(`- [x] ${t.id}: ${t.subject}${ws}`);
|
|
169
170
|
}
|
|
171
|
+
|
|
170
172
|
for (const t of inProgress) {
|
|
171
173
|
lines.push(`- [~] ${t.id}: ${t.subject}`);
|
|
172
174
|
}
|
|
175
|
+
|
|
173
176
|
for (const t of pending) {
|
|
174
177
|
lines.push(`- [ ] ${t.id}: ${t.subject}`);
|
|
175
178
|
}
|
|
179
|
+
|
|
176
180
|
for (const t of blocked) {
|
|
177
181
|
lines.push(`- [!] ${t.id}: ${t.subject}`);
|
|
178
182
|
}
|
|
@@ -6,17 +6,18 @@
|
|
|
6
6
|
* work to a new session (typically due to context window limits).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import * as crypto from "node:crypto";
|
|
9
10
|
import * as fs from "node:fs";
|
|
10
11
|
import * as path from "node:path";
|
|
11
|
-
|
|
12
|
-
import { getContextHandoffsDir, getContextDir } from "../base/constants.js";
|
|
12
|
+
|
|
13
13
|
import { atomicWrite } from "../base/atomic-write.js";
|
|
14
|
-
import {
|
|
14
|
+
import { getContextDir, getContextHandoffsDir } from "../base/constants.js";
|
|
15
|
+
import { logError, logInfo } from "../base/logger.js";
|
|
15
16
|
import { nowIso } from "../base/utils.js";
|
|
16
|
-
import { getContext, saveState } from "../context/context-store.js";
|
|
17
|
+
import { getContext, saveState as _saveState } from "../context/context-store.js";
|
|
17
18
|
import { getTasks } from "../context/task-tracker.js";
|
|
18
|
-
import {
|
|
19
|
-
import type { HandoffDocument, Task } from "../types.js";
|
|
19
|
+
import { formatContinuationHeader, formatReason, renderTaskList } from "../templates/formatters.js";
|
|
20
|
+
import type { HandoffDocument, Task as _Task } from "../types.js";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Generate and save a handoff document for a context.
|
|
@@ -124,8 +125,7 @@ function renderHandoffMarkdown(doc: HandoffDocument): string {
|
|
|
124
125
|
);
|
|
125
126
|
|
|
126
127
|
// Active tasks
|
|
127
|
-
lines.push(renderTaskList(doc.active_tasks, "Active Tasks", true).trimEnd());
|
|
128
|
-
lines.push("");
|
|
128
|
+
lines.push(renderTaskList(doc.active_tasks, "Active Tasks", true).trimEnd(), "");
|
|
129
129
|
|
|
130
130
|
// Completed this session
|
|
131
131
|
if (doc.completed_tasks_this_session.length > 0) {
|
|
@@ -133,8 +133,7 @@ function renderHandoffMarkdown(doc: HandoffDocument): string {
|
|
|
133
133
|
doc.completed_tasks_this_session as any[],
|
|
134
134
|
"Completed This Session",
|
|
135
135
|
false,
|
|
136
|
-
).trimEnd());
|
|
137
|
-
lines.push("");
|
|
136
|
+
).trimEnd(), "");
|
|
138
137
|
}
|
|
139
138
|
|
|
140
139
|
// Work summary
|
|
@@ -148,6 +147,7 @@ function renderHandoffMarkdown(doc: HandoffDocument): string {
|
|
|
148
147
|
for (let i = 0; i < doc.next_steps.length; i++) {
|
|
149
148
|
lines.push(`${i + 1}. ${doc.next_steps[i]}`);
|
|
150
149
|
}
|
|
150
|
+
|
|
151
151
|
lines.push("");
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -157,6 +157,7 @@ function renderHandoffMarkdown(doc: HandoffDocument): string {
|
|
|
157
157
|
for (const note of doc.important_notes) {
|
|
158
158
|
lines.push(`- ${note}`);
|
|
159
159
|
}
|
|
160
|
+
|
|
160
161
|
lines.push("");
|
|
161
162
|
}
|
|
162
163
|
|