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
|
@@ -14,29 +14,30 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import * as crypto from "node:crypto";
|
|
17
|
-
|
|
18
|
-
getContext,
|
|
19
|
-
getAllContexts,
|
|
20
|
-
getContextBySessionId,
|
|
21
|
-
createContextFromPrompt,
|
|
22
|
-
createContext,
|
|
23
|
-
completeContext,
|
|
24
|
-
bindSession,
|
|
25
|
-
updateMode,
|
|
26
|
-
} from "./context-store.js";
|
|
17
|
+
|
|
27
18
|
import {
|
|
28
19
|
formatActiveContextReminder,
|
|
20
|
+
formatActiveContinuation as _formatActiveContinuation,
|
|
21
|
+
formatCommandFeedback,
|
|
29
22
|
formatContextCreated,
|
|
30
23
|
formatContextPickerStderr,
|
|
31
|
-
formatCommandFeedback,
|
|
32
24
|
formatHandoffContinuation,
|
|
33
25
|
formatPlanContinuation,
|
|
34
|
-
formatActiveContinuation,
|
|
35
26
|
} from "./context-formatter.js";
|
|
27
|
+
import {
|
|
28
|
+
bindSession,
|
|
29
|
+
completeContext,
|
|
30
|
+
createContext,
|
|
31
|
+
createContextFromPrompt,
|
|
32
|
+
getAllContexts,
|
|
33
|
+
getContext,
|
|
34
|
+
getContextBySessionId,
|
|
35
|
+
updateMode,
|
|
36
|
+
} from "./context-store.js";
|
|
36
37
|
import { normalizePlanContent } from "./plan-manager.js";
|
|
38
|
+
import { logDebug, logError, logInfo, logWarn as _logWarn } from "../base/logger.js";
|
|
37
39
|
import { isInternalCall } from "../base/subprocess-utils.js";
|
|
38
|
-
import {
|
|
39
|
-
import type { ContextState, CaretCommand } from "../types.js";
|
|
40
|
+
import type { CaretCommand, ContextState } from "../types.js";
|
|
40
41
|
|
|
41
42
|
/** Minimum characters required for new context description. */
|
|
42
43
|
const MIN_NEW_CONTEXT_CHARS = 10;
|
|
@@ -65,7 +66,7 @@ export class BlockRequest extends Error {
|
|
|
65
66
|
export function resolveContextByPrefix(
|
|
66
67
|
query: string,
|
|
67
68
|
contexts: ContextState[],
|
|
68
|
-
): [
|
|
69
|
+
): [null | number, null | string] {
|
|
69
70
|
const q = query.toLowerCase();
|
|
70
71
|
const available = contexts.map(c => c.id).join(", ");
|
|
71
72
|
|
|
@@ -107,7 +108,7 @@ export function resolveContextByPrefix(
|
|
|
107
108
|
export function parseChainedCaret(
|
|
108
109
|
prompt: string,
|
|
109
110
|
contexts: ContextState[],
|
|
110
|
-
): [CaretCommand | null,
|
|
111
|
+
): [CaretCommand | null, null | string] {
|
|
111
112
|
if (!prompt.startsWith("^")) return [null, null];
|
|
112
113
|
|
|
113
114
|
const match = prompt.match(/^\^(\S+)(?:\s+(.*))?$/s);
|
|
@@ -120,7 +121,7 @@ export function parseChainedCaret(
|
|
|
120
121
|
|
|
121
122
|
// ^N shorthand
|
|
122
123
|
if (/^\d+$/.test(commandStr)) {
|
|
123
|
-
const num = parseInt(commandStr, 10);
|
|
124
|
+
const num = Number.parseInt(commandStr, 10);
|
|
124
125
|
if (num === 0) {
|
|
125
126
|
if (remaining.length < MIN_NEW_CONTEXT_CHARS) {
|
|
126
127
|
return [null,
|
|
@@ -130,21 +131,25 @@ export function parseChainedCaret(
|
|
|
130
131
|
`Example: ^0 implement user authentication with JWT tokens`,
|
|
131
132
|
];
|
|
132
133
|
}
|
|
134
|
+
|
|
133
135
|
return [{ ends: [], select: null, new_context_desc: remaining, remaining_prompt: "" }, null];
|
|
134
136
|
}
|
|
137
|
+
|
|
135
138
|
if (num < 1 || num > contexts.length) {
|
|
136
139
|
if (contexts.length === 0) {
|
|
137
140
|
return [null, "No existing contexts. Use ^0 <description> to create a new one."];
|
|
138
141
|
}
|
|
142
|
+
|
|
139
143
|
return [null, `Invalid selection. Choose 1-${contexts.length} for existing contexts, or ^0 for new.`];
|
|
140
144
|
}
|
|
145
|
+
|
|
141
146
|
const ctx = contexts[num - 1]!;
|
|
142
147
|
return [{ ends: [], select: ctx.id, new_context_desc: null, remaining_prompt: remaining }, null];
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
// Parse chained commands
|
|
146
151
|
const ends: string[] = [];
|
|
147
|
-
let select:
|
|
152
|
+
let select: null | string = null;
|
|
148
153
|
let pos = 0;
|
|
149
154
|
|
|
150
155
|
while (pos < commandStr.length) {
|
|
@@ -174,11 +179,13 @@ export function parseChainedCaret(
|
|
|
174
179
|
if (numStart === pos) {
|
|
175
180
|
return [null, `Expected number, '*', or ':prefix' after 'E' at position ${numStart + 1}`];
|
|
176
181
|
}
|
|
177
|
-
|
|
182
|
+
|
|
183
|
+
const num = Number.parseInt(commandStr.slice(numStart, pos), 10);
|
|
178
184
|
if (num < 1 || num > contexts.length) {
|
|
179
185
|
if (contexts.length === 0) return [null, "No contexts to end."];
|
|
180
186
|
return [null, `Context ^E${num} invalid. Choose 1-${contexts.length}.`];
|
|
181
187
|
}
|
|
188
|
+
|
|
182
189
|
if (pos < commandStr.length && commandStr[pos] === "+") {
|
|
183
190
|
pos++;
|
|
184
191
|
for (let i = num; i <= contexts.length; i++) {
|
|
@@ -208,13 +215,16 @@ export function parseChainedCaret(
|
|
|
208
215
|
if (numStart === pos) {
|
|
209
216
|
return [null, `Expected number or ':prefix' after 'S' at position ${numStart + 1}`];
|
|
210
217
|
}
|
|
211
|
-
|
|
218
|
+
|
|
219
|
+
const num = Number.parseInt(commandStr.slice(numStart, pos), 10);
|
|
212
220
|
if (num < 1 || num > contexts.length) {
|
|
213
221
|
if (contexts.length === 0) return [null, "No contexts to select."];
|
|
214
222
|
return [null, `Context ^S${num} invalid. Choose 1-${contexts.length}.`];
|
|
215
223
|
}
|
|
224
|
+
|
|
216
225
|
ctx = contexts[num - 1]!;
|
|
217
226
|
}
|
|
227
|
+
|
|
218
228
|
if (select === null) select = ctx.id;
|
|
219
229
|
} else {
|
|
220
230
|
return [null,
|
|
@@ -274,9 +284,9 @@ function matchPlanContent(prompt: string, hasPlanContexts: ContextState[]): Cont
|
|
|
274
284
|
}
|
|
275
285
|
|
|
276
286
|
// Tier 4 (legacy): Signature match
|
|
277
|
-
const promptHead = prompt.slice(0, 500);
|
|
287
|
+
const promptHead = new Set(prompt.slice(0, 500));
|
|
278
288
|
for (const ctx of hasPlanContexts) {
|
|
279
|
-
if (ctx.plan_signature && promptHead.
|
|
289
|
+
if (ctx.plan_signature && promptHead.has(ctx.plan_signature)) {
|
|
280
290
|
logDebug("context_selector", `Tier 4 legacy signature match: ${ctx.id}`);
|
|
281
291
|
return ctx;
|
|
282
292
|
}
|
|
@@ -292,15 +302,15 @@ function matchPlanContent(prompt: string, hasPlanContexts: ContextState[]): Cont
|
|
|
292
302
|
function createNewContext(
|
|
293
303
|
prompt: string,
|
|
294
304
|
projectRoot?: string,
|
|
295
|
-
): [
|
|
305
|
+
): [null | string, string, null | string] {
|
|
296
306
|
try {
|
|
297
307
|
const newCtx = createContextFromPrompt(prompt, projectRoot);
|
|
298
308
|
updateMode(newCtx.id, "active", projectRoot);
|
|
299
309
|
newCtx.mode = "active";
|
|
300
310
|
logInfo("context_selector", `Auto-created context: ${newCtx.id}`);
|
|
301
311
|
return [newCtx.id, "auto_created", formatContextCreated(newCtx)];
|
|
302
|
-
} catch (
|
|
303
|
-
logError("context_selector", `Primary context creation failed: ${
|
|
312
|
+
} catch (error: any) {
|
|
313
|
+
logError("context_selector", `Primary context creation failed: ${error}`);
|
|
304
314
|
try {
|
|
305
315
|
const now = new Date();
|
|
306
316
|
const yy = String(now.getFullYear()).slice(2);
|
|
@@ -320,8 +330,8 @@ function createNewContext(
|
|
|
320
330
|
newCtx.mode = "active";
|
|
321
331
|
logInfo("context_selector", `Fallback context created: ${newCtx.id}`);
|
|
322
332
|
return [newCtx.id, "auto_created_fallback", formatContextCreated(newCtx)];
|
|
323
|
-
} catch (
|
|
324
|
-
logError("context_selector", `ALL context creation failed: ${
|
|
333
|
+
} catch (error: any) {
|
|
334
|
+
logError("context_selector", `ALL context creation failed: ${error}`);
|
|
325
335
|
return [null, "creation_failed", null];
|
|
326
336
|
}
|
|
327
337
|
}
|
|
@@ -335,7 +345,7 @@ function handleCaretCommand(
|
|
|
335
345
|
prompt: string,
|
|
336
346
|
contexts: ContextState[],
|
|
337
347
|
projectRoot?: string,
|
|
338
|
-
): [
|
|
348
|
+
): [null | string, string, null | string] {
|
|
339
349
|
if (contexts.length === 0) {
|
|
340
350
|
const match = prompt.match(/^\^(\S+)(?:\s+(.*))?$/s);
|
|
341
351
|
if (!match) {
|
|
@@ -344,14 +354,16 @@ function handleCaretCommand(
|
|
|
344
354
|
"Example: ^0 implement user authentication system",
|
|
345
355
|
);
|
|
346
356
|
}
|
|
357
|
+
|
|
347
358
|
const prefixValue = match[1]!;
|
|
348
359
|
const remaining = match[2] ?? "";
|
|
349
|
-
if (!/^\d+$/.test(prefixValue) || parseInt(prefixValue, 10) !== 0) {
|
|
360
|
+
if (!/^\d+$/.test(prefixValue) || Number.parseInt(prefixValue, 10) !== 0) {
|
|
350
361
|
throw new BlockRequest(
|
|
351
362
|
"No existing contexts to select. Use ^0 <description> to create a new context.\n" +
|
|
352
363
|
"Example: ^0 implement user authentication system",
|
|
353
364
|
);
|
|
354
365
|
}
|
|
366
|
+
|
|
355
367
|
const description = remaining.trim();
|
|
356
368
|
if (description.length < MIN_NEW_CONTEXT_CHARS) {
|
|
357
369
|
throw new BlockRequest(
|
|
@@ -361,6 +373,7 @@ function handleCaretCommand(
|
|
|
361
373
|
`Example: ^0 implement user authentication with JWT tokens`,
|
|
362
374
|
);
|
|
363
375
|
}
|
|
376
|
+
|
|
364
377
|
return createNewContext(description, projectRoot);
|
|
365
378
|
}
|
|
366
379
|
|
|
@@ -374,6 +387,7 @@ function handleCaretCommand(
|
|
|
374
387
|
if (!ctxToEnd) {
|
|
375
388
|
throw new BlockRequest(`Context '${ctxId}' no longer exists.\n` + formatContextPickerStderr(contexts));
|
|
376
389
|
}
|
|
390
|
+
|
|
377
391
|
completeContext(ctxToEnd.id, projectRoot);
|
|
378
392
|
endedContexts.push(ctxToEnd);
|
|
379
393
|
logInfo("context_selector", `Ended context: ${ctxToEnd.id}`);
|
|
@@ -384,9 +398,10 @@ function handleCaretCommand(
|
|
|
384
398
|
if (ctxId && endedContexts.length > 0) {
|
|
385
399
|
const newCtx = getContext(ctxId, projectRoot);
|
|
386
400
|
const feedback = formatCommandFeedback(endedContexts, newCtx);
|
|
387
|
-
return [ctxId, method
|
|
401
|
+
return [ctxId, method === "creation_failed" ? method : "caret_new", feedback];
|
|
388
402
|
}
|
|
389
|
-
|
|
403
|
+
|
|
404
|
+
return [ctxId, method === "creation_failed" ? method : "caret_new", output];
|
|
390
405
|
}
|
|
391
406
|
|
|
392
407
|
if (cmd.select) {
|
|
@@ -394,6 +409,7 @@ function handleCaretCommand(
|
|
|
394
409
|
if (!selectedCtx) {
|
|
395
410
|
throw new BlockRequest(`Context '${cmd.select}' no longer exists.\n` + formatContextPickerStderr(contexts));
|
|
396
411
|
}
|
|
412
|
+
|
|
397
413
|
logInfo("context_selector", `Caret-selected context: ${selectedCtx.id}`);
|
|
398
414
|
return [selectedCtx.id, "caret_select", formatCommandFeedback(endedContexts, selectedCtx)];
|
|
399
415
|
}
|
|
@@ -408,6 +424,7 @@ function handleCaretCommand(
|
|
|
408
424
|
"Example: implement user authentication system",
|
|
409
425
|
);
|
|
410
426
|
}
|
|
427
|
+
|
|
411
428
|
throw new BlockRequest(
|
|
412
429
|
feedback + "\nNo context selected.\n\nSelect a context to continue:\n" +
|
|
413
430
|
formatContextPickerStderr(remainingContexts),
|
|
@@ -432,7 +449,7 @@ export function determineContext(
|
|
|
432
449
|
prompt: string,
|
|
433
450
|
sessionId?: string,
|
|
434
451
|
projectRoot?: string,
|
|
435
|
-
): [
|
|
452
|
+
): [null | string, string, null | string] {
|
|
436
453
|
if (isInternalCall()) {
|
|
437
454
|
logDebug("context_selector", "Skipping: internal subprocess call");
|
|
438
455
|
return [null, "skip_internal", null];
|
|
@@ -460,6 +477,7 @@ export function determineContext(
|
|
|
460
477
|
"Example: implement user authentication system",
|
|
461
478
|
);
|
|
462
479
|
}
|
|
480
|
+
|
|
463
481
|
throw new BlockRequest(formatContextPickerStderr(contexts));
|
|
464
482
|
}
|
|
465
483
|
|
|
@@ -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
|
}
|
|
@@ -13,8 +13,8 @@ import * as path from "node:path";
|
|
|
13
13
|
import * as crypto from "node:crypto";
|
|
14
14
|
import { getContextDir, getContextPlansDir, sanitizeTitle } from "../base/constants.js";
|
|
15
15
|
import { atomicWrite } from "../base/atomic-write.js";
|
|
16
|
-
import { readStateJson } from "../base/state-io.js";
|
|
17
16
|
import { logDebug, logInfo, logWarn, logError } from "../base/logger.js";
|
|
17
|
+
import { generateSlug } from "../base/utils.js";
|
|
18
18
|
import type { ContextState } from "../types.js";
|
|
19
19
|
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
@@ -30,11 +30,11 @@ import type { ContextState } from "../types.js";
|
|
|
30
30
|
* Returns [archivedPath, planHash, planSignature] on success,
|
|
31
31
|
* or [null, null, null] on error.
|
|
32
32
|
*/
|
|
33
|
-
export
|
|
33
|
+
export function archivePlan(
|
|
34
34
|
planPath: string,
|
|
35
35
|
contextId: string,
|
|
36
36
|
projectRoot?: string,
|
|
37
|
-
):
|
|
37
|
+
): [string | null, string | null, string | null] {
|
|
38
38
|
if (!fs.existsSync(planPath)) {
|
|
39
39
|
logWarn("plan_manager", `Plan file not found: ${planPath}`);
|
|
40
40
|
return [null, null, null];
|
|
@@ -69,23 +69,12 @@ export async function archivePlan(
|
|
|
69
69
|
String(now.getMinutes()).padStart(2, "0"),
|
|
70
70
|
].join("");
|
|
71
71
|
|
|
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
|
-
}
|
|
72
|
+
// Extract a clean summary from plan content for slug generation.
|
|
73
|
+
// Headings describe the plan's intent better than raw markdown body.
|
|
74
|
+
const summary = extractPlanSummary(content);
|
|
75
|
+
const slug = generateSlug(summary, 60, sanitizeTitle(
|
|
76
|
+
path.basename(planPath, path.extname(planPath)), 30,
|
|
77
|
+
));
|
|
89
78
|
|
|
90
79
|
let archiveName = `${dateStr}-${slug}.md`;
|
|
91
80
|
let archivePath = path.join(plansDir, archiveName);
|
|
@@ -109,6 +98,35 @@ export async function archivePlan(
|
|
|
109
98
|
return [archivePath, planHash, planSignature];
|
|
110
99
|
}
|
|
111
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Extract a human-readable summary from plan markdown content.
|
|
103
|
+
* Pulls headings and the first substantial paragraph, producing
|
|
104
|
+
* text suitable for the AI slug generator (which expects conversational input).
|
|
105
|
+
*/
|
|
106
|
+
function extractPlanSummary(content: string): string {
|
|
107
|
+
const lines = content.split(/\r?\n/);
|
|
108
|
+
const parts: string[] = [];
|
|
109
|
+
let firstParagraph = "";
|
|
110
|
+
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
const trimmed = line.trim();
|
|
113
|
+
// Collect markdown headings (strip # prefix)
|
|
114
|
+
if (trimmed.startsWith("#")) {
|
|
115
|
+
const heading = trimmed.replace(/^#+\s*/, "");
|
|
116
|
+
if (heading.length > 2) parts.push(heading);
|
|
117
|
+
}
|
|
118
|
+
// Grab first substantial non-heading line as context
|
|
119
|
+
if (!firstParagraph && !trimmed.startsWith("#") && trimmed.length > 20) {
|
|
120
|
+
firstParagraph = trimmed.slice(0, 120);
|
|
121
|
+
}
|
|
122
|
+
// Enough material for the AI
|
|
123
|
+
if (parts.length >= 5) break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (firstParagraph) parts.push(firstParagraph);
|
|
127
|
+
return parts.join(" ").slice(0, 500) || content.slice(0, 500);
|
|
128
|
+
}
|
|
129
|
+
|
|
112
130
|
// ---------------------------------------------------------------------------
|
|
113
131
|
// Plan lookup
|
|
114
132
|
// ---------------------------------------------------------------------------
|
|
@@ -124,7 +142,9 @@ export function findLatestPlan(
|
|
|
124
142
|
): string | null {
|
|
125
143
|
// 1. Check state.json plan_path first
|
|
126
144
|
try {
|
|
127
|
-
|
|
145
|
+
// Dynamic import to avoid circular dependency at module level
|
|
146
|
+
const stateIo = require("../base/state-io.js");
|
|
147
|
+
const state = stateIo.readStateJson(contextId, projectRoot);
|
|
128
148
|
if (state?.plan_path && fs.existsSync(state.plan_path)) {
|
|
129
149
|
return state.plan_path;
|
|
130
150
|
}
|
|
@@ -183,7 +203,7 @@ export function normalizePlanContent(text: string): string {
|
|
|
183
203
|
*/
|
|
184
204
|
export function extractPlanAnchors(content: string, maxAnchors = 5): string[] {
|
|
185
205
|
const anchors: string[] = [];
|
|
186
|
-
for (const line of content.split(
|
|
206
|
+
for (const line of content.split(/\r?\n/)) {
|
|
187
207
|
const trimmed = line.trim();
|
|
188
208
|
if (trimmed.startsWith("#") && trimmed.length > 3) {
|
|
189
209
|
anchors.push(trimmed.slice(0, 80));
|
|
@@ -228,7 +248,7 @@ export function findPlanPathInTranscript(transcriptPath: string): string | null
|
|
|
228
248
|
|
|
229
249
|
let lines: string[];
|
|
230
250
|
try {
|
|
231
|
-
lines = fs.readFileSync(transcriptPath, "utf-8").split(
|
|
251
|
+
lines = fs.readFileSync(transcriptPath, "utf-8").split(/\r?\n/);
|
|
232
252
|
} catch (e: any) {
|
|
233
253
|
logWarn("plan_manager", `Failed to read transcript: ${e}`);
|
|
234
254
|
return null;
|