aiwcli 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/templates/_shared/hooks-ts/session_end.ts +4 -1
- package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/logger.ts +15 -18
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +18 -15
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +26 -30
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +12 -13
- package/dist/templates/_shared/scripts/resume_handoff.ts +62 -38
- package/dist/templates/_shared/scripts/save_handoff.ts +24 -24
- package/dist/templates/_shared/scripts/status_line.ts +101 -147
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +239 -133
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +11 -12
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +139 -56
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +22 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +1 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +7 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +5 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +133 -13
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +6 -6
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +5 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +30 -33
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +118 -43
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +21 -0
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
import * as fs from "node:fs";
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
|
|
18
|
-
import { getProjectRoot } from "../lib-ts/base/constants.js";
|
|
19
|
-
import { getGitStatusShort } from "../lib-ts/base/git-state.js";
|
|
20
|
-
import { eprint } from "../lib-ts/base/utils.js";
|
|
21
|
-
import { findActiveContextId } from "../lib-ts/context/context-store.js";
|
|
22
18
|
import {
|
|
23
19
|
findLatestHandoff,
|
|
24
|
-
getHandoffPlanReference,
|
|
25
|
-
getHandoffTimestamp,
|
|
26
20
|
readHandoffSections,
|
|
21
|
+
getHandoffTimestamp,
|
|
22
|
+
getHandoffPlanReference,
|
|
27
23
|
} from "../lib-ts/handoff/handoff-reader.js";
|
|
24
|
+
import { getProjectRoot } from "../lib-ts/base/constants.js";
|
|
25
|
+
import { findActiveContextId } from "../lib-ts/context/context-store.js";
|
|
26
|
+
import { getGitStatusShort } from "../lib-ts/base/git-state.js";
|
|
27
|
+
import { eprint } from "../lib-ts/base/utils.js";
|
|
28
28
|
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
// Helpers
|
|
@@ -40,11 +40,9 @@ function formatRelativeAge(date: Date): string {
|
|
|
40
40
|
if (diffDays === 0) {
|
|
41
41
|
if (diffHours === 0) {
|
|
42
42
|
return diffMin <= 1 ? "just now" : `${diffMin} minutes ago`;
|
|
43
|
-
}
|
|
44
|
-
|
|
43
|
+
}
|
|
45
44
|
return diffHours === 1 ? "1 hour ago" : `${diffHours} hours ago`;
|
|
46
|
-
}
|
|
47
|
-
|
|
45
|
+
}
|
|
48
46
|
if (diffDays === 1) return "yesterday";
|
|
49
47
|
return `${diffDays} days ago`;
|
|
50
48
|
}
|
|
@@ -66,14 +64,13 @@ function countPlanProgress(planContent: string): [number, number] | null {
|
|
|
66
64
|
* Handoff section files typically start with "# Title\n\n..."
|
|
67
65
|
*/
|
|
68
66
|
function stripTitle(content: string): string {
|
|
69
|
-
const lines = content.split(
|
|
67
|
+
const lines = content.split(/\r?\n/);
|
|
70
68
|
if (lines[0]?.startsWith("# ")) {
|
|
71
69
|
// Remove title and leading blank lines
|
|
72
70
|
let i = 1;
|
|
73
71
|
while (i < lines.length && lines[i]!.trim() === "") i++;
|
|
74
72
|
return lines.slice(i).join("\n").trim();
|
|
75
|
-
}
|
|
76
|
-
|
|
73
|
+
}
|
|
77
74
|
return content.trim();
|
|
78
75
|
}
|
|
79
76
|
|
|
@@ -81,7 +78,7 @@ function stripTitle(content: string): string {
|
|
|
81
78
|
// Resolution
|
|
82
79
|
// ---------------------------------------------------------------------------
|
|
83
80
|
|
|
84
|
-
function resolveHandoffFolder(args: string[]): [string,
|
|
81
|
+
function resolveHandoffFolder(args: string[]): [string, string | null] {
|
|
85
82
|
const projectRoot = getProjectRoot(process.cwd());
|
|
86
83
|
|
|
87
84
|
// --context <id>
|
|
@@ -92,8 +89,7 @@ function resolveHandoffFolder(args: string[]): [string, null | string] {
|
|
|
92
89
|
if (!folder) {
|
|
93
90
|
eprint(`No handoff folders found for context: ${contextId}`);
|
|
94
91
|
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
|
|
92
|
+
}
|
|
97
93
|
return [folder, contextId];
|
|
98
94
|
}
|
|
99
95
|
|
|
@@ -128,8 +124,7 @@ function resolveHandoffFolder(args: string[]): [string, null | string] {
|
|
|
128
124
|
if (!folder) {
|
|
129
125
|
eprint(`No handoff folders found for context: ${discoveredId} (auto-discovered)`);
|
|
130
126
|
process.exit(1);
|
|
131
|
-
}
|
|
132
|
-
|
|
127
|
+
}
|
|
133
128
|
return [folder, discoveredId];
|
|
134
129
|
}
|
|
135
130
|
|
|
@@ -183,7 +178,7 @@ function main(): void {
|
|
|
183
178
|
const planRef = getHandoffPlanReference(handoffFolder, resolvedContextId, projectRoot);
|
|
184
179
|
if (planRef) {
|
|
185
180
|
try {
|
|
186
|
-
const planContent = fs.readFileSync(planRef, "
|
|
181
|
+
const planContent = fs.readFileSync(planRef, "utf-8");
|
|
187
182
|
const progress = countPlanProgress(planContent);
|
|
188
183
|
if (progress) {
|
|
189
184
|
const [done, total] = progress;
|
|
@@ -204,12 +199,19 @@ function main(): void {
|
|
|
204
199
|
// Build output
|
|
205
200
|
const out: string[] = [];
|
|
206
201
|
|
|
207
|
-
out.push(`## Session Resumed from Handoff
|
|
202
|
+
out.push(`## Session Resumed from Handoff`);
|
|
203
|
+
out.push("");
|
|
208
204
|
out.push(`**Source:** \`${path.basename(handoffFolder)}\` (${ageStr})`);
|
|
209
205
|
if (resolvedContextId) out.push(`**Context:** ${resolvedContextId}`);
|
|
210
206
|
out.push(`**Plan Status:** ${planStatus}`);
|
|
211
207
|
if (staleWarning) out.push(staleWarning);
|
|
212
|
-
out.push(""
|
|
208
|
+
out.push("");
|
|
209
|
+
out.push("---");
|
|
210
|
+
out.push("");
|
|
211
|
+
|
|
212
|
+
// Priority 1: Dead Ends
|
|
213
|
+
out.push("### Dead Ends — Do Not Retry");
|
|
214
|
+
out.push("");
|
|
213
215
|
if (sections.deadEnds) {
|
|
214
216
|
const content = stripTitle(sections.deadEnds);
|
|
215
217
|
if (content && content !== "(No content for this section)") {
|
|
@@ -219,9 +221,12 @@ function main(): void {
|
|
|
219
221
|
}
|
|
220
222
|
} else {
|
|
221
223
|
out.push("(No dead-ends.md found)");
|
|
222
|
-
}
|
|
224
|
+
}
|
|
225
|
+
out.push("");
|
|
223
226
|
|
|
224
|
-
|
|
227
|
+
// Priority 2: Pending
|
|
228
|
+
out.push("### Pending Items");
|
|
229
|
+
out.push("");
|
|
225
230
|
if (sections.pending) {
|
|
226
231
|
const content = stripTitle(sections.pending);
|
|
227
232
|
if (content && content !== "(No content for this section)") {
|
|
@@ -231,8 +236,7 @@ function main(): void {
|
|
|
231
236
|
}
|
|
232
237
|
} else {
|
|
233
238
|
out.push("(No pending.md found)");
|
|
234
|
-
}
|
|
235
|
-
|
|
239
|
+
}
|
|
236
240
|
out.push("");
|
|
237
241
|
|
|
238
242
|
// Priority 3: Plan remaining items (from plan.md in handoff)
|
|
@@ -240,21 +244,22 @@ function main(): void {
|
|
|
240
244
|
const planContent = stripTitle(sections.plan);
|
|
241
245
|
// Extract unchecked items
|
|
242
246
|
const remaining = planContent
|
|
243
|
-
.split(
|
|
247
|
+
.split(/\r?\n/)
|
|
244
248
|
.filter(line => /\[ \]/.test(line))
|
|
245
249
|
.map(line => line.trim());
|
|
246
250
|
if (remaining.length > 0) {
|
|
247
|
-
out.push("### Plan — Remaining Items"
|
|
251
|
+
out.push("### Plan — Remaining Items");
|
|
252
|
+
out.push("");
|
|
248
253
|
for (const item of remaining) {
|
|
249
254
|
out.push(item);
|
|
250
|
-
}
|
|
251
|
-
|
|
255
|
+
}
|
|
252
256
|
out.push("");
|
|
253
257
|
}
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
// Priority 4: Decisions
|
|
257
|
-
out.push("### Settled Decisions"
|
|
261
|
+
out.push("### Settled Decisions");
|
|
262
|
+
out.push("");
|
|
258
263
|
if (sections.decisions) {
|
|
259
264
|
const content = stripTitle(sections.decisions);
|
|
260
265
|
if (content && content !== "(No content for this section)") {
|
|
@@ -264,9 +269,21 @@ function main(): void {
|
|
|
264
269
|
}
|
|
265
270
|
} else {
|
|
266
271
|
out.push("(No decisions.md found)");
|
|
267
|
-
}
|
|
272
|
+
}
|
|
273
|
+
out.push("");
|
|
274
|
+
|
|
275
|
+
// Priority 5: Git Delta
|
|
276
|
+
out.push("### Git Delta Since Handoff");
|
|
277
|
+
out.push("");
|
|
278
|
+
out.push("**Current git status:**");
|
|
279
|
+
out.push("```");
|
|
280
|
+
out.push(currentGit);
|
|
281
|
+
out.push("```");
|
|
282
|
+
out.push("");
|
|
268
283
|
|
|
269
|
-
|
|
284
|
+
// Priority 6: Completed Work
|
|
285
|
+
out.push("### Completed Work");
|
|
286
|
+
out.push("");
|
|
270
287
|
if (sections.completedWork) {
|
|
271
288
|
const content = stripTitle(sections.completedWork);
|
|
272
289
|
if (content && content !== "(No content for this section)") {
|
|
@@ -276,9 +293,12 @@ function main(): void {
|
|
|
276
293
|
}
|
|
277
294
|
} else {
|
|
278
295
|
out.push("(No completed-work.md found)");
|
|
279
|
-
}
|
|
296
|
+
}
|
|
297
|
+
out.push("");
|
|
280
298
|
|
|
281
|
-
|
|
299
|
+
// Priority 7: Context
|
|
300
|
+
out.push("### Context Notes");
|
|
301
|
+
out.push("");
|
|
282
302
|
if (sections.context) {
|
|
283
303
|
const content = stripTitle(sections.context);
|
|
284
304
|
if (content && content !== "(No content for this section)") {
|
|
@@ -288,9 +308,13 @@ function main(): void {
|
|
|
288
308
|
}
|
|
289
309
|
} else {
|
|
290
310
|
out.push("None");
|
|
291
|
-
}
|
|
311
|
+
}
|
|
312
|
+
out.push("");
|
|
292
313
|
|
|
293
|
-
|
|
314
|
+
// Footer
|
|
315
|
+
out.push("---");
|
|
316
|
+
out.push("");
|
|
317
|
+
out.push("**Create ISC tasks** from the pending items and remaining plan items above using TaskCreate. Each task should be ~8 words, state a desired end-state (not an action), and be binary testable.");
|
|
294
318
|
|
|
295
319
|
console.log(out.join("\n"));
|
|
296
320
|
}
|
|
@@ -298,14 +322,14 @@ function main(): void {
|
|
|
298
322
|
/**
|
|
299
323
|
* Try to extract context_id from index.md frontmatter.
|
|
300
324
|
*/
|
|
301
|
-
function extractContextIdFromIndex(indexContent:
|
|
325
|
+
function extractContextIdFromIndex(indexContent: string | null): string | null {
|
|
302
326
|
if (!indexContent) return null;
|
|
303
327
|
if (!indexContent.startsWith("---")) return null;
|
|
304
328
|
|
|
305
329
|
const parts = indexContent.split("---", 3);
|
|
306
330
|
if (parts.length < 3) return null;
|
|
307
331
|
|
|
308
|
-
for (const line of parts[1]!.trim().split(
|
|
332
|
+
for (const line of parts[1]!.trim().split(/\r?\n/)) {
|
|
309
333
|
const colonIdx = line.indexOf(":");
|
|
310
334
|
if (colonIdx !== -1) {
|
|
311
335
|
const key = line.slice(0, colonIdx).trim();
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
import * as fs from "node:fs";
|
|
23
23
|
import * as path from "node:path";
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { getContext, saveState } from "../lib-ts/context/context-store.js";
|
|
26
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";
|
|
27
29
|
import { getGitStatusShort } from "../lib-ts/base/git-state.js";
|
|
28
|
-
import { logError, logInfo, logWarn } from "../lib-ts/base/logger.js";
|
|
29
30
|
import { eprint } from "../lib-ts/base/utils.js";
|
|
30
|
-
import { getContext, saveState } from "../lib-ts/context/context-store.js";
|
|
31
31
|
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
33
33
|
// Parsing helpers
|
|
@@ -40,15 +40,14 @@ function parseFrontmatter(content: string): [Record<string, string>, string] {
|
|
|
40
40
|
if (content.startsWith("---")) {
|
|
41
41
|
const parts = content.split("---", 3);
|
|
42
42
|
if (parts.length >= 3) {
|
|
43
|
-
for (const line of parts[1]!.trim().split(
|
|
43
|
+
for (const line of parts[1]!.trim().split(/\r?\n/)) {
|
|
44
44
|
const colonIdx = line.indexOf(":");
|
|
45
45
|
if (colonIdx !== -1) {
|
|
46
46
|
const key = line.slice(0, colonIdx).trim();
|
|
47
47
|
const value = line.slice(colonIdx + 1).trim();
|
|
48
48
|
frontmatter[key] = value;
|
|
49
49
|
}
|
|
50
|
-
}
|
|
51
|
-
|
|
50
|
+
}
|
|
52
51
|
remaining = parts[2]!.trim();
|
|
53
52
|
}
|
|
54
53
|
}
|
|
@@ -58,16 +57,15 @@ function parseFrontmatter(content: string): [Record<string, string>, string] {
|
|
|
58
57
|
|
|
59
58
|
function parseHandoffSections(content: string): Record<string, string> {
|
|
60
59
|
const sections: Record<string, string> = {};
|
|
61
|
-
let currentSection:
|
|
60
|
+
let currentSection: string | null = null;
|
|
62
61
|
const currentContent: string[] = [];
|
|
63
62
|
|
|
64
|
-
for (const line of content.split(
|
|
63
|
+
for (const line of content.split(/\r?\n/)) {
|
|
65
64
|
const marker = line.trim().match(/<!-- SECTION:\s*(\S+)\s*-->/);
|
|
66
65
|
if (marker) {
|
|
67
66
|
if (currentSection) {
|
|
68
67
|
sections[currentSection] = currentContent.join("\n").trim();
|
|
69
|
-
}
|
|
70
|
-
|
|
68
|
+
}
|
|
71
69
|
currentSection = marker[1]!;
|
|
72
70
|
currentContent.length = 0;
|
|
73
71
|
} else if (currentSection) {
|
|
@@ -86,13 +84,12 @@ function parseHandoffSections(content: string): Record<string, string> {
|
|
|
86
84
|
// Plan helper
|
|
87
85
|
// ---------------------------------------------------------------------------
|
|
88
86
|
|
|
89
|
-
function getPlanPathFromContext(contextId: string, projectRoot: string):
|
|
87
|
+
function getPlanPathFromContext(contextId: string, projectRoot: string): string | null {
|
|
90
88
|
const context = getContext(contextId, projectRoot);
|
|
91
89
|
if (!context?.plan_path) return null;
|
|
92
90
|
try {
|
|
93
91
|
if (fs.existsSync(context.plan_path)) return context.plan_path;
|
|
94
|
-
} catch { /* ignore */ }
|
|
95
|
-
|
|
92
|
+
} catch { /* ignore */ }
|
|
96
93
|
return null;
|
|
97
94
|
}
|
|
98
95
|
|
|
@@ -128,7 +125,7 @@ function generateIndex(
|
|
|
128
125
|
const summary = (sections["summary"] ?? "").trim();
|
|
129
126
|
if (summary) {
|
|
130
127
|
const summaryText = summary
|
|
131
|
-
.split(
|
|
128
|
+
.split(/\r?\n/)
|
|
132
129
|
.filter(l => !l.trim().startsWith("##"))
|
|
133
130
|
.join("\n")
|
|
134
131
|
.trim();
|
|
@@ -179,8 +176,7 @@ function writeSectionFile(folder: string, filename: string, title: string, conte
|
|
|
179
176
|
if (!success) {
|
|
180
177
|
logWarn("save_handoff", `Failed to write ${filename}: ${error}`);
|
|
181
178
|
return false;
|
|
182
|
-
}
|
|
183
|
-
|
|
179
|
+
}
|
|
184
180
|
return true;
|
|
185
181
|
}
|
|
186
182
|
|
|
@@ -204,7 +200,7 @@ function main(): void {
|
|
|
204
200
|
// Read content from stdin
|
|
205
201
|
let content: string;
|
|
206
202
|
try {
|
|
207
|
-
content = fs.readFileSync(0, "
|
|
203
|
+
content = fs.readFileSync(0, "utf-8");
|
|
208
204
|
} catch {
|
|
209
205
|
logError("save_handoff", "Failed to read from stdin");
|
|
210
206
|
process.exit(1);
|
|
@@ -247,15 +243,15 @@ function main(): void {
|
|
|
247
243
|
// Copy plan if exists
|
|
248
244
|
if (planPath) {
|
|
249
245
|
try {
|
|
250
|
-
const planContent = fs.readFileSync(planPath, "
|
|
246
|
+
const planContent = fs.readFileSync(planPath, "utf-8");
|
|
251
247
|
const [success, error] = atomicWrite(path.join(handoffFolder, "plan.md"), planContent);
|
|
252
248
|
if (success) {
|
|
253
249
|
logInfo("save_handoff", `Copied plan from ${planPath}`);
|
|
254
250
|
} else {
|
|
255
251
|
logWarn("save_handoff", `Failed to copy plan: ${error}`);
|
|
256
252
|
}
|
|
257
|
-
} catch (
|
|
258
|
-
logWarn("save_handoff", `Failed to read plan: ${
|
|
253
|
+
} catch (e) {
|
|
254
|
+
logWarn("save_handoff", `Failed to read plan: ${e}`);
|
|
259
255
|
}
|
|
260
256
|
}
|
|
261
257
|
|
|
@@ -271,7 +267,7 @@ function main(): void {
|
|
|
271
267
|
}
|
|
272
268
|
|
|
273
269
|
// Write section files
|
|
274
|
-
const sectionMapping: Record<string, [string,
|
|
270
|
+
const sectionMapping: Record<string, [string, string | null]> = {
|
|
275
271
|
completed: ["completed-work.md", "Work Completed"],
|
|
276
272
|
"dead-ends": ["dead-ends.md", "Dead Ends - Do Not Retry"],
|
|
277
273
|
decisions: ["decisions.md", "Key Decisions"],
|
|
@@ -294,7 +290,11 @@ function main(): void {
|
|
|
294
290
|
fileContents[filename]!.push(sectionContent);
|
|
295
291
|
} else {
|
|
296
292
|
// Write mode with title
|
|
297
|
-
|
|
293
|
+
if (!fileContents[filename]) {
|
|
294
|
+
fileContents[filename] = [`# ${title}`, "", sectionContent];
|
|
295
|
+
} else {
|
|
296
|
+
fileContents[filename] = [`# ${title}`, "", ...fileContents[filename]!, "", sectionContent];
|
|
297
|
+
}
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
|
|
@@ -339,8 +339,8 @@ function main(): void {
|
|
|
339
339
|
} else {
|
|
340
340
|
logWarn("save_handoff", `Could not load context state for ${contextId}`);
|
|
341
341
|
}
|
|
342
|
-
} catch (
|
|
343
|
-
logWarn("save_handoff", `Handoff saved but auto-resume won't work (context update failed): ${
|
|
342
|
+
} catch (e) {
|
|
343
|
+
logWarn("save_handoff", `Handoff saved but auto-resume won't work (context update failed): ${e}`);
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
// Output success message
|