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.
Files changed (25) hide show
  1. package/dist/templates/_shared/hooks-ts/session_end.ts +4 -1
  2. package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
  3. package/dist/templates/_shared/lib-ts/base/logger.ts +15 -18
  4. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +18 -15
  5. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +26 -30
  6. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +12 -13
  7. package/dist/templates/_shared/scripts/resume_handoff.ts +62 -38
  8. package/dist/templates/_shared/scripts/save_handoff.ts +24 -24
  9. package/dist/templates/_shared/scripts/status_line.ts +101 -147
  10. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +239 -133
  11. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +11 -12
  12. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +139 -56
  13. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +22 -2
  14. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
  15. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +1 -0
  16. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +7 -1
  17. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +5 -4
  18. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +133 -13
  19. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +6 -6
  20. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +5 -4
  21. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +30 -33
  22. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +118 -43
  23. package/dist/templates/cc-native/_cc-native/plan-review.config.json +21 -0
  24. package/oclif.manifest.json +1 -1
  25. 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("\n");
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, null | 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, "utf8");
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("", "---", "", "### Dead Ends — Do Not Retry", "");
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
- out.push("", "### Pending Items", "");
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("\n")
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
- out.push("", "### Git Delta Since Handoff", "", "**Current git status:**", "```", currentGit, "```", "", "### Completed Work", "");
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
- out.push("", "### Context Notes", "");
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
- 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.");
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: null | string): null | string {
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("\n")) {
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 { atomicWrite } from "../lib-ts/base/atomic-write.js";
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("\n")) {
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: null | string = null;
60
+ let currentSection: string | null = null;
62
61
  const currentContent: string[] = [];
63
62
 
64
- for (const line of content.split("\n")) {
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): null | 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("\n")
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, "utf8");
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, "utf8");
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 (error) {
258
- logWarn("save_handoff", `Failed to read plan: ${error}`);
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, null | 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
- fileContents[filename] = fileContents[filename] ? [`# ${title}`, "", ...fileContents[filename]!, "", sectionContent] : [`# ${title}`, "", sectionContent];
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 (error) {
343
- logWarn("save_handoff", `Handoff saved but auto-resume won't work (context update failed): ${error}`);
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