aiwcli 0.12.6 → 0.12.8
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/dev.cmd +3 -3
- package/bin/dev.js +16 -16
- package/bin/run.cmd +3 -3
- package/bin/run.js +21 -21
- package/dist/commands/branch.js +7 -2
- package/dist/lib/bmad-installer.js +37 -37
- package/dist/lib/terminal.d.ts +2 -0
- package/dist/lib/terminal.js +57 -7
- package/dist/templates/CLAUDE.md +232 -205
- package/dist/templates/_shared/.claude/settings.json +65 -65
- package/dist/templates/_shared/.claude/{commands/handoff.md → skills/handoff/SKILL.md} +13 -12
- package/dist/templates/_shared/.claude/{commands/handoff-resume.md → skills/handoff-resume/SKILL.md} +13 -12
- package/dist/templates/_shared/.codex/workflows/handoff.md +226 -226
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +226 -226
- package/dist/templates/_shared/handoff-system/CLAUDE.md +15 -3
- package/dist/templates/_shared/handoff-system/lib/document-generator.ts +215 -215
- package/dist/templates/_shared/handoff-system/lib/handoff-reader.ts +158 -158
- package/dist/templates/_shared/handoff-system/scripts/resume_handoff.ts +373 -373
- package/dist/templates/_shared/handoff-system/scripts/save_handoff.ts +469 -469
- package/dist/templates/_shared/handoff-system/workflows/handoff-resume.md +66 -66
- package/dist/templates/_shared/handoff-system/workflows/handoff.md +254 -254
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -2
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -159
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -147
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +128 -128
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -49
- package/dist/templates/_shared/hooks-ts/session_end.ts +196 -196
- package/dist/templates/_shared/hooks-ts/session_start.ts +163 -163
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -48
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -74
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +93 -93
- package/dist/templates/_shared/lib-ts/CLAUDE.md +367 -367
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -138
- package/dist/templates/_shared/lib-ts/base/constants.ts +24 -6
- package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -58
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +582 -582
- package/dist/templates/_shared/lib-ts/base/inference.ts +301 -301
- package/dist/templates/_shared/lib-ts/base/logger.ts +247 -247
- package/dist/templates/_shared/lib-ts/base/state-io.ts +202 -202
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -184
- package/dist/templates/_shared/lib-ts/base/utils.ts +184 -184
- package/dist/templates/_shared/lib-ts/context/CLAUDE.md +134 -0
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +566 -566
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +524 -524
- package/dist/templates/_shared/lib-ts/context/context-store.ts +712 -712
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +312 -312
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -185
- package/dist/templates/_shared/lib-ts/package.json +20 -20
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +102 -102
- package/dist/templates/_shared/lib-ts/templates/plan-context.ts +58 -58
- package/dist/templates/_shared/lib-ts/tsconfig.json +13 -13
- package/dist/templates/_shared/lib-ts/types.ts +186 -186
- package/dist/templates/_shared/scripts/resolve_context.ts +33 -33
- package/dist/templates/_shared/scripts/status_line.ts +687 -690
- package/dist/templates/cc-native/.claude/commands/cc-native/rlm/ask.md +136 -136
- package/dist/templates/cc-native/.claude/commands/cc-native/rlm/index.md +21 -21
- package/dist/templates/cc-native/.claude/commands/cc-native/rlm/overview.md +56 -56
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -10
- package/dist/templates/cc-native/.claude/settings.json +3 -2
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -8
- package/dist/templates/cc-native/CC-NATIVE-README.md +189 -189
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +304 -304
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +143 -143
- package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +213 -213
- package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -70
- package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +64 -0
- package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/format.ts +1 -1
- package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/write.ts +2 -2
- package/dist/templates/cc-native/_cc-native/cc-native.config.json +96 -96
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +14 -24
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +1 -1
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +54 -54
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +51 -51
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -53
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -61
- package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +76 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +9 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +319 -319
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -144
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -57
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -83
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +79 -79
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +4 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -168
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +80 -80
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -41
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/CLAUDE.md +480 -480
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +287 -287
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +148 -148
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +54 -54
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +58 -58
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +208 -208
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +460 -460
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +446 -446
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +280 -280
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +274 -274
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +201 -201
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +278 -278
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +184 -184
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +275 -275
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -18
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +1 -1
- package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +149 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/CLAUDE.md +143 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/PLAN-ORCHESTRATOR.md +213 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-EVOLUTION.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-PATTERNS.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-STRUCTURE.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ASSUMPTION-TRACER.md +56 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CLARITY-AUDITOR.md +53 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-GAPS.md +70 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-ORDERING.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DEVILS-ADVOCATE.md +56 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HANDOFF-READINESS.md +59 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-DEPENDENCY.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-FMEA.md +66 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-PREMORTEM.md +71 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-REVERSIBILITY.md +74 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SCOPE-BOUNDARY.md +77 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SKEPTIC.md +68 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-COSTS.md +67 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-COVERAGE.md +74 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-STRENGTH.md +69 -0
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/agent-selection.ts +3 -3
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/corroboration.ts +1 -1
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/graduation.ts +1 -1
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/orchestrator.ts +2 -2
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/output-builder.ts +3 -3
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/plan-questions.ts +6 -6
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/review-pipeline.ts +15 -15
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/agent.ts +5 -5
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/base/base-agent.ts +4 -4
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/claude-agent.ts +4 -4
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/codex-agent.ts +6 -6
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/gemini-agent.ts +1 -1
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/orchestrator-claude-agent.ts +4 -4
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/types.ts +3 -3
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/verdict.ts +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +108 -108
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +0 -21
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +0 -3
- /package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/index.ts +0 -0
- /package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/tracker.ts +0 -0
- /package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/index.ts +0 -0
- /package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/schemas.ts +0 -0
- /package/dist/templates/cc-native/_cc-native/{workflows → plan-review/workflows}/specdev.md +0 -0
|
@@ -1,196 +1,196 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* SessionEnd hook: Save session state, assign plan fields (fallback),
|
|
4
|
-
* stage has_plan/has_handoff for next session.
|
|
5
|
-
*/
|
|
6
|
-
import * as fs from "node:fs";
|
|
7
|
-
import * as crypto from "node:crypto";
|
|
8
|
-
import * as path from "node:path";
|
|
9
|
-
import {
|
|
10
|
-
loadHookInput, runHook, logDebug, logInfo, logWarn, logError, logDiagnostic,
|
|
11
|
-
} from "../lib-ts/base/hook-utils.js";
|
|
12
|
-
import { getProjectRoot, getContextDir } from "../lib-ts/base/constants.js";
|
|
13
|
-
import { nowIso } from "../lib-ts/base/utils.js";
|
|
14
|
-
import { getContextBySessionId, saveState, determineArtifactType } from "../lib-ts/context/context-store.js";
|
|
15
|
-
import {
|
|
16
|
-
findLatestPlan, normalizePlanContent, generatePlanId, extractPlanAnchors,
|
|
17
|
-
} from "../lib-ts/context/plan-manager.js";
|
|
18
|
-
import { getGitState } from "../lib-ts/base/git-state.js";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Archive session transcript to context's session-transcripts/ folder.
|
|
22
|
-
* Returns archived path on success, null if skipped or failed.
|
|
23
|
-
*/
|
|
24
|
-
function archiveTranscript(
|
|
25
|
-
transcriptPath: string,
|
|
26
|
-
contextId: string,
|
|
27
|
-
sessionId: string,
|
|
28
|
-
projectRoot: string,
|
|
29
|
-
): string | null {
|
|
30
|
-
// 1. Validate inputs
|
|
31
|
-
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
|
32
|
-
logDebug("session_end", `Transcript not found: ${transcriptPath}`);
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// 2. Ensure session-transcripts directory exists
|
|
37
|
-
const contextDir = getContextDir(contextId, projectRoot);
|
|
38
|
-
const transcriptsDir = path.join(contextDir, "session-transcripts");
|
|
39
|
-
fs.mkdirSync(transcriptsDir, { recursive: true });
|
|
40
|
-
|
|
41
|
-
// 3. Generate archive filename: YYYY-MM-DD-HHMM-{session_id}.jsonl
|
|
42
|
-
const now = new Date();
|
|
43
|
-
// Format: 2026-02-14-1400 (year-month-day-hourminute)
|
|
44
|
-
// Note: Hours and minutes are concatenated without separator (HHMM)
|
|
45
|
-
const timestamp =
|
|
46
|
-
`${now.getFullYear()}-` +
|
|
47
|
-
`${String(now.getMonth() + 1).padStart(2, "0")}-` +
|
|
48
|
-
`${String(now.getDate()).padStart(2, "0")}-` +
|
|
49
|
-
`${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
50
|
-
|
|
51
|
-
// 4. Handle collisions (rare, but possible with rapid session churn)
|
|
52
|
-
let archiveName = `${timestamp}-${sessionId}.jsonl`;
|
|
53
|
-
let archivePath = path.join(transcriptsDir, archiveName);
|
|
54
|
-
let counter = 2;
|
|
55
|
-
while (fs.existsSync(archivePath)) {
|
|
56
|
-
archiveName = `${timestamp}-${sessionId}-${counter}.jsonl`;
|
|
57
|
-
archivePath = path.join(transcriptsDir, archiveName);
|
|
58
|
-
counter++;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 5. Copy transcript file
|
|
62
|
-
try {
|
|
63
|
-
fs.copyFileSync(transcriptPath, archivePath);
|
|
64
|
-
return archivePath;
|
|
65
|
-
} catch (e) {
|
|
66
|
-
logError("session_end", `Failed to copy transcript: ${e}`);
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function main(): void {
|
|
72
|
-
const payload = loadHookInput();
|
|
73
|
-
if (!payload) return;
|
|
74
|
-
|
|
75
|
-
const sessionId = payload.session_id;
|
|
76
|
-
if (!sessionId) {
|
|
77
|
-
logDebug("session_end", "No session_id, skipping");
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const projectRoot = getProjectRoot(payload.cwd);
|
|
82
|
-
const source = payload.source ?? "unknown";
|
|
83
|
-
const permissionMode = payload.permission_mode ?? "";
|
|
84
|
-
|
|
85
|
-
const state = getContextBySessionId(sessionId, projectRoot);
|
|
86
|
-
if (!state) {
|
|
87
|
-
logDebug("session_end", `No context bound to session ${sessionId}`);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Capture git state
|
|
92
|
-
const gitState = getGitState(projectRoot);
|
|
93
|
-
|
|
94
|
-
// Save session metadata
|
|
95
|
-
state.last_session = {
|
|
96
|
-
session_id: sessionId,
|
|
97
|
-
save_reason: source,
|
|
98
|
-
saved_at: nowIso(),
|
|
99
|
-
transcript_path: payload.transcript_path ?? undefined,
|
|
100
|
-
git_state: gitState,
|
|
101
|
-
};
|
|
102
|
-
state.last_active = nowIso();
|
|
103
|
-
|
|
104
|
-
// Archive transcript (NEW)
|
|
105
|
-
// Note: state is a ContextState object (from getContextBySessionId on line 33)
|
|
106
|
-
// state.id is the context ID used to construct paths like _output/contexts/{context_id}/
|
|
107
|
-
if (payload.transcript_path) {
|
|
108
|
-
try {
|
|
109
|
-
const archived = archiveTranscript(
|
|
110
|
-
payload.transcript_path,
|
|
111
|
-
state.id, // Context ID, verified by existing code on line 98: saveState(state.id, ...)
|
|
112
|
-
sessionId,
|
|
113
|
-
projectRoot,
|
|
114
|
-
);
|
|
115
|
-
if (archived) {
|
|
116
|
-
logInfo("session_end", `Archived transcript: ${path.basename(archived)}`);
|
|
117
|
-
}
|
|
118
|
-
} catch (e) {
|
|
119
|
-
logError("session_end", `Transcript archival failed: ${e}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// CRITICAL ORDER: New plan detection FIRST, then fallback assignment
|
|
124
|
-
// This prevents plan fallback from resurrecting old plan and clearing newer handoff
|
|
125
|
-
|
|
126
|
-
// New plan detection (reset consumed flag if hash changed)
|
|
127
|
-
// Handles both hash change AND first plan creation (plan_hash_consumed null)
|
|
128
|
-
if (
|
|
129
|
-
state.plan_hash &&
|
|
130
|
-
(!state.plan_hash_consumed || state.plan_hash !== state.plan_hash_consumed)
|
|
131
|
-
) {
|
|
132
|
-
logInfo("session_end", `New plan detected: hash=${state.plan_hash}`);
|
|
133
|
-
state.work_consumed = false; // CHANGED: unified flag
|
|
134
|
-
state.next_artifact_type = "plan";
|
|
135
|
-
|
|
136
|
-
// Latest artifact wins: clear handoff if it exists
|
|
137
|
-
if (state.handoff_path) {
|
|
138
|
-
logInfo("session_end", "New plan replaces existing handoff (latest wins)");
|
|
139
|
-
state.handoff_path = null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Plan fallback assignment AFTER new plan check
|
|
144
|
-
// Guard: Only assign fallback if no plan AND no handoff AND no next_artifact_type
|
|
145
|
-
// (don't overwrite handoff, don't run after new plan detection just set artifact type)
|
|
146
|
-
// Note: Removed permissionMode guard - fallback now runs in plan mode to fix staging bug
|
|
147
|
-
if (
|
|
148
|
-
!state.plan_hash &&
|
|
149
|
-
!state.handoff_path &&
|
|
150
|
-
!state.next_artifact_type
|
|
151
|
-
) {
|
|
152
|
-
const latestPlanPath = findLatestPlan(state.id, projectRoot);
|
|
153
|
-
if (latestPlanPath) {
|
|
154
|
-
try {
|
|
155
|
-
const content = fs.readFileSync(latestPlanPath, "utf-8");
|
|
156
|
-
const normalized = normalizePlanContent(content);
|
|
157
|
-
const planHash = crypto
|
|
158
|
-
.createHash("sha256")
|
|
159
|
-
.update(normalized, "utf-8")
|
|
160
|
-
.digest("hex")
|
|
161
|
-
.slice(0, 12);
|
|
162
|
-
|
|
163
|
-
state.plan_hash = planHash;
|
|
164
|
-
state.plan_path = latestPlanPath;
|
|
165
|
-
state.plan_signature = content.slice(0, 200);
|
|
166
|
-
state.plan_id = generatePlanId();
|
|
167
|
-
state.plan_anchors = extractPlanAnchors(content);
|
|
168
|
-
state.work_consumed = state.work_consumed ?? false; // CHANGED: unified flag
|
|
169
|
-
|
|
170
|
-
logInfo("session_end", `Assigned plan fallback: hash=${planHash}`);
|
|
171
|
-
} catch (e) {
|
|
172
|
-
logError("session_end", `Failed to read plan: ${e}`);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Unified staging logic (replaces separate plan/handoff checks)
|
|
178
|
-
const artifactType = determineArtifactType(state);
|
|
179
|
-
// Allow staging from active mode OR when session ends in plan mode (fixes plan mode staging bug)
|
|
180
|
-
const canStage = state.mode === "active" || permissionMode === "plan";
|
|
181
|
-
if (artifactType && canStage && !state.work_consumed) {
|
|
182
|
-
state.mode = "has_staged_work"; // CHANGED: unified mode
|
|
183
|
-
state.next_artifact_type = artifactType;
|
|
184
|
-
logInfo("session_end", `Staged ${state.id}: ${state.mode} → has_staged_work (${artifactType})`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Save final state
|
|
188
|
-
const [ok, err] = saveState(state.id, state, projectRoot);
|
|
189
|
-
if (ok) {
|
|
190
|
-
logDiagnostic("session_end", "saved", `${state.id} mode=${state.mode}`);
|
|
191
|
-
} else {
|
|
192
|
-
logError("session_end", `Failed to save state: ${err}`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
runHook(main, "session_end");
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* SessionEnd hook: Save session state, assign plan fields (fallback),
|
|
4
|
+
* stage has_plan/has_handoff for next session.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as crypto from "node:crypto";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import {
|
|
10
|
+
loadHookInput, runHook, logDebug, logInfo, logWarn, logError, logDiagnostic,
|
|
11
|
+
} from "../lib-ts/base/hook-utils.js";
|
|
12
|
+
import { getProjectRoot, getContextDir } from "../lib-ts/base/constants.js";
|
|
13
|
+
import { nowIso } from "../lib-ts/base/utils.js";
|
|
14
|
+
import { getContextBySessionId, saveState, determineArtifactType } from "../lib-ts/context/context-store.js";
|
|
15
|
+
import {
|
|
16
|
+
findLatestPlan, normalizePlanContent, generatePlanId, extractPlanAnchors,
|
|
17
|
+
} from "../lib-ts/context/plan-manager.js";
|
|
18
|
+
import { getGitState } from "../lib-ts/base/git-state.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Archive session transcript to context's session-transcripts/ folder.
|
|
22
|
+
* Returns archived path on success, null if skipped or failed.
|
|
23
|
+
*/
|
|
24
|
+
function archiveTranscript(
|
|
25
|
+
transcriptPath: string,
|
|
26
|
+
contextId: string,
|
|
27
|
+
sessionId: string,
|
|
28
|
+
projectRoot: string,
|
|
29
|
+
): string | null {
|
|
30
|
+
// 1. Validate inputs
|
|
31
|
+
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
|
32
|
+
logDebug("session_end", `Transcript not found: ${transcriptPath}`);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. Ensure session-transcripts directory exists
|
|
37
|
+
const contextDir = getContextDir(contextId, projectRoot);
|
|
38
|
+
const transcriptsDir = path.join(contextDir, "session-transcripts");
|
|
39
|
+
fs.mkdirSync(transcriptsDir, { recursive: true });
|
|
40
|
+
|
|
41
|
+
// 3. Generate archive filename: YYYY-MM-DD-HHMM-{session_id}.jsonl
|
|
42
|
+
const now = new Date();
|
|
43
|
+
// Format: 2026-02-14-1400 (year-month-day-hourminute)
|
|
44
|
+
// Note: Hours and minutes are concatenated without separator (HHMM)
|
|
45
|
+
const timestamp =
|
|
46
|
+
`${now.getFullYear()}-` +
|
|
47
|
+
`${String(now.getMonth() + 1).padStart(2, "0")}-` +
|
|
48
|
+
`${String(now.getDate()).padStart(2, "0")}-` +
|
|
49
|
+
`${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
50
|
+
|
|
51
|
+
// 4. Handle collisions (rare, but possible with rapid session churn)
|
|
52
|
+
let archiveName = `${timestamp}-${sessionId}.jsonl`;
|
|
53
|
+
let archivePath = path.join(transcriptsDir, archiveName);
|
|
54
|
+
let counter = 2;
|
|
55
|
+
while (fs.existsSync(archivePath)) {
|
|
56
|
+
archiveName = `${timestamp}-${sessionId}-${counter}.jsonl`;
|
|
57
|
+
archivePath = path.join(transcriptsDir, archiveName);
|
|
58
|
+
counter++;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 5. Copy transcript file
|
|
62
|
+
try {
|
|
63
|
+
fs.copyFileSync(transcriptPath, archivePath);
|
|
64
|
+
return archivePath;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
logError("session_end", `Failed to copy transcript: ${e}`);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function main(): void {
|
|
72
|
+
const payload = loadHookInput();
|
|
73
|
+
if (!payload) return;
|
|
74
|
+
|
|
75
|
+
const sessionId = payload.session_id;
|
|
76
|
+
if (!sessionId) {
|
|
77
|
+
logDebug("session_end", "No session_id, skipping");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const projectRoot = getProjectRoot(payload.cwd);
|
|
82
|
+
const source = payload.source ?? "unknown";
|
|
83
|
+
const permissionMode = payload.permission_mode ?? "";
|
|
84
|
+
|
|
85
|
+
const state = getContextBySessionId(sessionId, projectRoot);
|
|
86
|
+
if (!state) {
|
|
87
|
+
logDebug("session_end", `No context bound to session ${sessionId}`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Capture git state
|
|
92
|
+
const gitState = getGitState(projectRoot);
|
|
93
|
+
|
|
94
|
+
// Save session metadata
|
|
95
|
+
state.last_session = {
|
|
96
|
+
session_id: sessionId,
|
|
97
|
+
save_reason: source,
|
|
98
|
+
saved_at: nowIso(),
|
|
99
|
+
transcript_path: payload.transcript_path ?? undefined,
|
|
100
|
+
git_state: gitState,
|
|
101
|
+
};
|
|
102
|
+
state.last_active = nowIso();
|
|
103
|
+
|
|
104
|
+
// Archive transcript (NEW)
|
|
105
|
+
// Note: state is a ContextState object (from getContextBySessionId on line 33)
|
|
106
|
+
// state.id is the context ID used to construct paths like _output/contexts/{context_id}/
|
|
107
|
+
if (payload.transcript_path) {
|
|
108
|
+
try {
|
|
109
|
+
const archived = archiveTranscript(
|
|
110
|
+
payload.transcript_path,
|
|
111
|
+
state.id, // Context ID, verified by existing code on line 98: saveState(state.id, ...)
|
|
112
|
+
sessionId,
|
|
113
|
+
projectRoot,
|
|
114
|
+
);
|
|
115
|
+
if (archived) {
|
|
116
|
+
logInfo("session_end", `Archived transcript: ${path.basename(archived)}`);
|
|
117
|
+
}
|
|
118
|
+
} catch (e) {
|
|
119
|
+
logError("session_end", `Transcript archival failed: ${e}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// CRITICAL ORDER: New plan detection FIRST, then fallback assignment
|
|
124
|
+
// This prevents plan fallback from resurrecting old plan and clearing newer handoff
|
|
125
|
+
|
|
126
|
+
// New plan detection (reset consumed flag if hash changed)
|
|
127
|
+
// Handles both hash change AND first plan creation (plan_hash_consumed null)
|
|
128
|
+
if (
|
|
129
|
+
state.plan_hash &&
|
|
130
|
+
(!state.plan_hash_consumed || state.plan_hash !== state.plan_hash_consumed)
|
|
131
|
+
) {
|
|
132
|
+
logInfo("session_end", `New plan detected: hash=${state.plan_hash}`);
|
|
133
|
+
state.work_consumed = false; // CHANGED: unified flag
|
|
134
|
+
state.next_artifact_type = "plan";
|
|
135
|
+
|
|
136
|
+
// Latest artifact wins: clear handoff if it exists
|
|
137
|
+
if (state.handoff_path) {
|
|
138
|
+
logInfo("session_end", "New plan replaces existing handoff (latest wins)");
|
|
139
|
+
state.handoff_path = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Plan fallback assignment AFTER new plan check
|
|
144
|
+
// Guard: Only assign fallback if no plan AND no handoff AND no next_artifact_type
|
|
145
|
+
// (don't overwrite handoff, don't run after new plan detection just set artifact type)
|
|
146
|
+
// Note: Removed permissionMode guard - fallback now runs in plan mode to fix staging bug
|
|
147
|
+
if (
|
|
148
|
+
!state.plan_hash &&
|
|
149
|
+
!state.handoff_path &&
|
|
150
|
+
!state.next_artifact_type
|
|
151
|
+
) {
|
|
152
|
+
const latestPlanPath = findLatestPlan(state.id, projectRoot);
|
|
153
|
+
if (latestPlanPath) {
|
|
154
|
+
try {
|
|
155
|
+
const content = fs.readFileSync(latestPlanPath, "utf-8");
|
|
156
|
+
const normalized = normalizePlanContent(content);
|
|
157
|
+
const planHash = crypto
|
|
158
|
+
.createHash("sha256")
|
|
159
|
+
.update(normalized, "utf-8")
|
|
160
|
+
.digest("hex")
|
|
161
|
+
.slice(0, 12);
|
|
162
|
+
|
|
163
|
+
state.plan_hash = planHash;
|
|
164
|
+
state.plan_path = latestPlanPath;
|
|
165
|
+
state.plan_signature = content.slice(0, 200);
|
|
166
|
+
state.plan_id = generatePlanId();
|
|
167
|
+
state.plan_anchors = extractPlanAnchors(content);
|
|
168
|
+
state.work_consumed = state.work_consumed ?? false; // CHANGED: unified flag
|
|
169
|
+
|
|
170
|
+
logInfo("session_end", `Assigned plan fallback: hash=${planHash}`);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
logError("session_end", `Failed to read plan: ${e}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Unified staging logic (replaces separate plan/handoff checks)
|
|
178
|
+
const artifactType = determineArtifactType(state);
|
|
179
|
+
// Allow staging from active mode OR when session ends in plan mode (fixes plan mode staging bug)
|
|
180
|
+
const canStage = state.mode === "active" || permissionMode === "plan";
|
|
181
|
+
if (artifactType && canStage && !state.work_consumed) {
|
|
182
|
+
state.mode = "has_staged_work"; // CHANGED: unified mode
|
|
183
|
+
state.next_artifact_type = artifactType;
|
|
184
|
+
logInfo("session_end", `Staged ${state.id}: ${state.mode} → has_staged_work (${artifactType})`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Save final state
|
|
188
|
+
const [ok, err] = saveState(state.id, state, projectRoot);
|
|
189
|
+
if (ok) {
|
|
190
|
+
logDiagnostic("session_end", "saved", `${state.id} mode=${state.mode}`);
|
|
191
|
+
} else {
|
|
192
|
+
logError("session_end", `Failed to save state: ${err}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
runHook(main, "session_end");
|