aiwcli 0.10.1 → 0.10.3
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/dist/commands/clean.js +1 -0
- package/dist/commands/clear.d.ts +19 -2
- package/dist/commands/clear.js +351 -160
- package/dist/commands/init/index.d.ts +1 -17
- package/dist/commands/init/index.js +19 -104
- package/dist/lib/gitignore-manager.d.ts +9 -0
- package/dist/lib/gitignore-manager.js +121 -0
- package/dist/lib/template-installer.d.ts +7 -12
- package/dist/lib/template-installer.js +69 -193
- package/dist/lib/template-settings-reconstructor.d.ts +35 -0
- package/dist/lib/template-settings-reconstructor.js +130 -0
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +10 -2
- package/dist/templates/_shared/hooks/session_end.py +37 -29
- 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__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/hook_utils.py +8 -10
- package/dist/templates/_shared/lib/base/inference.py +51 -62
- package/dist/templates/_shared/lib/base/logger.py +35 -21
- package/dist/templates/_shared/lib/base/stop_words.py +8 -0
- package/dist/templates/_shared/lib/base/utils.py +29 -8
- package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/plan_manager.py +101 -2
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -0
- package/dist/templates/_shared/lib-ts/base/constants.ts +299 -0
- package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -0
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +360 -0
- package/dist/templates/_shared/lib-ts/base/inference.ts +245 -0
- package/dist/templates/_shared/lib-ts/base/logger.ts +234 -0
- package/dist/templates/_shared/lib-ts/base/state-io.ts +114 -0
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -0
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +23 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +184 -0
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +432 -0
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +497 -0
- package/dist/templates/_shared/lib-ts/context/context-store.ts +679 -0
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +292 -0
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +181 -0
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +215 -0
- package/dist/templates/_shared/lib-ts/package.json +21 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +102 -0
- package/dist/templates/_shared/lib-ts/templates/plan-context.ts +65 -0
- package/dist/templates/_shared/lib-ts/tsconfig.json +13 -0
- package/dist/templates/_shared/lib-ts/types.ts +151 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +359 -0
- package/dist/templates/_shared/scripts/status_line.py +17 -2
- package/dist/templates/cc-native/_cc-native/agents/ARCH-EVOLUTION.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/ARCH-PATTERNS.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/ARCH-STRUCTURE.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-CHAIN-TRACER.md → ASSUMPTION-TRACER.md} +6 -10
- package/dist/templates/cc-native/_cc-native/agents/CLARITY-AUDITOR.md +6 -10
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +74 -1
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-FEASIBILITY.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-GAPS.md +71 -0
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-ORDERING.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/CONSTRAINT-VALIDATOR.md +73 -0
- package/dist/templates/cc-native/_cc-native/agents/DESIGN-ADR-VALIDATOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/DESIGN-SCALE-MATCHER.md +65 -0
- package/dist/templates/cc-native/_cc-native/agents/DEVILS-ADVOCATE.md +6 -9
- package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-PHILOSOPHY.md +87 -0
- package/dist/templates/cc-native/_cc-native/agents/HANDOFF-READINESS.md +5 -9
- package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY-DETECTOR.md → HIDDEN-COMPLEXITY.md} +6 -10
- package/dist/templates/cc-native/_cc-native/agents/INCREMENTAL-DELIVERY.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +91 -18
- package/dist/templates/cc-native/_cc-native/agents/RISK-DEPENDENCY.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-FMEA.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-PREMORTEM.md +72 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-REVERSIBILITY.md +75 -0
- package/dist/templates/cc-native/_cc-native/agents/SCOPE-BOUNDARY.md +78 -0
- package/dist/templates/cc-native/_cc-native/agents/SIMPLICITY-GUARDIAN.md +5 -9
- package/dist/templates/cc-native/_cc-native/agents/SKEPTIC.md +16 -12
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-BEHAVIOR-AUDITOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-CHARACTERIZATION.md +72 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-FIRST-VALIDATOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-PYRAMID-ANALYZER.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-COSTS.md +68 -0
- package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-STAKEHOLDERS.md +66 -0
- package/dist/templates/cc-native/_cc-native/agents/VERIFY-COVERAGE.md +75 -0
- package/dist/templates/cc-native/_cc-native/agents/VERIFY-STRENGTH.md +70 -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/cc-native-plan-review.py +125 -40
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/utils.py +57 -13
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +11 -7
- package/oclif.manifest.json +17 -2
- package/package.json +1 -1
- package/dist/lib/template-merger.d.ts +0 -47
- package/dist/lib/template-merger.js +0 -162
- package/dist/templates/cc-native/_cc-native/agents/ACCESSIBILITY-TESTER.md +0 -79
- package/dist/templates/cc-native/_cc-native/agents/ARCHITECT-REVIEWER.md +0 -48
- package/dist/templates/cc-native/_cc-native/agents/CODE-REVIEWER.md +0 -70
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-CHECKER.md +0 -59
- package/dist/templates/cc-native/_cc-native/agents/CONTEXT-EXTRACTOR.md +0 -92
- package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-REVIEWER.md +0 -51
- package/dist/templates/cc-native/_cc-native/agents/FEASIBILITY-ANALYST.md +0 -57
- package/dist/templates/cc-native/_cc-native/agents/FRESH-PERSPECTIVE.md +0 -54
- package/dist/templates/cc-native/_cc-native/agents/INCENTIVE-MAPPER.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/PENETRATION-TESTER.md +0 -79
- package/dist/templates/cc-native/_cc-native/agents/PERFORMANCE-ENGINEER.md +0 -75
- package/dist/templates/cc-native/_cc-native/agents/PRECEDENT-FINDER.md +0 -70
- package/dist/templates/cc-native/_cc-native/agents/REVERSIBILITY-ANALYST.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/RISK-ASSESSOR.md +0 -58
- package/dist/templates/cc-native/_cc-native/agents/SECOND-ORDER-ANALYST.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/STAKEHOLDER-ADVOCATE.md +0 -55
- package/dist/templates/cc-native/_cc-native/agents/TRADE-OFF-ILLUMINATOR.md +0 -204
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatters for context management templates.
|
|
3
|
+
* Constants and helpers for consistent formatting across hooks and display.
|
|
4
|
+
* See SPEC.md §13
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Task } from "../types.js";
|
|
8
|
+
|
|
9
|
+
// §13.1 — Legacy mode display mapping (used by handoff/document_generator)
|
|
10
|
+
export const LEGACY_MODE_MAP: Record<string, string> = {
|
|
11
|
+
planning: "[Planning]",
|
|
12
|
+
pending_implementation: "[Plan Ready]",
|
|
13
|
+
implementing: "[Implementing]",
|
|
14
|
+
none: "",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// §13.2 — Status icon mapping
|
|
18
|
+
export const STATUS_ICONS: Record<string, string> = {
|
|
19
|
+
pending: "⬜",
|
|
20
|
+
in_progress: "🔄",
|
|
21
|
+
blocked: "🚫",
|
|
22
|
+
completed: "✅",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function getModeDisplay(mode: string): string {
|
|
26
|
+
return LEGACY_MODE_MAP[mode] ?? "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getStatusIcon(status: string): string {
|
|
30
|
+
return STATUS_ICONS[status] ?? "⬜";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// §13.3 — Task rendering
|
|
34
|
+
export function renderTaskItem(
|
|
35
|
+
task: Task | Record<string, any>,
|
|
36
|
+
showDescription = true,
|
|
37
|
+
maxDescriptionLength = 100,
|
|
38
|
+
): string {
|
|
39
|
+
const status = (task as any).status ?? "pending";
|
|
40
|
+
const subject = (task as any).subject ?? "";
|
|
41
|
+
const description = (task as any).description ?? "";
|
|
42
|
+
|
|
43
|
+
const icon = getStatusIcon(status);
|
|
44
|
+
const statusText = `[${status.toUpperCase()}]`;
|
|
45
|
+
const line = `- ${icon} ${statusText} ${subject}`;
|
|
46
|
+
|
|
47
|
+
if (showDescription && description) {
|
|
48
|
+
let truncated = description.slice(0, maxDescriptionLength);
|
|
49
|
+
if (description.length > maxDescriptionLength) {
|
|
50
|
+
truncated += "...";
|
|
51
|
+
}
|
|
52
|
+
return `${line}\n - ${truncated}`;
|
|
53
|
+
}
|
|
54
|
+
return line;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function renderTaskList(
|
|
58
|
+
tasks: Array<Task | Record<string, any>>,
|
|
59
|
+
header = "Active Tasks",
|
|
60
|
+
showDescription = true,
|
|
61
|
+
): string {
|
|
62
|
+
const lines = [`### ${header}`, ""];
|
|
63
|
+
|
|
64
|
+
if (tasks.length === 0) {
|
|
65
|
+
lines.push("No active tasks.");
|
|
66
|
+
} else {
|
|
67
|
+
for (const task of tasks) {
|
|
68
|
+
lines.push(renderTaskItem(task, showDescription));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
lines.push("");
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// §13.4 — Continuation headers
|
|
77
|
+
const CONTINUATION_HEADERS: Record<string, (id: string) => string> = {
|
|
78
|
+
context: (id) => `## CONTINUING CONTEXT: ${id}`,
|
|
79
|
+
resuming: (id) => `## RESUMING FROM HANDOFF: ${id}`,
|
|
80
|
+
implementing: (id) => `## CONTINUING IMPLEMENTATION: ${id}`,
|
|
81
|
+
handoff: (id) => `# Session Handoff: ${id}`,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export function formatContinuationHeader(
|
|
85
|
+
headerType: string,
|
|
86
|
+
contextId: string,
|
|
87
|
+
): string {
|
|
88
|
+
const fn = CONTINUATION_HEADERS[headerType];
|
|
89
|
+
return fn ? fn(contextId) : `## ${contextId}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// §13.5 — Reason formatting
|
|
93
|
+
export const REASON_MAP: Record<string, string> = {
|
|
94
|
+
low_context: "Context window running low",
|
|
95
|
+
user_requested: "User requested handoff",
|
|
96
|
+
error_recovery: "Error recovery",
|
|
97
|
+
session_end: "Session ending",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export function formatReason(reason: string): string {
|
|
101
|
+
return REASON_MAP[reason] ?? reason;
|
|
102
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan context templates for add_plan_context hook.
|
|
3
|
+
* See SPEC.md §13.6
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function getEvaluationContextReminder(): string {
|
|
7
|
+
return `## CRITICAL: Write This Plan for a Different Agent
|
|
8
|
+
|
|
9
|
+
The agent executing this plan has ZERO context from this conversation — no chat history, no memory of files you explored or research you did.
|
|
10
|
+
|
|
11
|
+
**Write as if YOU are that agent. What would you need?**
|
|
12
|
+
|
|
13
|
+
### Required Structure
|
|
14
|
+
|
|
15
|
+
\`\`\`
|
|
16
|
+
# Plan: <descriptive title>
|
|
17
|
+
|
|
18
|
+
## Background
|
|
19
|
+
Why this change is needed (2-3 sentences)
|
|
20
|
+
|
|
21
|
+
## Task
|
|
22
|
+
What exactly to build/change
|
|
23
|
+
|
|
24
|
+
## Files
|
|
25
|
+
**Modify:**
|
|
26
|
+
- \`exact/path/to/file.py\` - What changes (reference line numbers or patterns)
|
|
27
|
+
|
|
28
|
+
**Reference:**
|
|
29
|
+
- \`exact/path/to/reference.py\` - Why relevant (e.g., "pattern to follow at lines 12-30")
|
|
30
|
+
|
|
31
|
+
## Steps
|
|
32
|
+
1. [Specific steps with function names, patterns, or code snippets]
|
|
33
|
+
2. [Enough detail for someone who never saw this conversation]
|
|
34
|
+
|
|
35
|
+
## Constraints
|
|
36
|
+
- Technical requirements, preferences, or limitations
|
|
37
|
+
|
|
38
|
+
## Documentation
|
|
39
|
+
Decisions not written down are lost when this session ends. Update the nearest CLAUDE.md and MEMORY.md so the next session inherits what you learned.
|
|
40
|
+
|
|
41
|
+
**CLAUDE.md** (nearest to changed code — cascades to subdirectories):
|
|
42
|
+
- \`exact/path/to/CLAUDE.md\` — What to document
|
|
43
|
+
|
|
44
|
+
**What to write:**
|
|
45
|
+
- Architectural choices and why alternatives were rejected
|
|
46
|
+
- Non-obvious constraints (what breaks if this changes)
|
|
47
|
+
- Workarounds with context on the underlying issue
|
|
48
|
+
- Patterns that prevent future mistakes
|
|
49
|
+
|
|
50
|
+
**Format:** \`## Topic\` / \`**Decision:** ...\` / \`**Rationale:** ...\`
|
|
51
|
+
|
|
52
|
+
**MEMORY.md** (cross-session learning for the AI agent):
|
|
53
|
+
- Insight that would prevent a future mistake (e.g., "hook X silently drops field Y")
|
|
54
|
+
|
|
55
|
+
**Include when:** Architectural decisions, non-obvious constraints, workarounds, or patterns discovered during implementation.
|
|
56
|
+
**Omit entries for:** Routine changes with no decisions (rename, formatting, dependency bump).
|
|
57
|
+
When in doubt, write it — a lean entry is better than a lost decision.
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
### Self-Check
|
|
61
|
+
- [ ] Could I execute this if I forgot our entire conversation?
|
|
62
|
+
- [ ] Are file paths exact (not "the auth file")?
|
|
63
|
+
- [ ] Are implementation details specific (not "use the approach we discussed")?
|
|
64
|
+
- [ ] Do documentation entries capture decisions the next session would otherwise lose?`;
|
|
65
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for the lib-ts library.
|
|
3
|
+
* All field names use snake_case for JSON backward compatibility with state.json.
|
|
4
|
+
* See SPEC.md §1 for full behavioral specification.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// §1.1
|
|
8
|
+
export type Mode = "idle" | "has_plan" | "has_handoff" | "active";
|
|
9
|
+
|
|
10
|
+
export interface ContextState {
|
|
11
|
+
id: string;
|
|
12
|
+
status: "active" | "completed";
|
|
13
|
+
summary: string;
|
|
14
|
+
method: string;
|
|
15
|
+
tags: string[];
|
|
16
|
+
created_at: string;
|
|
17
|
+
last_active: string;
|
|
18
|
+
mode: Mode;
|
|
19
|
+
plan_path: string | null;
|
|
20
|
+
plan_hash: string | null;
|
|
21
|
+
plan_signature: string | null;
|
|
22
|
+
plan_id: string | null;
|
|
23
|
+
plan_anchors: string[];
|
|
24
|
+
plan_consumed: boolean;
|
|
25
|
+
handoff_path: string | null;
|
|
26
|
+
handoff_consumed: boolean;
|
|
27
|
+
session_ids: string[];
|
|
28
|
+
last_session: LastSession | null;
|
|
29
|
+
tasks: Task[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// §1.2
|
|
33
|
+
export interface GitState {
|
|
34
|
+
branch?: string;
|
|
35
|
+
uncommitted_files?: string[];
|
|
36
|
+
last_commit_short?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface LastSession {
|
|
40
|
+
session_id?: string;
|
|
41
|
+
saved_at?: string;
|
|
42
|
+
save_reason?: string;
|
|
43
|
+
transcript_path?: string;
|
|
44
|
+
context_remaining_pct?: number;
|
|
45
|
+
git_state?: GitState;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// §1.3
|
|
49
|
+
export interface Task {
|
|
50
|
+
id: string;
|
|
51
|
+
subject: string;
|
|
52
|
+
description: string;
|
|
53
|
+
active_form: string;
|
|
54
|
+
status: "pending" | "in_progress" | "completed" | "blocked";
|
|
55
|
+
created_at: string;
|
|
56
|
+
completed_at: string | null;
|
|
57
|
+
evidence: string;
|
|
58
|
+
work_summary: string;
|
|
59
|
+
files_changed: string[];
|
|
60
|
+
session_id?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// §1.4
|
|
64
|
+
export interface IndexEntry {
|
|
65
|
+
summary: string;
|
|
66
|
+
mode: string;
|
|
67
|
+
last_active: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface IndexFile {
|
|
71
|
+
version: "3.0";
|
|
72
|
+
updated_at: string;
|
|
73
|
+
sessions: Record<string, string>;
|
|
74
|
+
contexts: Record<string, IndexEntry>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// §1.5
|
|
78
|
+
export interface LogEntry {
|
|
79
|
+
ts: string;
|
|
80
|
+
level: "debug" | "info" | "warn" | "error";
|
|
81
|
+
hook: string;
|
|
82
|
+
msg: string;
|
|
83
|
+
component?: string;
|
|
84
|
+
data?: any;
|
|
85
|
+
tb?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// §1.6
|
|
89
|
+
export interface HookInput {
|
|
90
|
+
hook_event_name: string;
|
|
91
|
+
tool_name?: string;
|
|
92
|
+
tool_input?: Record<string, any>;
|
|
93
|
+
tool_result?: string;
|
|
94
|
+
session_id?: string;
|
|
95
|
+
cwd?: string;
|
|
96
|
+
transcript_path?: string;
|
|
97
|
+
context_window?: {
|
|
98
|
+
current_usage?: {
|
|
99
|
+
cache_read_input_tokens?: number;
|
|
100
|
+
input_tokens?: number;
|
|
101
|
+
cache_creation_input_tokens?: number;
|
|
102
|
+
output_tokens?: number;
|
|
103
|
+
};
|
|
104
|
+
context_window_size?: number;
|
|
105
|
+
};
|
|
106
|
+
permission_mode?: string;
|
|
107
|
+
source?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// §1.7
|
|
111
|
+
export interface HookOutput {
|
|
112
|
+
hookSpecificOutput?: {
|
|
113
|
+
additionalContext?: string;
|
|
114
|
+
permissionDecision?: "allow" | "deny";
|
|
115
|
+
permissionDecisionReason?: string;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// §1.8
|
|
120
|
+
export interface InferenceResult {
|
|
121
|
+
success: boolean;
|
|
122
|
+
output: string;
|
|
123
|
+
error?: string;
|
|
124
|
+
latency_ms: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// §1.9
|
|
128
|
+
export interface HandoffDocument {
|
|
129
|
+
context_id: string;
|
|
130
|
+
context_summary: string;
|
|
131
|
+
session_id: string;
|
|
132
|
+
reason: string;
|
|
133
|
+
created_at: string;
|
|
134
|
+
plan_path: string | null;
|
|
135
|
+
context_folder: string;
|
|
136
|
+
events_log_path: string;
|
|
137
|
+
active_tasks: Task[];
|
|
138
|
+
completed_tasks_this_session: Array<{ subject: string }>;
|
|
139
|
+
work_summary: string;
|
|
140
|
+
next_steps: string[];
|
|
141
|
+
important_notes: string[];
|
|
142
|
+
file_path: string | null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// §1.10
|
|
146
|
+
export interface CaretCommand {
|
|
147
|
+
ends: string[];
|
|
148
|
+
select: string | null;
|
|
149
|
+
new_context_desc: string | null;
|
|
150
|
+
remaining_prompt: string;
|
|
151
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Save a handoff document with folder-based sharding.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* bun .aiwcli/_shared/scripts/save_handoff.ts <context_id> <<'EOF'
|
|
7
|
+
* # Your handoff markdown content here (with <!-- SECTION: name --> markers)
|
|
8
|
+
* EOF
|
|
9
|
+
*
|
|
10
|
+
* Or with a file:
|
|
11
|
+
* bun .aiwcli/_shared/scripts/save_handoff.ts <context_id> < handoff.md
|
|
12
|
+
*
|
|
13
|
+
* This script:
|
|
14
|
+
* 1. Parses sections from incoming markdown using <!-- SECTION: name --> markers
|
|
15
|
+
* 2. Creates a timestamped folder at _output/contexts/{context_id}/handoffs/{YYYY-MM-DD-HHMM}/
|
|
16
|
+
* 3. Writes sharded files:
|
|
17
|
+
* - index.md (main entry point with navigation)
|
|
18
|
+
* - completed-work.md, dead-ends.md, decisions.md, pending.md, context.md
|
|
19
|
+
* - plan.md (copy of original plan if it exists)
|
|
20
|
+
* 4. Sets handoff_path and handoff_consumed=false in state.json
|
|
21
|
+
*/
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import * as path from "node:path";
|
|
24
|
+
|
|
25
|
+
import { getContext, saveState } from "../lib-ts/context/context-store.js";
|
|
26
|
+
import { getHandoffFolderPath, getProjectRoot } from "../lib-ts/base/constants.js";
|
|
27
|
+
import { atomicWrite } from "../lib-ts/base/atomic-write.js";
|
|
28
|
+
import { logInfo, logWarn, logError } from "../lib-ts/base/logger.js";
|
|
29
|
+
import { getGitStatusShort } from "../lib-ts/base/git-state.js";
|
|
30
|
+
import { eprint } from "../lib-ts/base/utils.js";
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Parsing helpers
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
function parseFrontmatter(content: string): [Record<string, string>, string] {
|
|
37
|
+
const frontmatter: Record<string, string> = {};
|
|
38
|
+
let remaining = content;
|
|
39
|
+
|
|
40
|
+
if (content.startsWith("---")) {
|
|
41
|
+
const parts = content.split("---", 3);
|
|
42
|
+
if (parts.length >= 3) {
|
|
43
|
+
for (const line of parts[1]!.trim().split("\n")) {
|
|
44
|
+
const colonIdx = line.indexOf(":");
|
|
45
|
+
if (colonIdx !== -1) {
|
|
46
|
+
const key = line.slice(0, colonIdx).trim();
|
|
47
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
48
|
+
frontmatter[key] = value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
remaining = parts[2]!.trim();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return [frontmatter, remaining];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseHandoffSections(content: string): Record<string, string> {
|
|
59
|
+
const sections: Record<string, string> = {};
|
|
60
|
+
let currentSection: string | null = null;
|
|
61
|
+
const currentContent: string[] = [];
|
|
62
|
+
|
|
63
|
+
for (const line of content.split("\n")) {
|
|
64
|
+
const marker = line.trim().match(/<!-- SECTION:\s*(\S+)\s*-->/);
|
|
65
|
+
if (marker) {
|
|
66
|
+
if (currentSection) {
|
|
67
|
+
sections[currentSection] = currentContent.join("\n").trim();
|
|
68
|
+
}
|
|
69
|
+
currentSection = marker[1]!;
|
|
70
|
+
currentContent.length = 0;
|
|
71
|
+
} else if (currentSection) {
|
|
72
|
+
currentContent.push(line);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (currentSection) {
|
|
77
|
+
sections[currentSection] = currentContent.join("\n").trim();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return sections;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Plan helper
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
function getPlanPathFromContext(contextId: string, projectRoot: string): string | null {
|
|
88
|
+
const context = getContext(contextId, projectRoot);
|
|
89
|
+
if (!context?.plan_path) return null;
|
|
90
|
+
try {
|
|
91
|
+
if (fs.existsSync(context.plan_path)) return context.plan_path;
|
|
92
|
+
} catch { /* ignore */ }
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// File generation
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
function generateIndex(
|
|
101
|
+
frontmatter: Record<string, string>,
|
|
102
|
+
sections: Record<string, string>,
|
|
103
|
+
gitStatus: string,
|
|
104
|
+
hasPlan: boolean,
|
|
105
|
+
): string {
|
|
106
|
+
const now = new Date();
|
|
107
|
+
const isoStr = now.toISOString();
|
|
108
|
+
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
109
|
+
|
|
110
|
+
const lines: string[] = [
|
|
111
|
+
"---",
|
|
112
|
+
"type: handoff",
|
|
113
|
+
`context_id: ${frontmatter["context_id"] ?? "unknown"}`,
|
|
114
|
+
`created_at: ${isoStr}`,
|
|
115
|
+
`session_id: ${frontmatter["session_id"] ?? "unknown"}`,
|
|
116
|
+
`project: ${frontmatter["project"] ?? "unknown"}`,
|
|
117
|
+
`plan_path: ${frontmatter["plan_document"] ?? "none"}`,
|
|
118
|
+
"---",
|
|
119
|
+
"",
|
|
120
|
+
`# Session Handoff - ${dateStr}`,
|
|
121
|
+
"",
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
// Summary
|
|
125
|
+
const summary = (sections["summary"] ?? "").trim();
|
|
126
|
+
if (summary) {
|
|
127
|
+
const summaryText = summary
|
|
128
|
+
.split("\n")
|
|
129
|
+
.filter(l => !l.trim().startsWith("##"))
|
|
130
|
+
.join("\n")
|
|
131
|
+
.trim();
|
|
132
|
+
lines.push("## Summary", summaryText, "");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Navigation
|
|
136
|
+
lines.push(
|
|
137
|
+
"## Quick Navigation",
|
|
138
|
+
"",
|
|
139
|
+
"| Document | Purpose | Priority |",
|
|
140
|
+
"|----------|---------|----------|",
|
|
141
|
+
"| [Dead Ends](./dead-ends.md) | Failed approaches - DO NOT RETRY | Read First |",
|
|
142
|
+
"| [Pending](./pending.md) | Next steps and blockers | Action Items |",
|
|
143
|
+
"| [Completed Work](./completed-work.md) | Tasks finished this session | Reference |",
|
|
144
|
+
"| [Decisions](./decisions.md) | Technical choices and rationale | Reference |",
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (hasPlan) {
|
|
148
|
+
lines.push("| [Plan](./plan.md) | Original plan being implemented | Reference |");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
lines.push(
|
|
152
|
+
"| [Context](./context.md) | External requirements and notes | Reference |",
|
|
153
|
+
"",
|
|
154
|
+
"## Continuation Instructions",
|
|
155
|
+
"",
|
|
156
|
+
"To continue this work in a new session:",
|
|
157
|
+
"1. This index document provides the overview",
|
|
158
|
+
"2. **Read [Dead Ends](./dead-ends.md) first** to avoid repeating failed approaches",
|
|
159
|
+
"3. Check [Pending](./pending.md) for immediate next steps",
|
|
160
|
+
"4. Reference other documents as needed",
|
|
161
|
+
"",
|
|
162
|
+
"## Git Status at Handoff",
|
|
163
|
+
"```",
|
|
164
|
+
gitStatus,
|
|
165
|
+
"```",
|
|
166
|
+
"",
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return lines.join("\n");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function writeSectionFile(folder: string, filename: string, title: string, content: string): boolean {
|
|
173
|
+
const text = `# ${title}\n\n${content || "(No content for this section)"}\n`;
|
|
174
|
+
const filePath = path.join(folder, filename);
|
|
175
|
+
const [success, error] = atomicWrite(filePath, text);
|
|
176
|
+
if (!success) {
|
|
177
|
+
logWarn("save_handoff", `Failed to write ${filename}: ${error}`);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Main
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
function main(): void {
|
|
188
|
+
if (process.argv.length < 3) {
|
|
189
|
+
eprint(
|
|
190
|
+
"Usage: bun save_handoff.ts <context_id> < content.md\n" +
|
|
191
|
+
" bun save_handoff.ts <context_id> <<'EOF'\n" +
|
|
192
|
+
" ... markdown content with <!-- SECTION: name --> markers ...\n" +
|
|
193
|
+
" EOF",
|
|
194
|
+
);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const contextId = process.argv[2]!;
|
|
199
|
+
|
|
200
|
+
// Read content from stdin
|
|
201
|
+
let content: string;
|
|
202
|
+
try {
|
|
203
|
+
content = fs.readFileSync(0, "utf-8");
|
|
204
|
+
} catch {
|
|
205
|
+
logError("save_handoff", "Failed to read from stdin");
|
|
206
|
+
process.exit(1);
|
|
207
|
+
return; // unreachable but makes TS happy
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!content.trim()) {
|
|
211
|
+
logError("save_handoff", "No content provided via stdin");
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Project root via shared utility (checks CLAUDE_PROJECT_DIR, falls back to cwd)
|
|
216
|
+
const projectRoot = getProjectRoot(process.cwd());
|
|
217
|
+
|
|
218
|
+
// Verify context exists
|
|
219
|
+
const context = getContext(contextId, projectRoot);
|
|
220
|
+
if (!context) {
|
|
221
|
+
logError("save_handoff", `Context not found: ${contextId}`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Parse frontmatter and sections
|
|
226
|
+
const [frontmatter, body] = parseFrontmatter(content);
|
|
227
|
+
const sections = parseHandoffSections(body);
|
|
228
|
+
|
|
229
|
+
logInfo("save_handoff", `Parsed ${Object.keys(sections).length} sections: ${Object.keys(sections).join(", ")}`);
|
|
230
|
+
|
|
231
|
+
// Create handoff folder
|
|
232
|
+
const handoffFolder = getHandoffFolderPath(contextId, projectRoot);
|
|
233
|
+
fs.mkdirSync(handoffFolder, { recursive: true });
|
|
234
|
+
logInfo("save_handoff", `Created folder: ${handoffFolder}`);
|
|
235
|
+
|
|
236
|
+
// Git status
|
|
237
|
+
const gitStatus = getGitStatusShort(projectRoot);
|
|
238
|
+
|
|
239
|
+
// Check for plan
|
|
240
|
+
const planPath = getPlanPathFromContext(contextId, projectRoot);
|
|
241
|
+
const hasPlan = planPath !== null;
|
|
242
|
+
|
|
243
|
+
// Copy plan if exists
|
|
244
|
+
if (planPath) {
|
|
245
|
+
try {
|
|
246
|
+
const planContent = fs.readFileSync(planPath, "utf-8");
|
|
247
|
+
const [success, error] = atomicWrite(path.join(handoffFolder, "plan.md"), planContent);
|
|
248
|
+
if (success) {
|
|
249
|
+
logInfo("save_handoff", `Copied plan from ${planPath}`);
|
|
250
|
+
} else {
|
|
251
|
+
logWarn("save_handoff", `Failed to copy plan: ${error}`);
|
|
252
|
+
}
|
|
253
|
+
} catch (e) {
|
|
254
|
+
logWarn("save_handoff", `Failed to read plan: ${e}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Write index.md
|
|
259
|
+
const indexContent = generateIndex(frontmatter, sections, gitStatus, hasPlan);
|
|
260
|
+
const indexPath = path.join(handoffFolder, "index.md");
|
|
261
|
+
{
|
|
262
|
+
const [success, error] = atomicWrite(indexPath, indexContent);
|
|
263
|
+
if (!success) {
|
|
264
|
+
logError("save_handoff", `Failed to write index.md: ${error}`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Write section files
|
|
270
|
+
const sectionMapping: Record<string, [string, string | null]> = {
|
|
271
|
+
completed: ["completed-work.md", "Work Completed"],
|
|
272
|
+
"dead-ends": ["dead-ends.md", "Dead Ends - Do Not Retry"],
|
|
273
|
+
decisions: ["decisions.md", "Key Decisions"],
|
|
274
|
+
pending: ["pending.md", "Pending Issues"],
|
|
275
|
+
"next-steps": ["pending.md", null], // Append to pending.md
|
|
276
|
+
files: ["completed-work.md", null], // Append to completed-work.md
|
|
277
|
+
context: ["context.md", "Context for Future Sessions"],
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Track accumulated content per file
|
|
281
|
+
const fileContents: Record<string, string[]> = {};
|
|
282
|
+
|
|
283
|
+
for (const [sectionName, [filename, title]] of Object.entries(sectionMapping)) {
|
|
284
|
+
const sectionContent = sections[sectionName];
|
|
285
|
+
if (!sectionContent) continue;
|
|
286
|
+
|
|
287
|
+
if (title === null) {
|
|
288
|
+
// Append mode
|
|
289
|
+
if (!fileContents[filename]) fileContents[filename] = [];
|
|
290
|
+
fileContents[filename]!.push(sectionContent);
|
|
291
|
+
} else {
|
|
292
|
+
// Write mode with title
|
|
293
|
+
if (!fileContents[filename]) {
|
|
294
|
+
fileContents[filename] = [`# ${title}`, "", sectionContent];
|
|
295
|
+
} else {
|
|
296
|
+
fileContents[filename] = [`# ${title}`, "", ...fileContents[filename]!, "", sectionContent];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Write all accumulated content
|
|
302
|
+
for (const [filename, parts] of Object.entries(fileContents)) {
|
|
303
|
+
const filePath = path.join(handoffFolder, filename);
|
|
304
|
+
const [success, error] = atomicWrite(filePath, parts.join("\n") + "\n");
|
|
305
|
+
if (!success) {
|
|
306
|
+
logWarn("save_handoff", `Failed to write ${filename}: ${error}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Ensure all expected files exist (even if empty)
|
|
311
|
+
const expectedFiles: Record<string, string> = {
|
|
312
|
+
"completed-work.md": "Work Completed",
|
|
313
|
+
"dead-ends.md": "Dead Ends - Do Not Retry",
|
|
314
|
+
"decisions.md": "Key Decisions",
|
|
315
|
+
"pending.md": "Pending Issues & Next Steps",
|
|
316
|
+
"context.md": "Context for Future Sessions",
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
for (const [filename, title] of Object.entries(expectedFiles)) {
|
|
320
|
+
const filePath = path.join(handoffFolder, filename);
|
|
321
|
+
if (!fs.existsSync(filePath)) {
|
|
322
|
+
writeSectionFile(handoffFolder, filename, title, "");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Set handoff_path and handoff_consumed=false in state.json
|
|
327
|
+
try {
|
|
328
|
+
const indexPathStr = path.join(handoffFolder, "index.md");
|
|
329
|
+
const state = getContext(contextId, projectRoot);
|
|
330
|
+
if (state) {
|
|
331
|
+
state.handoff_path = indexPathStr;
|
|
332
|
+
state.handoff_consumed = false;
|
|
333
|
+
const [ok, err] = saveState(contextId, state, projectRoot);
|
|
334
|
+
if (ok) {
|
|
335
|
+
logInfo("save_handoff", `Set handoff_path: ${indexPathStr}`);
|
|
336
|
+
} else {
|
|
337
|
+
logWarn("save_handoff", `Failed to save state: ${err}`);
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
logWarn("save_handoff", `Could not load context state for ${contextId}`);
|
|
341
|
+
}
|
|
342
|
+
} catch (e) {
|
|
343
|
+
logWarn("save_handoff", `Handoff saved but auto-resume won't work (context update failed): ${e}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Output success message
|
|
347
|
+
console.log(`[OK] Created handoff folder: ${handoffFolder}`);
|
|
348
|
+
console.log(" - index.md (entry point with navigation)");
|
|
349
|
+
|
|
350
|
+
const filesCreated = fs.readdirSync(handoffFolder)
|
|
351
|
+
.filter(f => f !== "index.md" && fs.statSync(path.join(handoffFolder, f)).isFile())
|
|
352
|
+
.sort();
|
|
353
|
+
console.log(` - ${filesCreated.join(", ")}`);
|
|
354
|
+
|
|
355
|
+
console.log("");
|
|
356
|
+
console.log("Handoff document saved. Use this folder for context in the next session.");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
main();
|