aiwcli 0.12.7 → 0.13.0

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 (134) hide show
  1. package/dist/commands/clean.d.ts +7 -0
  2. package/dist/commands/clean.js +17 -8
  3. package/dist/commands/clear.d.ts +85 -0
  4. package/dist/commands/clear.js +455 -347
  5. package/dist/commands/init/index.d.ts +15 -0
  6. package/dist/commands/init/index.js +79 -38
  7. package/dist/lib/gitignore-manager.js +12 -13
  8. package/dist/lib/settings-hierarchy.d.ts +13 -1
  9. package/dist/lib/settings-hierarchy.js +1 -1
  10. package/dist/lib/template-linter.d.ts +4 -0
  11. package/dist/lib/template-linter.js +1 -1
  12. package/dist/lib/tty-detection.d.ts +1 -0
  13. package/dist/lib/tty-detection.js +1 -0
  14. package/dist/templates/CLAUDE.md +27 -0
  15. package/dist/templates/_shared/.claude/settings.json +7 -7
  16. package/dist/templates/_shared/.claude/{commands/handoff.md → skills/handoff/SKILL.md} +4 -3
  17. package/dist/templates/_shared/.claude/{commands/handoff-resume.md → skills/handoff-resume/SKILL.md} +3 -2
  18. package/dist/templates/_shared/.claude/skills/meta-plan/SKILL.md +43 -0
  19. package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
  20. package/dist/templates/_shared/.codex/workflows/meta-plan.md +347 -0
  21. package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
  22. package/dist/templates/_shared/.windsurf/workflows/meta-plan.md +347 -0
  23. package/dist/templates/_shared/hooks-ts/lint_after_edit.ts +59 -0
  24. package/dist/templates/_shared/hooks-ts/session_end.ts +11 -10
  25. package/dist/templates/_shared/hooks-ts/session_start.ts +15 -12
  26. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +12 -12
  27. package/dist/templates/_shared/lib-ts/CLAUDE.md +3 -3
  28. package/dist/templates/_shared/lib-ts/base/constants.ts +324 -306
  29. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +26 -7
  30. package/dist/templates/_shared/lib-ts/base/inference.ts +19 -19
  31. package/dist/templates/_shared/lib-ts/base/lint-dispatch.ts +287 -0
  32. package/dist/templates/_shared/lib-ts/base/state-io.ts +4 -3
  33. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +3 -3
  34. package/dist/templates/_shared/lib-ts/context/CLAUDE.md +134 -0
  35. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +16 -15
  36. package/dist/templates/_shared/lib-ts/context/context-selector.ts +16 -16
  37. package/dist/templates/_shared/lib-ts/context/context-store.ts +15 -14
  38. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +2 -2
  39. package/dist/templates/_shared/scripts/resolve-run.ts +61 -0
  40. package/dist/templates/_shared/scripts/resolve_context.ts +1 -1
  41. package/dist/templates/_shared/scripts/status_line.ts +100 -94
  42. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/CLAUDE.md +433 -421
  43. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/lib/document-generator.ts +5 -4
  44. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/lib/handoff-reader.ts +2 -1
  45. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/scripts/resume_handoff.ts +6 -6
  46. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/scripts/save_handoff.ts +16 -17
  47. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/workflows/handoff-resume.md +2 -2
  48. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/workflows/handoff.md +3 -3
  49. package/dist/templates/_shared/skills/meta-plan/CLAUDE.md +44 -0
  50. package/dist/templates/_shared/skills/meta-plan/workflows/meta-plan.md +347 -0
  51. package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +1 -1
  52. package/dist/templates/cc-native/.claude/settings.json +86 -57
  53. package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +64 -0
  54. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/format.ts +599 -597
  55. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/index.ts +26 -26
  56. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/tracker.ts +107 -106
  57. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/write.ts +119 -118
  58. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +237 -247
  59. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +76 -74
  60. package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +76 -0
  61. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +163 -156
  62. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +15 -16
  63. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +116 -116
  64. package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +3 -3
  65. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +16 -12
  66. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +2 -3
  67. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +31 -31
  68. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +7 -6
  69. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +9 -7
  70. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +17 -14
  71. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +41 -37
  72. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +43 -33
  73. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +20 -20
  74. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +9 -8
  75. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +4 -3
  76. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +9 -10
  77. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +20 -19
  78. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +329 -329
  79. package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +149 -0
  80. package/dist/templates/cc-native/_cc-native/plan-review/agents/CLAUDE.md +143 -0
  81. package/dist/templates/cc-native/_cc-native/plan-review/agents/PLAN-ORCHESTRATOR.md +213 -0
  82. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
  83. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-EVOLUTION.md +62 -0
  84. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-PATTERNS.md +61 -0
  85. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-STRUCTURE.md +62 -0
  86. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ASSUMPTION-TRACER.md +56 -0
  87. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CLARITY-AUDITOR.md +53 -0
  88. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -0
  89. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-GAPS.md +70 -0
  90. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-ORDERING.md +62 -0
  91. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -0
  92. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -0
  93. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -0
  94. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DEVILS-ADVOCATE.md +56 -0
  95. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -0
  96. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HANDOFF-READINESS.md +59 -0
  97. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -0
  98. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -0
  99. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-DEPENDENCY.md +62 -0
  100. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-FMEA.md +66 -0
  101. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-PREMORTEM.md +71 -0
  102. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-REVERSIBILITY.md +74 -0
  103. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SCOPE-BOUNDARY.md +77 -0
  104. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -0
  105. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SKEPTIC.md +68 -0
  106. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -0
  107. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -0
  108. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -0
  109. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -0
  110. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-COSTS.md +67 -0
  111. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -0
  112. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-COVERAGE.md +74 -0
  113. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-STRENGTH.md +69 -0
  114. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/agent-selection.ts +162 -163
  115. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/corroboration.ts +119 -119
  116. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/graduation.ts +132 -132
  117. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/orchestrator.ts +70 -70
  118. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/output-builder.ts +121 -130
  119. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/plan-questions.ts +101 -102
  120. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/review-pipeline.ts +507 -511
  121. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/agent.ts +73 -74
  122. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/base/base-agent.ts +217 -217
  123. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/index.ts +12 -12
  124. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/claude-agent.ts +66 -66
  125. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/codex-agent.ts +185 -185
  126. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/gemini-agent.ts +39 -39
  127. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/orchestrator-claude-agent.ts +196 -196
  128. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/schemas.ts +201 -201
  129. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/types.ts +23 -23
  130. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/verdict.ts +72 -72
  131. package/dist/templates/cc-native/_cc-native/{workflows → plan-review/workflows}/specdev.md +9 -9
  132. package/oclif.manifest.json +1 -1
  133. package/package.json +6 -5
  134. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +0 -21
@@ -9,8 +9,9 @@
9
9
 
10
10
  import * as fs from "node:fs";
11
11
  import * as path from "node:path";
12
- import { parseIsoTimestamp } from "../base/utils.js";
12
+
13
13
  import { getContextDir } from "../base/constants.js";
14
+ import { parseIsoTimestamp } from "../base/utils.js";
14
15
  import type { ContextState, Task } from "../types.js";
15
16
 
16
17
  const MAX_PLAN_INLINE_CHARS = 30_000;
@@ -44,7 +45,7 @@ export function getModeDisplay(mode: string): string {
44
45
  export function formatRelativeTime(isoTimestamp: string | null): string {
45
46
  if (!isoTimestamp) return "unknown";
46
47
 
47
- let dt = parseIsoTimestamp(isoTimestamp);
48
+ const dt = parseIsoTimestamp(isoTimestamp);
48
49
  if (!dt) return isoTimestamp.slice(0, 16);
49
50
 
50
51
  const now = new Date();
@@ -100,7 +101,7 @@ function readPlanContent(planPath: string): [string | null, boolean, number] {
100
101
 
101
102
  function modeLabel(ctx: ContextState): string {
102
103
  const d = getModeDisplay(ctx.mode ?? "idle");
103
- return d ? d.replace(/^\[|\]$/g, "") : "Active";
104
+ return d ? d.replaceAll(/^\[|\]$/g, "") : "Active";
104
105
  }
105
106
 
106
107
  /**
@@ -119,7 +120,7 @@ export function buildRestoreSections(
119
120
  const savedAt = lastSession.saved_at ?? "";
120
121
  if (savedAt) {
121
122
  const reason = lastSession.save_reason ?? "";
122
- const reasonDisplay = reason ? reason.replace(/_/g, " ") : "unknown";
123
+ const reasonDisplay = reason ? reason.replaceAll('_', " ") : "unknown";
123
124
  sections.push(`**Last session ended:** ${formatRelativeTime(savedAt)} (${reasonDisplay})`);
124
125
  }
125
126
  }
@@ -222,8 +223,8 @@ export function formatHandoffContinuation(ctx: ContextState, projectRoot?: strin
222
223
  } else {
223
224
  lines.push(`*Handoff document not found at \`${handoffPath}\`*`, "");
224
225
  }
225
- } catch (e: any) {
226
- lines.push(`*Handoff document at \`${handoffPath}\` could not be read: ${e}*`, "");
226
+ } catch (error: any) {
227
+ lines.push(`*Handoff document at \`${handoffPath}\` could not be read: ${error}*`, "");
227
228
  }
228
229
 
229
230
  const restore = buildRestoreSections(ctx, projectRoot, true);
@@ -266,8 +267,8 @@ export function formatContextList(contexts: ContextState[]): string {
266
267
  if (contexts.length === 0) return "No active contexts found.";
267
268
 
268
269
  const lines = ["## Active Contexts\n"];
269
- for (let i = 0; i < contexts.length; i++) {
270
- const ctx = contexts[i]!;
270
+ for (const [i, context_] of contexts.entries()) {
271
+ const ctx = context_!;
271
272
  const timeStr = formatRelativeTime(ctx.last_active);
272
273
  const md = getModeDisplay(ctx.mode ?? "idle");
273
274
  const si = md ? ` ${md}` : "";
@@ -349,11 +350,11 @@ export function formatContextPickerStderr(contexts: ContextState[]): string {
349
350
  ];
350
351
 
351
352
  let selectableCount = 0;
352
- for (let i = 0; i < contexts.length; i++) {
353
- const ctx = contexts[i]!;
353
+ for (const [i, context_] of contexts.entries()) {
354
+ const ctx = context_!;
354
355
  const timeStr = formatRelativeTime(ctx.last_active);
355
356
  const mode = ctx.mode ?? "idle";
356
- const isSelectable = mode === "active" || !!ctx.handoff_path;
357
+ const isSelectable = mode === "active" || Boolean(ctx.handoff_path);
357
358
  if (isSelectable) selectableCount++;
358
359
 
359
360
  let status = "";
@@ -451,7 +452,7 @@ const KNOWN_FOLDERS: Record<string, string> = {
451
452
  "notes": "Analysis files, reports, and documentation that don't belong in the codebase",
452
453
  };
453
454
 
454
- function collectFolderPath(contextId: string, contextDir: string, state: ContextState): string | null {
455
+ function collectFolderPath(contextId: string, contextDir: string, _state: ContextState): string | null {
455
456
  if (!fs.existsSync(contextDir)) return null;
456
457
  return `**Context folder:** \`${contextDir}\`\n**State file:** \`${path.join(contextDir, "state.json")}\` — contains session history, task records, plan/handoff metadata`;
457
458
  }
@@ -485,7 +486,7 @@ function countFilesRecursive(dirPath: string): number {
485
486
  return count;
486
487
  }
487
488
 
488
- function collectFolderInventory(contextId: string, contextDir: string, state: ContextState): string | null {
489
+ function collectFolderInventory(contextId: string, contextDir: string, _state: ContextState): string | null {
489
490
  if (!fs.existsSync(contextDir)) return null;
490
491
  let entries: fs.Dirent[];
491
492
  try {
@@ -519,7 +520,7 @@ function collectSessionStats(contextId: string, contextDir: string, state: Conte
519
520
  transcriptCount = files.length;
520
521
  if (files.length > 1) {
521
522
  const oldest = files[0]!.slice(0, 10);
522
- const newest = files[files.length - 1]!.slice(0, 10);
523
+ const newest = files.at(-1)!.slice(0, 10);
523
524
  if (oldest !== newest) timeRange = ` (${oldest} to ${newest})`;
524
525
  }
525
526
  } catch { /* ignore */ }
@@ -532,7 +533,7 @@ function collectSessionStats(contextId: string, contextDir: string, state: Conte
532
533
  return line;
533
534
  }
534
535
 
535
- function collectNotesGuidance(contextId: string, contextDir: string, state: ContextState): string | null {
536
+ function collectNotesGuidance(contextId: string, contextDir: string, _state: ContextState): string | null {
536
537
  const notesDir = path.join(contextDir, "notes");
537
538
  return `**Notes:** Put notes and files that don't belong in the codebase here. Reference them in other documents as needed: \`${notesDir}\``;
538
539
  }
@@ -14,6 +14,15 @@
14
14
  */
15
15
 
16
16
  import * as crypto from "node:crypto";
17
+
18
+ import {
19
+ formatActiveContextReminder,
20
+ formatContextCreated,
21
+ formatContextPickerStderr,
22
+ formatCommandFeedback,
23
+ formatHandoffContinuation,
24
+ formatPlanContinuation,
25
+ } from "./context-formatter.js";
17
26
  import {
18
27
  getContext,
19
28
  getAllContexts,
@@ -25,18 +34,9 @@ import {
25
34
  updateMode,
26
35
  determineArtifactType,
27
36
  } from "./context-store.js";
28
- import {
29
- formatActiveContextReminder,
30
- formatContextCreated,
31
- formatContextPickerStderr,
32
- formatCommandFeedback,
33
- formatHandoffContinuation,
34
- formatPlanContinuation,
35
- formatActiveContinuation,
36
- } from "./context-formatter.js";
37
37
  import { normalizePlanContent } from "./plan-manager.js";
38
+ import { logDebug, logInfo, logError } from "../base/logger.js";
38
39
  import { isInternalCall } from "../base/subprocess-utils.js";
39
- import { logDebug, logInfo, logWarn, logError } from "../base/logger.js";
40
40
  import type { ContextState, CaretCommand } from "../types.js";
41
41
 
42
42
  /** Minimum characters required for new context description. */
@@ -277,9 +277,9 @@ function matchPlanContent(prompt: string, hasPlanContexts: ContextState[]): Cont
277
277
  }
278
278
 
279
279
  // Tier 4 (legacy): Signature match
280
- const promptHead = prompt.slice(0, 500);
280
+ const promptHead = new Set(prompt.slice(0, 500));
281
281
  for (const ctx of hasPlanContexts) {
282
- if (ctx.plan_signature && promptHead.includes(ctx.plan_signature)) {
282
+ if (ctx.plan_signature && promptHead.has(ctx.plan_signature)) {
283
283
  logDebug("context_selector", `Tier 4 legacy signature match: ${ctx.id}`);
284
284
  return ctx;
285
285
  }
@@ -302,8 +302,8 @@ function createNewContext(
302
302
  newCtx.mode = "active";
303
303
  logInfo("context_selector", `Auto-created context: ${newCtx.id}`);
304
304
  return [newCtx.id, "auto_created", formatContextCreated(newCtx)];
305
- } catch (e: any) {
306
- logError("context_selector", `Primary context creation failed: ${e}`);
305
+ } catch (error: any) {
306
+ logError("context_selector", `Primary context creation failed: ${error}`);
307
307
  try {
308
308
  const now = new Date();
309
309
  const yy = String(now.getFullYear()).slice(2);
@@ -323,8 +323,8 @@ function createNewContext(
323
323
  newCtx.mode = "active";
324
324
  logInfo("context_selector", `Fallback context created: ${newCtx.id}`);
325
325
  return [newCtx.id, "auto_created_fallback", formatContextCreated(newCtx)];
326
- } catch (e2: any) {
327
- logError("context_selector", `ALL context creation failed: ${e2}`);
326
+ } catch (error: any) {
327
+ logError("context_selector", `ALL context creation failed: ${error}`);
328
328
  return [null, "creation_failed", null];
329
329
  }
330
330
  }
@@ -9,7 +9,7 @@
9
9
 
10
10
  import * as fs from "node:fs";
11
11
  import * as path from "node:path";
12
- import { readStateJson, writeStateJson, toDict, dictToState } from "../base/state-io.js";
12
+
13
13
  import { atomicWrite } from "../base/atomic-write.js";
14
14
  import {
15
15
  getContextDir,
@@ -20,7 +20,8 @@ import {
20
20
  getArchiveIndexPath,
21
21
  validateContextId,
22
22
  } from "../base/constants.js";
23
- import { logDebug, logInfo, logWarn, logError, setContextPath } from "../base/logger.js";
23
+ import { logInfo, logWarn, logError, setContextPath } from "../base/logger.js";
24
+ import { readStateJson, writeStateJson } from "../base/state-io.js";
24
25
  import { nowIso, generateContextId } from "../base/utils.js";
25
26
  import type { ContextState, IndexFile, IndexEntry, Mode } from "../types.js";
26
27
 
@@ -77,8 +78,8 @@ function loadIndex(projectRoot?: string): IndexFile {
77
78
  try {
78
79
  const raw = fs.readFileSync(indexPath, "utf-8");
79
80
  return JSON.parse(raw) as IndexFile;
80
- } catch (e: any) {
81
- logWarn("context_store", `Failed to read index, recreating: ${e}`);
81
+ } catch (error: any) {
82
+ logWarn("context_store", `Failed to read index, recreating: ${error}`);
82
83
  }
83
84
  }
84
85
  return { version: INDEX_VERSION, updated_at: nowIso(), sessions: {}, contexts: {} };
@@ -145,8 +146,8 @@ function migrateContextJson(contextId: string, projectRoot?: string): ContextSta
145
146
  last_session: null,
146
147
  tasks: [],
147
148
  };
148
- } catch (e: any) {
149
- logWarn("context_store", `Failed to migrate context.json for '${contextId}': ${e}`);
149
+ } catch (error: any) {
150
+ logWarn("context_store", `Failed to migrate context.json for '${contextId}': ${error}`);
150
151
  return null;
151
152
  }
152
153
  }
@@ -556,8 +557,8 @@ export function archiveContext(contextId: string, projectRoot?: string): Context
556
557
 
557
558
  try {
558
559
  fs.renameSync(sourceDir, archiveDest);
559
- } catch (e: any) {
560
- logError("context_store", `Failed to move context to archive: ${e}`);
560
+ } catch (error: any) {
561
+ logError("context_store", `Failed to move context to archive: ${error}`);
561
562
  return null;
562
563
  }
563
564
 
@@ -647,8 +648,8 @@ function updateArchiveIndex(state: ContextState, projectRoot?: string): boolean
647
648
  if (fs.existsSync(archiveIndexPath)) {
648
649
  try {
649
650
  archiveIndex = JSON.parse(fs.readFileSync(archiveIndexPath, "utf-8"));
650
- } catch (e: any) {
651
- logWarn("context_store", `Failed to read archive index, recreating: ${e}`);
651
+ } catch (error_: any) {
652
+ logWarn("context_store", `Failed to read archive index, recreating: ${error_}`);
652
653
  }
653
654
  }
654
655
 
@@ -675,8 +676,8 @@ function restoreFromArchive(contextId: string, projectRoot?: string): ContextSta
675
676
 
676
677
  try {
677
678
  fs.renameSync(archiveDir, activeDir);
678
- } catch (e: any) {
679
- logError("context_store", `Failed to restore context from archive: ${e}`);
679
+ } catch (error: any) {
680
+ logError("context_store", `Failed to restore context from archive: ${error}`);
680
681
  return null;
681
682
  }
682
683
 
@@ -705,8 +706,8 @@ function removeFromArchiveIndex(contextId: string, projectRoot?: string): boolea
705
706
  }
706
707
  }
707
708
  return true;
708
- } catch (e: any) {
709
- logWarn("context_store", `Failed to read archive index: ${e}`);
709
+ } catch (error: any) {
710
+ logWarn("context_store", `Failed to read archive index: ${error}`);
710
711
  return false;
711
712
  }
712
713
  }
@@ -13,10 +13,9 @@ import * as fs from "node:fs";
13
13
  import * as path from "node:path";
14
14
 
15
15
  import { atomicWrite } from "../base/atomic-write.js";
16
- import { getContextDir, getContextPlansDir, sanitizeTitle } from "../base/constants.js";
16
+ import { getContextPlansDir, sanitizeTitle } from "../base/constants.js";
17
17
  import { logDebug, logInfo, logWarn, logError } from "../base/logger.js";
18
18
  import { generateSlug } from "../base/utils.js";
19
- import type { ContextState } from "../types.js";
20
19
 
21
20
  // ---------------------------------------------------------------------------
22
21
  // Plan archival
@@ -144,6 +143,7 @@ export function findLatestPlan(
144
143
  // 1. Check state.json plan_path first
145
144
  try {
146
145
  // Dynamic import to avoid circular dependency at module level
146
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, no-undef -- dynamic require to avoid circular dependency
147
147
  const stateIo = require("../base/state-io.js");
148
148
  const state = stateIo.readStateJson(contextId, projectRoot);
149
149
  if (state?.plan_path && fs.existsSync(state.plan_path)) {
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Cross-platform project root resolver for hook and status line commands.
4
+ *
5
+ * Finds the project root (via git or .aiwcli/ anchor walk-up), then spawns
6
+ * the target script with cwd set to the root. stdin/stdout/stderr pass through.
7
+ *
8
+ * Install: ~/.aiwcli/bin/resolve-run.ts (global, always findable via ~)
9
+ * Usage: bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/scripts/status_line.ts
10
+ *
11
+ * Works on: bash, zsh, PowerShell, cmd (anywhere bun + ~ expansion works)
12
+ */
13
+ import { execSync } from "node:child_process";
14
+ import * as fs from "node:fs";
15
+ import * as path from "node:path";
16
+
17
+ function findProjectRoot(): string {
18
+ // 1. git (works from any subdirectory of a repo)
19
+ try {
20
+ const root = execSync("git rev-parse --show-toplevel", {
21
+ encoding: "utf-8",
22
+ stdio: ["pipe", "pipe", "pipe"],
23
+ timeout: 2000,
24
+ }).trim();
25
+ if (root && fs.existsSync(path.join(root, ".aiwcli"))) return root;
26
+ } catch { /* not a git repo or git not available */ }
27
+
28
+ // 2. Walk up from cwd to find .aiwcli/ anchor
29
+ let dir = process.cwd();
30
+ while (true) {
31
+ if (fs.existsSync(path.join(dir, ".aiwcli"))) return dir;
32
+ const parent = path.dirname(dir);
33
+ if (parent === dir) break;
34
+ dir = parent;
35
+ }
36
+
37
+ return process.cwd(); // last resort
38
+ }
39
+
40
+ const target = process.argv[2];
41
+ if (!target) {
42
+ process.stderr.write("resolve-run: missing script path argument\n");
43
+ process.exit(1);
44
+ }
45
+
46
+ const root = findProjectRoot();
47
+ const fullPath = path.resolve(root, target);
48
+
49
+ if (!fs.existsSync(fullPath)) {
50
+ process.stderr.write(`resolve-run: script not found: ${fullPath}\n`);
51
+ process.exit(1);
52
+ }
53
+
54
+ const result = Bun.spawnSync(["bun", fullPath], {
55
+ stdin: "inherit",
56
+ stdout: "inherit",
57
+ stderr: "inherit",
58
+ cwd: root,
59
+ });
60
+
61
+ process.exit(result.exitCode);
@@ -11,9 +11,9 @@
11
11
  *
12
12
  * Requires CLAUDE_SESSION_ID environment variable (set by Claude Code).
13
13
  */
14
- import { getContextBySessionId } from "../lib-ts/context/context-store.js";
15
14
  import { getProjectRoot } from "../lib-ts/base/constants.js";
16
15
  import { eprint } from "../lib-ts/base/utils.js";
16
+ import { getContextBySessionId } from "../lib-ts/context/context-store.js";
17
17
 
18
18
  const projectRoot = getProjectRoot(process.cwd());
19
19
  const sessionId = process.env.CLAUDE_SESSION_ID;
@@ -22,7 +22,10 @@ import { findLatestPlan } from "../lib-ts/context/plan-manager.js";
22
22
  // Path setup
23
23
  // ---------------------------------------------------------------------------
24
24
  const SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname);
25
- const OUTPUT_DIR = path.join(".", "_output");
25
+ // Resolve project root from script location (.aiwcli/_shared/scripts/) so paths
26
+ // work even when cwd has drifted via `cd` in a Bash tool call.
27
+ const PROJECT_ROOT = path.resolve(SCRIPT_DIR, "..", "..", "..");
28
+ const OUTPUT_DIR = path.join(PROJECT_ROOT, "_output");
26
29
  const CACHE_DIR = path.join(OUTPUT_DIR, "cache");
27
30
  const STATUSLINE_CACHE = path.join(CACHE_DIR, ".statusline-cache.json");
28
31
 
@@ -266,9 +269,10 @@ function runGit(args: string[], cwd: string, timeout = 2000): string | null {
266
269
  }
267
270
 
268
271
  function getGitStatus(cwd: string): GitStatus | null {
269
- if (runGit(["rev-parse", "--git-dir"], cwd) === null) {
270
- return null;
271
- }
272
+ // One call replaces 6: branch, modified, staged, untracked, ahead/behind, and repo check.
273
+ // porcelain=v2 -b gives structured header lines + per-file XY status codes.
274
+ const porcelain = runGit(["status", "--porcelain=v2", "-b"], cwd);
275
+ if (porcelain === null) return null;
272
276
 
273
277
  const status: GitStatus = {
274
278
  branch: "detached",
@@ -282,36 +286,32 @@ function getGitStatus(cwd: string): GitStatus | null {
282
286
  age_color: GIT_AGE_FRESH,
283
287
  };
284
288
 
285
- // Branch
286
- const branch = runGit(["branch", "--show-current"], cwd);
287
- if (branch) status.branch = branch;
288
-
289
- // Modified files
290
- const diff = runGit(["diff", "--name-only"], cwd);
291
- if (diff) status.modified = diff.split(/\r?\n/).filter(Boolean).length;
292
-
293
- // Staged files
294
- const staged = runGit(["diff", "--cached", "--name-only"], cwd);
295
- if (staged) status.staged = staged.split(/\r?\n/).filter(Boolean).length;
296
-
297
- // Untracked files
298
- const untracked = runGit(["ls-files", "--others", "--exclude-standard"], cwd);
299
- if (untracked) status.untracked = untracked.split(/\r?\n/).filter(Boolean).length;
289
+ for (const line of porcelain.split(/\r?\n/)) {
290
+ if (line.startsWith("# branch.head ")) {
291
+ const b = line.slice(14).trim();
292
+ status.branch = b === "(detached)" ? "detached" : b;
293
+ } else if (line.startsWith("# branch.ab ")) {
294
+ // Format: "+<ahead> -<behind>"
295
+ const m = line.match(/\+(\d+) -(\d+)/);
296
+ if (m) {
297
+ status.ahead = parseInt(m[1]!, 10);
298
+ status.behind = parseInt(m[2]!, 10);
299
+ }
300
+ } else if (line.startsWith("1 ") || line.startsWith("2 ")) {
301
+ // Changed tracked file: "1 XY ..." where X=staged Y=unstaged, '.'=unchanged
302
+ const x = line[2]!;
303
+ const y = line[3]!;
304
+ if (x !== ".") status.staged++;
305
+ if (y !== ".") status.modified++;
306
+ } else if (line.startsWith("? ")) {
307
+ status.untracked++;
308
+ }
309
+ }
300
310
 
301
- // Stash count
311
+ // Stash count (no equivalent in status output)
302
312
  const stash = runGit(["stash", "list"], cwd);
303
313
  if (stash) status.stash_count = stash.split(/\r?\n/).filter(Boolean).length;
304
314
 
305
- // Ahead/behind
306
- const ab = runGit(["rev-list", "--left-right", "--count", "HEAD...@{u}"], cwd);
307
- if (ab) {
308
- const parts = ab.split(/\s+/);
309
- if (parts.length >= 2) {
310
- status.ahead = parseInt(parts[0]!, 10) || 0;
311
- status.behind = parseInt(parts[1]!, 10) || 0;
312
- }
313
- }
314
-
315
315
  // Commit age
316
316
  const log = runGit(["log", "-1", "--format=%ct"], cwd);
317
317
  if (log) {
@@ -345,89 +345,97 @@ function getGitStatus(cwd: string): GitStatus | null {
345
345
  return status;
346
346
  }
347
347
 
348
- function renderGit(mode: string, git: GitStatus, dirName: string): void {
349
- const totalChanged = git.modified + git.staged;
350
- const statusIcon = (totalChanged > 0 || git.untracked > 0) ? "*" : "\u2713";
348
+ function renderGit(mode: string, git: GitStatus | null, dirName: string): void {
349
+ const totalChanged = git ? git.modified + git.staged : 0;
350
+ const statusIcon = git && (totalChanged > 0 || git.untracked > 0) ? "*" : "\u2713";
351
351
 
352
352
  switch (mode) {
353
353
  case "micro": {
354
- let line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
355
- if (git.age_display) {
356
- line += ` ${git.age_color}${git.age_display}${RESET}`;
357
- }
358
- line += " ";
359
- if (statusIcon === "\u2713") {
360
- line += `${GIT_CLEAN}${statusIcon}${RESET}`;
361
- } else {
362
- line += `${GIT_MODIFIED}${statusIcon}${totalChanged}${RESET}`;
354
+ let line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET}`;
355
+ if (git) {
356
+ line += ` ${GIT_VALUE}${git.branch}${RESET}`;
357
+ if (git.age_display) {
358
+ line += ` ${git.age_color}${git.age_display}${RESET}`;
359
+ }
360
+ line += " ";
361
+ if (statusIcon === "\u2713") {
362
+ line += `${GIT_CLEAN}${statusIcon}${RESET}`;
363
+ } else {
364
+ line += `${GIT_MODIFIED}${statusIcon}${totalChanged}${RESET}`;
365
+ }
363
366
  }
364
367
  console.log(line);
365
-
368
+
366
369
  break;
367
370
  }
368
371
  case "mini": {
369
- let line =
370
- `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET} ` +
371
- `${SLATE_600}\u2502${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
372
- if (git.age_display) {
373
- line += ` ${SLATE_600}\u2502${RESET} ${git.age_color}${git.age_display}${RESET}`;
374
- }
375
- line += ` ${SLATE_600}\u2502${RESET} `;
376
- if (statusIcon === "\u2713") {
377
- line += `${GIT_CLEAN}${statusIcon}${RESET}`;
378
- } else {
379
- line += `${GIT_MODIFIED}${statusIcon}${totalChanged}${RESET}`;
380
- if (git.untracked > 0) {
381
- line += ` ${GIT_ADDED}+${git.untracked}${RESET}`;
372
+ let line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET}`;
373
+ if (git) {
374
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
375
+ if (git.age_display) {
376
+ line += ` ${SLATE_600}\u2502${RESET} ${git.age_color}${git.age_display}${RESET}`;
377
+ }
378
+ line += ` ${SLATE_600}\u2502${RESET} `;
379
+ if (statusIcon === "\u2713") {
380
+ line += `${GIT_CLEAN}${statusIcon}${RESET}`;
381
+ } else {
382
+ line += `${GIT_MODIFIED}${statusIcon}${totalChanged}${RESET}`;
383
+ if (git.untracked > 0) {
384
+ line += ` ${GIT_ADDED}+${git.untracked}${RESET}`;
385
+ }
382
386
  }
383
387
  }
384
388
  console.log(line);
385
-
389
+
386
390
  break;
387
391
  }
388
392
  case "nano": {
389
- let line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET} ${GIT_VALUE}${git.branch}${RESET} `;
390
- if (statusIcon === "\u2713") {
391
- line += `${GIT_CLEAN}\u2713${RESET}`;
392
- } else {
393
- line += `${GIT_MODIFIED}*${totalChanged}${RESET}`;
393
+ let line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET}`;
394
+ if (git) {
395
+ line += ` ${GIT_VALUE}${git.branch}${RESET} `;
396
+ if (statusIcon === "\u2713") {
397
+ line += `${GIT_CLEAN}\u2713${RESET}`;
398
+ } else {
399
+ line += `${GIT_MODIFIED}*${totalChanged}${RESET}`;
400
+ }
394
401
  }
395
402
  console.log(line);
396
-
403
+
397
404
  break;
398
405
  }
399
406
  default: {
400
- let line =
401
- `${GIT_PRIMARY}\u25C8${RESET} ${GIT_PRIMARY}PWD:${RESET} ${GIT_DIR}${dirName}${RESET} ` +
402
- `${SLATE_600}\u2502${RESET} ` +
403
- `${GIT_PRIMARY}Branch:${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
404
- if (git.age_display) {
405
- line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Age:${RESET} ${git.age_color}${git.age_display}${RESET}`;
406
- }
407
- if (git.stash_count > 0) {
408
- line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Stash:${RESET} ${GIT_STASH}${git.stash_count}${RESET}`;
409
- }
410
-
411
- if (totalChanged > 0 || git.untracked > 0) {
412
- line += ` ${SLATE_600}\u2502${RESET} `;
413
- if (totalChanged > 0) {
414
- line += `${GIT_PRIMARY}Mod:${RESET} ${GIT_MODIFIED}${totalChanged}${RESET}`;
407
+ let line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_PRIMARY}PWD:${RESET} ${GIT_DIR}${dirName}${RESET}`;
408
+ if (git) {
409
+ line += ` ${SLATE_600}\u2502${RESET} ` +
410
+ `${GIT_PRIMARY}Branch:${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
411
+ if (git.age_display) {
412
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Age:${RESET} ${git.age_color}${git.age_display}${RESET}`;
415
413
  }
416
- if (git.untracked > 0) {
417
- if (totalChanged > 0) line += " ";
418
- line += `${GIT_PRIMARY}New:${RESET} ${GIT_ADDED}${git.untracked}${RESET}`;
414
+ if (git.stash_count > 0) {
415
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Stash:${RESET} ${GIT_STASH}${git.stash_count}${RESET}`;
419
416
  }
420
- } else {
421
- line += ` ${SLATE_600}\u2502${RESET} ${GIT_CLEAN}\u2713 clean${RESET}`;
422
- }
423
417
 
424
- if (git.ahead > 0 || git.behind > 0) {
425
- line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Sync:${RESET} `;
426
- if (git.ahead > 0) {
427
- line += `${GIT_CLEAN}\u2191${git.ahead}${RESET}`;
418
+ if (totalChanged > 0 || git.untracked > 0) {
419
+ line += ` ${SLATE_600}\u2502${RESET} `;
420
+ if (totalChanged > 0) {
421
+ line += `${GIT_PRIMARY}Mod:${RESET} ${GIT_MODIFIED}${totalChanged}${RESET}`;
422
+ }
423
+ if (git.untracked > 0) {
424
+ if (totalChanged > 0) line += " ";
425
+ line += `${GIT_PRIMARY}New:${RESET} ${GIT_ADDED}${git.untracked}${RESET}`;
426
+ }
427
+ } else {
428
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_CLEAN}\u2713 clean${RESET}`;
428
429
  }
429
- if (git.behind > 0) {
430
- line += `${GIT_STASH}\u2193${git.behind}${RESET}`;
430
+
431
+ if (git.ahead > 0 || git.behind > 0) {
432
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Sync:${RESET} `;
433
+ if (git.ahead > 0) {
434
+ line += `${GIT_CLEAN}\u2191${git.ahead}${RESET}`;
435
+ }
436
+ if (git.behind > 0) {
437
+ line += `${GIT_STASH}\u2193${git.behind}${RESET}`;
438
+ }
431
439
  }
432
440
  }
433
441
  console.log(line);
@@ -687,11 +695,9 @@ function main(): void {
687
695
  // Render context section
688
696
  renderContext(mode, contextPct, contextK, maxK, timeDisplay, modelName);
689
697
 
690
- // Render git section
698
+ // Render PWD + git section (PWD always shown, git stats only when in a repo)
691
699
  const git = getGitStatus(currentDir);
692
- if (git) {
693
- renderGit(mode, git, dirName);
694
- }
700
+ renderGit(mode, git, dirName);
695
701
 
696
702
  // Render context manager line (line 3) with separator
697
703
  console.log(SEPARATOR);