aiwcli 0.12.1 → 0.12.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/templates/_shared/.claude/commands/handoff.md +44 -78
- package/dist/templates/_shared/hooks-ts/session_end.ts +16 -11
- package/dist/templates/_shared/hooks-ts/session_start.ts +25 -16
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +20 -8
- package/dist/templates/_shared/lib-ts/base/inference.ts +72 -23
- package/dist/templates/_shared/lib-ts/base/state-io.ts +12 -7
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +151 -29
- package/dist/templates/_shared/lib-ts/context/context-store.ts +35 -74
- package/dist/templates/_shared/lib-ts/types.ts +64 -63
- package/dist/templates/_shared/scripts/resolve_context.ts +14 -5
- package/dist/templates/_shared/scripts/resume_handoff.ts +41 -13
- package/dist/templates/_shared/scripts/save_handoff.ts +30 -31
- package/dist/templates/_shared/workflows/handoff.md +28 -6
- package/dist/templates/cc-native/.claude/commands/rlm/ask.md +136 -0
- package/dist/templates/cc-native/.claude/commands/rlm/index.md +21 -0
- package/dist/templates/cc-native/.claude/commands/rlm/overview.md +56 -0
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +4 -4
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +1 -7
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-EVOLUTION.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-PATTERNS.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-STRUCTURE.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ASSUMPTION-TRACER.md +56 -57
- package/dist/templates/cc-native/_cc-native/agents/plan-review/CLARITY-AUDITOR.md +53 -54
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-GAPS.md +70 -71
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-ORDERING.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -73
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -65
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DEVILS-ADVOCATE.md +56 -57
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -87
- package/dist/templates/cc-native/_cc-native/agents/plan-review/HANDOFF-READINESS.md +59 -60
- package/dist/templates/cc-native/_cc-native/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -59
- package/dist/templates/cc-native/_cc-native/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-DEPENDENCY.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-FMEA.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-PREMORTEM.md +71 -72
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-REVERSIBILITY.md +74 -75
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SCOPE-BOUNDARY.md +77 -78
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SKEPTIC.md +68 -69
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -72
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TRADEOFF-COSTS.md +67 -68
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -66
- package/dist/templates/cc-native/_cc-native/agents/plan-review/VERIFY-COVERAGE.md +74 -75
- package/dist/templates/cc-native/_cc-native/agents/plan-review/VERIFY-STRENGTH.md +69 -70
- package/dist/templates/cc-native/_cc-native/{plan-review.config.json → cc-native.config.json} +12 -0
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +19 -2
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +28 -1010
- package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +1 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/format.ts +597 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/index.ts +26 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/tracker.ts +107 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/write.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +19 -821
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +36 -13
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +3 -3
- package/dist/templates/cc-native/_cc-native/lib-ts/graduation.ts +132 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +1 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/output-builder.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/review-pipeline.ts +511 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/CLAUDE.md +480 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +287 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +148 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +54 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +58 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +208 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +460 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +447 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +280 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +274 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +201 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +278 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +184 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +51 -17
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +42 -3
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,20 @@ Generate a handoff document summarizing the current session's work, decisions, a
|
|
|
8
8
|
- `/handoff path/to/PLAN.md` - with plan document integration
|
|
9
9
|
- Phrases like "write a handoff", "create a session summary", "document what we did", "end session with notes"
|
|
10
10
|
|
|
11
|
+
## Philosophy
|
|
12
|
+
|
|
13
|
+
Handoffs exist to minimize the token and time cost of session startup. A precise handoff lets the next session hit the ground running without rediscovering context through Explore agents or file searches.
|
|
14
|
+
|
|
15
|
+
**Think: "What would I need to continue this work with zero prior context?"**
|
|
16
|
+
|
|
17
|
+
Key principles:
|
|
18
|
+
- **Absolute paths over vague references** — `C:\project\src\auth\handler.ts:45` not "handler.ts line 45"
|
|
19
|
+
- **Specific locations over general areas** — Name the function/class/section, not just the file
|
|
20
|
+
- **Enough detail to skip discovery** — Include enough context that the next session doesn't need to grep or search
|
|
21
|
+
- **Self-contained decisions** — Capture the "why" and the "why not" so alternatives aren't re-explored
|
|
22
|
+
|
|
23
|
+
A good handoff pays for itself in the first minute of the next session.
|
|
24
|
+
|
|
11
25
|
## Arguments
|
|
12
26
|
|
|
13
27
|
- `$ARGUMENTS` - Optional path to a plan document. If provided, the handoff will:
|
|
@@ -17,19 +31,7 @@ Generate a handoff document summarizing the current session's work, decisions, a
|
|
|
17
31
|
|
|
18
32
|
## Process
|
|
19
33
|
|
|
20
|
-
### Step 1:
|
|
21
|
-
|
|
22
|
-
Resolve the active context ID programmatically:
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
bun .aiwcli/_shared/scripts/resolve_context.ts
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
This prints the active context ID to stdout. Use its output as `{context_id}` in subsequent steps.
|
|
29
|
-
|
|
30
|
-
If the script exits with an error (no active context found), inform the user and stop — handoffs require an active context.
|
|
31
|
-
|
|
32
|
-
### Step 2: Gather Information
|
|
34
|
+
### Step 1: Gather Information
|
|
33
35
|
|
|
34
36
|
1. Review conversation history for:
|
|
35
37
|
- Completed tasks and implementations
|
|
@@ -37,21 +39,15 @@ If the script exits with an error (no active context found), inform the user and
|
|
|
37
39
|
- Failed approaches (to avoid repeating)
|
|
38
40
|
- External context (deadlines, stakeholder requirements)
|
|
39
41
|
|
|
40
|
-
2.
|
|
41
|
-
```bash
|
|
42
|
-
git status --short
|
|
43
|
-
git diff --stat
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
3. Look for TODOs/FIXMEs mentioned in session
|
|
42
|
+
2. Look for TODOs/FIXMEs mentioned in session
|
|
47
43
|
|
|
48
|
-
|
|
44
|
+
3. **If plan document provided**: Read the plan and identify:
|
|
49
45
|
- Tasks that are now completed
|
|
50
46
|
- Tasks that are partially done
|
|
51
47
|
- Tasks that were attempted but blocked
|
|
52
48
|
- New tasks discovered during implementation
|
|
53
49
|
|
|
54
|
-
### Step
|
|
50
|
+
### Step 2: Generate Document
|
|
55
51
|
|
|
56
52
|
Use this template. The `<!-- SECTION: name -->` markers are required for the save script to parse sections into sharded files.
|
|
57
53
|
|
|
@@ -61,7 +57,6 @@ title: Session Handoff
|
|
|
61
57
|
date: {ISO timestamp}
|
|
62
58
|
session_id: {conversation ID if available}
|
|
63
59
|
project: {project name from package.json, Cargo.toml, or directory name}
|
|
64
|
-
context_id: {context_id from Step 1}
|
|
65
60
|
plan_document: {path to plan if provided, or "none"}
|
|
66
61
|
---
|
|
67
62
|
|
|
@@ -69,11 +64,24 @@ plan_document: {path to plan if provided, or "none"}
|
|
|
69
64
|
|
|
70
65
|
<!-- SECTION: summary -->
|
|
71
66
|
## Summary
|
|
72
|
-
{2-3 sentences: what
|
|
67
|
+
{2-3 sentences covering: (1) session goal, (2) what changed technically, (3) current state}
|
|
68
|
+
|
|
69
|
+
Example: "Session goal: Implement JWT refresh tokens. Added refresh token generation in `C:\project\src\auth\tokens.ts:145-203` and rotation endpoint. Feature works but needs rate-limiting before production (see Pending Issues)."
|
|
73
70
|
|
|
74
71
|
<!-- SECTION: completed -->
|
|
75
72
|
## Work Completed
|
|
76
|
-
|
|
73
|
+
|
|
74
|
+
**Bad (requires exploration):**
|
|
75
|
+
- Fixed auth bug
|
|
76
|
+
- Updated tests
|
|
77
|
+
|
|
78
|
+
**Good (exploration-free):**
|
|
79
|
+
- Auth: Fixed JWT validation race condition in `C:\project\src\auth\handler.ts:67-89`
|
|
80
|
+
- Issue: Token validation happened after user session creation → security gap
|
|
81
|
+
- Fix: Moved validation to middleware, added async lock
|
|
82
|
+
- Tests updated: `C:\project\tests\auth\handler.test.ts:45` now covers concurrent validation
|
|
83
|
+
|
|
84
|
+
{Your work completed here, following the "Good" example format with absolute paths}
|
|
77
85
|
|
|
78
86
|
<!-- SECTION: dead-ends -->
|
|
79
87
|
## Dead Ends — Do Not Retry
|
|
@@ -94,11 +102,11 @@ These approaches were attempted and failed. Do not retry without addressing the
|
|
|
94
102
|
|
|
95
103
|
<!-- SECTION: next-steps -->
|
|
96
104
|
## Next Steps
|
|
97
|
-
1. {Actionable item with
|
|
105
|
+
1. {Actionable item with absolute path and line number, e.g., C:\path\to\file.ts:123}
|
|
98
106
|
|
|
99
107
|
<!-- SECTION: files -->
|
|
100
108
|
## Files Modified
|
|
101
|
-
{
|
|
109
|
+
{Absolute paths with line numbers for significant changes. Skip formatting-only edits.}
|
|
102
110
|
|
|
103
111
|
<!-- SECTION: context -->
|
|
104
112
|
## Context for Future Sessions
|
|
@@ -106,7 +114,7 @@ These approaches were attempted and failed. Do not retry without addressing the
|
|
|
106
114
|
|
|
107
115
|
```
|
|
108
116
|
|
|
109
|
-
### Step
|
|
117
|
+
### Step 3: Update Plan Document (if provided)
|
|
110
118
|
|
|
111
119
|
If a plan document path was provided in `$ARGUMENTS`:
|
|
112
120
|
|
|
@@ -146,27 +154,17 @@ If a plan document path was provided in `$ARGUMENTS`:
|
|
|
146
154
|
5. **If no plan document was provided**:
|
|
147
155
|
- Skip plan creation - the handoff document serves as the session record
|
|
148
156
|
|
|
149
|
-
### Step
|
|
157
|
+
### Step 4: Save and Update Status
|
|
150
158
|
|
|
151
159
|
Instead of writing the file directly, pipe your handoff content to the save script:
|
|
152
160
|
|
|
153
161
|
```bash
|
|
154
|
-
bun .aiwcli/_shared/scripts/save_handoff.ts
|
|
155
|
-
{Your complete handoff markdown content from Step
|
|
162
|
+
bun .aiwcli/_shared/scripts/save_handoff.ts <<'EOF'
|
|
163
|
+
{Your complete handoff markdown content from Step 2}
|
|
156
164
|
EOF
|
|
157
165
|
```
|
|
158
166
|
|
|
159
|
-
|
|
160
|
-
1. Creates a folder at `_output/contexts/{context_id}/handoffs/{YYYY-MM-DD-HHMM}/`
|
|
161
|
-
2. Parses sections and writes sharded files (index.md, completed-work.md, dead-ends.md, etc.)
|
|
162
|
-
3. Copies the current plan (if any) to plan.md
|
|
163
|
-
4. Sets `handoff_path` to the index.md path and `handoff_consumed = false` in state.json
|
|
164
|
-
5. Mode stays `active` — staging happens later via session_end hook
|
|
165
|
-
|
|
166
|
-
When the session ends, `session_end.ts` stages `active → has_handoff` (if handoff_path
|
|
167
|
-
exists and handoff_consumed is false). On next `/clear`, `session_start.ts` picks up the
|
|
168
|
-
`has_handoff` state, binds the new session, transitions to `active`, and injects the
|
|
169
|
-
handoff content via `formatHandoffContinuation()`.
|
|
167
|
+
The script will automatically find the active context and create the handoff folder structure.
|
|
170
168
|
|
|
171
169
|
## Dead Ends Section Guidelines
|
|
172
170
|
|
|
@@ -194,39 +192,7 @@ This section is critical for preventing context rot across sessions. Be specific
|
|
|
194
192
|
|
|
195
193
|
## Post-Generation Output
|
|
196
194
|
|
|
197
|
-
After
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
- index.md (entry point with navigation)
|
|
202
|
-
- completed-work.md, dead-ends.md, decisions.md, pending.md, context.md
|
|
203
|
-
- plan.md (copy of current plan, if any)
|
|
204
|
-
|
|
205
|
-
To continue next session:
|
|
206
|
-
Automatic: Handoff restored on next /clear via session_start hook.
|
|
207
|
-
Manual: Use /handoff-resume to explicitly load handoff context at any time.
|
|
208
|
-
Read dead-ends.md first to avoid repeating failed approaches.
|
|
209
|
-
|
|
210
|
-
⚠️ {N} dead ends documented — avoid re-attempting these approaches
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
If plan was updated:
|
|
214
|
-
```
|
|
215
|
-
✓ Updated plan document: {path}
|
|
216
|
-
- {N} items marked complete
|
|
217
|
-
- {N} items partially complete
|
|
218
|
-
- {N} new items added
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
## Success Criteria
|
|
222
|
-
|
|
223
|
-
- [ ] Handoff folder created at `handoffs/{YYYY-MM-DD-HHMM}/`
|
|
224
|
-
- [ ] index.md contains summary and navigation table
|
|
225
|
-
- [ ] All section files created (completed-work.md, dead-ends.md, etc.)
|
|
226
|
-
- [ ] Dead ends use structured table format for quick scanning
|
|
227
|
-
- [ ] plan.md copied from context if plan exists
|
|
228
|
-
- [ ] Next steps are actionable with file references
|
|
229
|
-
- [ ] Git status included in index.md
|
|
230
|
-
- [ ] If plan provided: checkboxes updated to reflect completion status
|
|
231
|
-
- [ ] If plan provided: Session Progress Log appended
|
|
232
|
-
- [ ] State has handoff_path set and handoff_consumed = false
|
|
195
|
+
After saving, confirm:
|
|
196
|
+
- Handoff folder location
|
|
197
|
+
- Number of dead ends documented (if any)
|
|
198
|
+
- Plan update summary (if plan was provided)
|
|
@@ -3,20 +3,19 @@
|
|
|
3
3
|
* SessionEnd hook: Save session state, assign plan fields (fallback),
|
|
4
4
|
* stage has_plan/has_handoff for next session.
|
|
5
5
|
*/
|
|
6
|
-
import * as crypto from "node:crypto";
|
|
7
6
|
import * as fs from "node:fs";
|
|
8
|
-
import * as
|
|
9
|
-
|
|
10
|
-
import { getProjectRoot, getContextDir } from "../lib-ts/base/constants.js";
|
|
11
|
-
import { getGitState } from "../lib-ts/base/git-state.js";
|
|
7
|
+
import * as crypto from "node:crypto";
|
|
8
|
+
import * as path from "node:path";
|
|
12
9
|
import {
|
|
13
10
|
loadHookInput, runHook, logDebug, logInfo, logWarn, logError, logDiagnostic,
|
|
14
11
|
} from "../lib-ts/base/hook-utils.js";
|
|
12
|
+
import { getProjectRoot, getContextDir } from "../lib-ts/base/constants.js";
|
|
15
13
|
import { nowIso } from "../lib-ts/base/utils.js";
|
|
16
14
|
import { getContextBySessionId, saveState } from "../lib-ts/context/context-store.js";
|
|
17
15
|
import {
|
|
18
16
|
findLatestPlan, normalizePlanContent, generatePlanId, extractPlanAnchors,
|
|
19
17
|
} from "../lib-ts/context/plan-manager.js";
|
|
18
|
+
import { getGitState } from "../lib-ts/base/git-state.js";
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
* Archive session transcript to context's session-transcripts/ folder.
|
|
@@ -63,8 +62,8 @@ function archiveTranscript(
|
|
|
63
62
|
try {
|
|
64
63
|
fs.copyFileSync(transcriptPath, archivePath);
|
|
65
64
|
return archivePath;
|
|
66
|
-
} catch (
|
|
67
|
-
logError("session_end", `Failed to copy transcript: ${
|
|
65
|
+
} catch (e) {
|
|
66
|
+
logError("session_end", `Failed to copy transcript: ${e}`);
|
|
68
67
|
return null;
|
|
69
68
|
}
|
|
70
69
|
}
|
|
@@ -116,8 +115,8 @@ function main(): void {
|
|
|
116
115
|
if (archived) {
|
|
117
116
|
logInfo("session_end", `Archived transcript: ${path.basename(archived)}`);
|
|
118
117
|
}
|
|
119
|
-
} catch (
|
|
120
|
-
logError("session_end", `Transcript archival failed: ${
|
|
118
|
+
} catch (e) {
|
|
119
|
+
logError("session_end", `Transcript archival failed: ${e}`);
|
|
121
120
|
}
|
|
122
121
|
}
|
|
123
122
|
|
|
@@ -146,12 +145,18 @@ function main(): void {
|
|
|
146
145
|
state.plan_consumed = state.plan_consumed || false;
|
|
147
146
|
|
|
148
147
|
logInfo("session_end", `Assigned plan fallback: hash=${planHash}, path=${latestPlanPath}`);
|
|
149
|
-
} catch (
|
|
150
|
-
logError("session_end", `Failed to read plan: ${
|
|
148
|
+
} catch (e) {
|
|
149
|
+
logError("session_end", `Failed to read plan: ${e}`);
|
|
151
150
|
}
|
|
152
151
|
}
|
|
153
152
|
}
|
|
154
153
|
|
|
154
|
+
// NEW PLAN DETECTION: If plan_hash differs from last consumed hash, reset consumed flag
|
|
155
|
+
if (state.plan_hash && state.plan_hash_consumed && state.plan_hash !== state.plan_hash_consumed) {
|
|
156
|
+
logInfo("session_end", `New plan detected: ${state.plan_hash} != ${state.plan_hash_consumed}, resetting plan_consumed`);
|
|
157
|
+
state.plan_consumed = false;
|
|
158
|
+
}
|
|
159
|
+
|
|
155
160
|
// Step 2: Stage has_plan if conditions met
|
|
156
161
|
if (state.plan_hash && state.mode === "active" && !state.plan_consumed) {
|
|
157
162
|
state.mode = "has_plan";
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
* SessionStart hook: Restore context after /clear (plan/handoff) or compaction.
|
|
4
4
|
* Routes by source field to appropriate handler.
|
|
5
5
|
*/
|
|
6
|
-
import { getProjectRoot } from "../lib-ts/base/constants.js";
|
|
7
6
|
import {
|
|
8
7
|
loadHookInput, emitContext, runHook, runHookAsync,
|
|
9
8
|
logDebug, logInfo, logError, logDiagnostic,
|
|
10
9
|
} from "../lib-ts/base/hook-utils.js";
|
|
11
|
-
import {
|
|
12
|
-
buildRestoreSections, formatHandoffContinuation, getModeDisplay,
|
|
13
|
-
} from "../lib-ts/context/context-formatter.js";
|
|
10
|
+
import { getProjectRoot } from "../lib-ts/base/constants.js";
|
|
14
11
|
import {
|
|
15
12
|
getContextBySessionId, getAllContexts, bindSession, updateMode,
|
|
16
13
|
} from "../lib-ts/context/context-store.js";
|
|
14
|
+
import {
|
|
15
|
+
buildRestoreSections, formatHandoffContinuation, getModeDisplay,
|
|
16
|
+
buildContextInventory,
|
|
17
|
+
} from "../lib-ts/context/context-formatter.js";
|
|
17
18
|
import type { ContextState } from "../lib-ts/types.js";
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -39,6 +40,9 @@ function handleCompactRestore(sessionId: string, projectRoot: string): void {
|
|
|
39
40
|
const restore = buildRestoreSections(state, projectRoot, true);
|
|
40
41
|
if (restore) sections.push(restore);
|
|
41
42
|
|
|
43
|
+
const inventory = buildContextInventory(state, projectRoot);
|
|
44
|
+
if (inventory) sections.push("", inventory);
|
|
45
|
+
|
|
42
46
|
sections.push(
|
|
43
47
|
"",
|
|
44
48
|
"---",
|
|
@@ -63,7 +67,10 @@ async function handleClearRestore(sessionId: string, projectRoot: string): Promi
|
|
|
63
67
|
const ctx = hasPlan[0]!;
|
|
64
68
|
|
|
65
69
|
bindSession(ctx.id, sessionId, projectRoot);
|
|
66
|
-
updateMode(ctx.id, "active", projectRoot, {
|
|
70
|
+
updateMode(ctx.id, "active", projectRoot, {
|
|
71
|
+
plan_consumed: true,
|
|
72
|
+
plan_hash_consumed: ctx.plan_hash // Track which hash was consumed
|
|
73
|
+
});
|
|
67
74
|
|
|
68
75
|
logInfo("session_start", `Clear restore: ${ctx.id} has_plan → active (plan_consumed=true)`);
|
|
69
76
|
|
|
@@ -79,6 +86,9 @@ async function handleClearRestore(sessionId: string, projectRoot: string): Promi
|
|
|
79
86
|
const restore = buildRestoreSections(ctx, projectRoot, false);
|
|
80
87
|
if (restore) sections.push(restore);
|
|
81
88
|
|
|
89
|
+
const inventory = buildContextInventory(ctx, projectRoot);
|
|
90
|
+
if (inventory) sections.push("", inventory);
|
|
91
|
+
|
|
82
92
|
sections.push(
|
|
83
93
|
"",
|
|
84
94
|
"---",
|
|
@@ -100,7 +110,9 @@ async function handleClearRestore(sessionId: string, projectRoot: string): Promi
|
|
|
100
110
|
logInfo("session_start", `Clear restore: ${ctx.id} has_handoff → active (handoff_consumed=true)`);
|
|
101
111
|
|
|
102
112
|
const handoffContent = formatHandoffContinuation(ctx, projectRoot);
|
|
103
|
-
|
|
113
|
+
const handoffInventory = buildContextInventory(ctx, projectRoot);
|
|
114
|
+
const combined = handoffInventory ? handoffContent + "\n\n" + handoffInventory : handoffContent;
|
|
115
|
+
emitContext(combined);
|
|
104
116
|
return;
|
|
105
117
|
}
|
|
106
118
|
|
|
@@ -124,18 +136,15 @@ async function main(): Promise<void> {
|
|
|
124
136
|
logDiagnostic("session_start", "entry", `source=${source}, session=${sessionId}`);
|
|
125
137
|
|
|
126
138
|
switch (source) {
|
|
127
|
-
case "
|
|
128
|
-
await handleClearRestore(sessionId, projectRoot);
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
case "compact": {
|
|
139
|
+
case "compact":
|
|
132
140
|
handleCompactRestore(sessionId, projectRoot);
|
|
133
|
-
break;
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
break;
|
|
142
|
+
case "clear":
|
|
143
|
+
await handleClearRestore(sessionId, projectRoot);
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
136
146
|
logDebug("session_start", `Unhandled source: ${source}`);
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
147
|
+
break;
|
|
139
148
|
}
|
|
140
149
|
}
|
|
141
150
|
|
|
@@ -6,14 +6,15 @@
|
|
|
6
6
|
* Uses emitContext() for output — context text is passed via hookSpecificOutput JSON.
|
|
7
7
|
* Catches BlockRequest and uses emitBlock() to block the prompt.
|
|
8
8
|
*/
|
|
9
|
-
import { getProjectRoot } from "../lib-ts/base/constants.js";
|
|
10
9
|
import {
|
|
11
10
|
loadHookInput, runHookAsync, logDebug, logInfo, logWarn, logBlocking, logDiagnostic, hookLog, emitContext, emitBlock,
|
|
12
11
|
} from "../lib-ts/base/hook-utils.js";
|
|
13
|
-
import {
|
|
12
|
+
import { getProjectRoot } from "../lib-ts/base/constants.js";
|
|
14
13
|
import {
|
|
15
14
|
getContextBySessionId, bindSession, maybeActivate, saveState,
|
|
16
15
|
} from "../lib-ts/context/context-store.js";
|
|
16
|
+
import { determineContext, BlockRequest } from "../lib-ts/context/context-selector.js";
|
|
17
|
+
import { buildContextInventory } from "../lib-ts/context/context-formatter.js";
|
|
17
18
|
|
|
18
19
|
async function asyncMain(): Promise<void> {
|
|
19
20
|
const payload = loadHookInput();
|
|
@@ -38,8 +39,8 @@ async function asyncMain(): Promise<void> {
|
|
|
38
39
|
// Returning user — context already bound (stderr: false to avoid "hook error" display)
|
|
39
40
|
try {
|
|
40
41
|
maybeActivate(existingCtx.id, permissionMode, projectRoot, "user_prompt_submit");
|
|
41
|
-
} catch (
|
|
42
|
-
hookLog("warn", "user_prompt_submit", `maybeActivate failed (non-critical): ${
|
|
42
|
+
} catch (e) {
|
|
43
|
+
hookLog("warn", "user_prompt_submit", `maybeActivate failed (non-critical): ${e}`, { stderr: false });
|
|
43
44
|
}
|
|
44
45
|
hookLog("debug", "user_prompt_submit", `Session bound to ${existingCtx.id}`, { stderr: false });
|
|
45
46
|
} else if (prompt) {
|
|
@@ -64,12 +65,23 @@ async function asyncMain(): Promise<void> {
|
|
|
64
65
|
if (outputText) {
|
|
65
66
|
outputs.push(outputText);
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
|
|
69
|
+
// Append context folder inventory
|
|
70
|
+
try {
|
|
71
|
+
const boundState = getContextBySessionId(sessionId, projectRoot);
|
|
72
|
+
if (boundState) {
|
|
73
|
+
const inventory = buildContextInventory(boundState, projectRoot);
|
|
74
|
+
if (inventory) outputs.push(inventory);
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
logWarn("user_prompt_submit", `Inventory failed (non-critical): ${e}`);
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {
|
|
80
|
+
if (e instanceof BlockRequest) {
|
|
81
|
+
emitBlock((e as Error).message);
|
|
70
82
|
return;
|
|
71
83
|
}
|
|
72
|
-
throw
|
|
84
|
+
throw e; // Re-throw unexpected errors
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
|
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
|
-
|
|
9
8
|
import { logDebug, logWarn } from "./logger.js";
|
|
10
9
|
import { STOP_WORDS } from "./stop-words.js";
|
|
11
|
-
import { cleanTextForSlug } from "./utils.js";
|
|
12
10
|
import type { InferenceResult } from "../types.js";
|
|
11
|
+
import { execFileAsync, getInternalSubprocessEnv, shellQuoteWin } from "./subprocess-utils.js";
|
|
13
12
|
|
|
14
13
|
// Model configurations §6.1
|
|
15
14
|
const MODELS: Record<string, string> = {
|
|
@@ -35,8 +34,8 @@ export function inference(
|
|
|
35
34
|
timeout?: number,
|
|
36
35
|
): InferenceResult {
|
|
37
36
|
const startTime = Date.now();
|
|
38
|
-
const model = MODELS[level] ?? MODELS
|
|
39
|
-
const timeoutSec = timeout ?? TIMEOUTS[level] ??
|
|
37
|
+
const model = MODELS[level] ?? MODELS.fast;
|
|
38
|
+
const timeoutSec = timeout ?? TIMEOUTS[level] ?? TIMEOUTS.fast;
|
|
40
39
|
const fullPrompt = `${systemPrompt}\n\n${userPrompt}`;
|
|
41
40
|
|
|
42
41
|
// Remove ANTHROPIC_API_KEY to force subscription auth
|
|
@@ -45,6 +44,8 @@ export function inference(
|
|
|
45
44
|
|
|
46
45
|
try {
|
|
47
46
|
const isWin = process.platform === "win32";
|
|
47
|
+
let stdout: string;
|
|
48
|
+
|
|
48
49
|
// On Windows with shell:true, Node.js sets windowsVerbatimArguments —
|
|
49
50
|
// args are joined with spaces, NOT individually quoted. We must manually
|
|
50
51
|
// wrap multi-word/special-char args in "..." for cmd.exe parsing.
|
|
@@ -52,10 +53,10 @@ export function inference(
|
|
|
52
53
|
const empty = isWin ? '""' : "";
|
|
53
54
|
let promptArg = fullPrompt;
|
|
54
55
|
if (isWin) {
|
|
55
|
-
promptArg = '"' + fullPrompt.
|
|
56
|
+
promptArg = '"' + fullPrompt.replace(/\r?\n/g, " ").replace(/"/g, '""') + '"';
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
stdout = execFileSync(
|
|
59
60
|
"claude",
|
|
60
61
|
["--model", model, "--print", "--setting-sources", empty, "-p", promptArg],
|
|
61
62
|
{
|
|
@@ -73,10 +74,10 @@ export function inference(
|
|
|
73
74
|
output: stdout.trim(),
|
|
74
75
|
latency_ms: latencyMs,
|
|
75
76
|
};
|
|
76
|
-
} catch (
|
|
77
|
+
} catch (e: any) {
|
|
77
78
|
const latencyMs = Date.now() - startTime;
|
|
78
79
|
|
|
79
|
-
if (
|
|
80
|
+
if (e.code === "ETIMEDOUT" || e.killed) {
|
|
80
81
|
return {
|
|
81
82
|
success: false,
|
|
82
83
|
output: "",
|
|
@@ -85,7 +86,7 @@ export function inference(
|
|
|
85
86
|
};
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
if (
|
|
89
|
+
if (e.code === "ENOENT") {
|
|
89
90
|
return {
|
|
90
91
|
success: false,
|
|
91
92
|
output: "",
|
|
@@ -95,11 +96,11 @@ export function inference(
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// Non-zero exit code
|
|
98
|
-
if (
|
|
99
|
+
if (e.status !== undefined && e.status !== 0) {
|
|
99
100
|
return {
|
|
100
101
|
success: false,
|
|
101
|
-
output: (
|
|
102
|
-
error: (
|
|
102
|
+
output: (e.stdout ?? "").toString().trim(),
|
|
103
|
+
error: (e.stderr ?? "").toString().trim() || `Exit code: ${e.status}`,
|
|
103
104
|
latency_ms: latencyMs,
|
|
104
105
|
};
|
|
105
106
|
}
|
|
@@ -107,7 +108,7 @@ export function inference(
|
|
|
107
108
|
return {
|
|
108
109
|
success: false,
|
|
109
110
|
output: "",
|
|
110
|
-
error: String(
|
|
111
|
+
error: String(e),
|
|
111
112
|
latency_ms: latencyMs,
|
|
112
113
|
};
|
|
113
114
|
}
|
|
@@ -133,13 +134,13 @@ Output ONLY the keywords separated by spaces, nothing else.`;
|
|
|
133
134
|
export function generateSemanticSummary(
|
|
134
135
|
prompt: string,
|
|
135
136
|
timeout = 15,
|
|
136
|
-
):
|
|
137
|
+
): string | null {
|
|
137
138
|
const result = inference(CONTEXT_ID_SYSTEM_PROMPT, prompt, "standard", timeout);
|
|
138
139
|
|
|
139
140
|
if (!result.success || !result.output) return null;
|
|
140
141
|
|
|
141
142
|
let summary = result.output.trim();
|
|
142
|
-
summary = summary.
|
|
143
|
+
summary = summary.replace(/^["']+|["']+$/g, "");
|
|
143
144
|
summary = summary.replace(/[.!?]+$/, "");
|
|
144
145
|
|
|
145
146
|
// Filter stop words
|
|
@@ -194,7 +195,7 @@ Respond with ONLY a JSON object: {"slug": "your 8-12 word phrase here"}`;
|
|
|
194
195
|
export function generateContextIdSlug(
|
|
195
196
|
prompt: string,
|
|
196
197
|
timeout = 3,
|
|
197
|
-
):
|
|
198
|
+
): string | null {
|
|
198
199
|
const truncated = prompt.slice(0, 500);
|
|
199
200
|
|
|
200
201
|
const result = inference(CONTEXT_ID_SLUG_PROMPT, truncated, "fast", timeout);
|
|
@@ -207,7 +208,7 @@ export function generateContextIdSlug(
|
|
|
207
208
|
const raw = result.output.trim();
|
|
208
209
|
|
|
209
210
|
// Parse JSON response, fall back to raw text
|
|
210
|
-
let slug:
|
|
211
|
+
let slug: string | null = null;
|
|
211
212
|
try {
|
|
212
213
|
const parsed = JSON.parse(raw);
|
|
213
214
|
if (parsed && typeof parsed === "object" && "slug" in parsed) {
|
|
@@ -220,11 +221,11 @@ export function generateContextIdSlug(
|
|
|
220
221
|
if (!slug) slug = raw;
|
|
221
222
|
|
|
222
223
|
// Clean up
|
|
223
|
-
slug = slug.
|
|
224
|
+
slug = slug.replace(/^["'`]+|["'`]+$/g, "");
|
|
224
225
|
slug = slug.replace(/[.!?]+$/, "");
|
|
225
|
-
slug = slug.
|
|
226
|
-
slug = slug.
|
|
227
|
-
slug = slug.
|
|
226
|
+
slug = slug.replace(/-/g, " ");
|
|
227
|
+
slug = slug.replace(/[^a-zA-Z0-9 ]/g, "");
|
|
228
|
+
slug = slug.replace(/\s+/g, " ").trim();
|
|
228
229
|
|
|
229
230
|
const words = slug.split(" ");
|
|
230
231
|
|
|
@@ -239,13 +240,61 @@ export function generateContextIdSlug(
|
|
|
239
240
|
return resultSlug;
|
|
240
241
|
}
|
|
241
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Async version of inference() that does NOT block the event loop.
|
|
245
|
+
* Use for parallel AI calls (e.g., Stage 3 parallel summarizers).
|
|
246
|
+
* Uses execFileAsync and getInternalSubprocessEnv for proper subprocess isolation.
|
|
247
|
+
*/
|
|
248
|
+
export async function inferenceAsync(
|
|
249
|
+
systemPrompt: string,
|
|
250
|
+
userPrompt: string,
|
|
251
|
+
level = "fast",
|
|
252
|
+
timeout?: number,
|
|
253
|
+
): Promise<InferenceResult> {
|
|
254
|
+
const startTime = Date.now();
|
|
255
|
+
const model = (level in MODELS ? MODELS[level] : undefined) ?? MODELS.fast;
|
|
256
|
+
const timeoutSec = timeout ?? (level in TIMEOUTS ? TIMEOUTS[level] : undefined) ?? TIMEOUTS.fast;
|
|
257
|
+
const timeoutMs = timeoutSec * 1000;
|
|
258
|
+
const fullPrompt = `${systemPrompt}\n\n${userPrompt}`;
|
|
259
|
+
|
|
260
|
+
const env = getInternalSubprocessEnv();
|
|
261
|
+
delete env.ANTHROPIC_API_KEY;
|
|
262
|
+
|
|
263
|
+
const isWin = process.platform === "win32";
|
|
264
|
+
const empty = isWin ? '""' : "";
|
|
265
|
+
const promptArg = isWin
|
|
266
|
+
? shellQuoteWin(fullPrompt.replace(/\r?\n/g, " "))
|
|
267
|
+
: fullPrompt;
|
|
268
|
+
|
|
269
|
+
const result = await execFileAsync(
|
|
270
|
+
"claude",
|
|
271
|
+
["--model", model, "--print", "--setting-sources", empty, "-p", promptArg],
|
|
272
|
+
{ timeout: timeoutMs, env, shell: isWin },
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const latencyMs = Date.now() - startTime;
|
|
276
|
+
|
|
277
|
+
if (result.killed) {
|
|
278
|
+
return { success: false, output: "", error: `Timeout after ${timeoutSec}s`, latency_ms: latencyMs };
|
|
279
|
+
}
|
|
280
|
+
if (result.exitCode !== 0) {
|
|
281
|
+
return {
|
|
282
|
+
success: false,
|
|
283
|
+
output: result.stdout.trim(),
|
|
284
|
+
error: result.stderr.trim() || `Exit code: ${result.exitCode}`,
|
|
285
|
+
latency_ms: latencyMs,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return { success: true, output: result.stdout.trim(), latency_ms: latencyMs };
|
|
289
|
+
}
|
|
290
|
+
|
|
242
291
|
/**
|
|
243
292
|
* Filter stop words from text.
|
|
244
293
|
* See SPEC.md §6.4
|
|
245
294
|
*/
|
|
246
295
|
function filterStopWords(text: string): string {
|
|
247
|
-
|
|
248
|
-
|
|
296
|
+
return text
|
|
297
|
+
.toLowerCase()
|
|
249
298
|
.split(/\s+/)
|
|
250
299
|
.filter((w) => !STOP_WORDS.has(w) && w.length > 1)
|
|
251
300
|
.join(" ");
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
|
|
10
|
-
import { atomicWrite } from "./atomic-write.js";
|
|
11
9
|
import { getContextDir } from "./constants.js";
|
|
10
|
+
import { atomicWrite } from "./atomic-write.js";
|
|
12
11
|
import { logWarn } from "./logger.js";
|
|
13
12
|
import type { ContextState, Mode } from "../types.js";
|
|
14
13
|
|
|
@@ -31,7 +30,6 @@ export function toDict(state: ContextState): Record<string, unknown> {
|
|
|
31
30
|
result[key] = value;
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
|
-
|
|
35
33
|
return result;
|
|
36
34
|
}
|
|
37
35
|
|
|
@@ -54,11 +52,11 @@ export function readStateJson(
|
|
|
54
52
|
if (!fs.existsSync(sp)) return null;
|
|
55
53
|
|
|
56
54
|
try {
|
|
57
|
-
const raw = fs.readFileSync(sp, "
|
|
55
|
+
const raw = fs.readFileSync(sp, "utf-8");
|
|
58
56
|
const data = JSON.parse(raw) as Record<string, any>;
|
|
59
57
|
return dictToState(data);
|
|
60
|
-
} catch (
|
|
61
|
-
logWarn("state_io", `Failed to read state.json for '${contextId}': ${
|
|
58
|
+
} catch (e: any) {
|
|
59
|
+
logWarn("state_io", `Failed to read state.json for '${contextId}': ${e}`);
|
|
62
60
|
return null;
|
|
63
61
|
}
|
|
64
62
|
}
|
|
@@ -71,7 +69,7 @@ export function writeStateJson(
|
|
|
71
69
|
contextId: string,
|
|
72
70
|
state: ContextState,
|
|
73
71
|
projectRoot?: string,
|
|
74
|
-
): [boolean,
|
|
72
|
+
): [boolean, string | null] {
|
|
75
73
|
const sp = statePath(contextId, projectRoot);
|
|
76
74
|
const dir = path.dirname(sp);
|
|
77
75
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -112,6 +110,13 @@ export function dictToState(data: Record<string, any>): ContextState {
|
|
|
112
110
|
if ("handoff_path" in data) state.handoff_path = data.handoff_path;
|
|
113
111
|
if ("last_session" in data) state.last_session = data.last_session;
|
|
114
112
|
|
|
113
|
+
// Migration: plan_hash_consumed (added in multi-plan context fix)
|
|
114
|
+
if ("plan_hash_consumed" in data) {
|
|
115
|
+
state.plan_hash_consumed = data.plan_hash_consumed;
|
|
116
|
+
} else {
|
|
117
|
+
state.plan_hash_consumed = null; // Default for old contexts
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
// Preserve method-specific extension data (e.g., cc_native) that isn't
|
|
116
121
|
// part of the core ContextState interface. Without this, round-trip
|
|
117
122
|
// read→write cycles silently drop extension fields.
|