aiwcli 0.12.6 → 0.12.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dev.cmd +3 -3
- package/bin/dev.js +16 -16
- package/bin/run.cmd +3 -3
- package/bin/run.js +21 -21
- package/dist/commands/branch.js +7 -2
- package/dist/lib/bmad-installer.js +37 -37
- package/dist/lib/terminal.d.ts +2 -0
- package/dist/lib/terminal.js +57 -7
- package/dist/templates/CLAUDE.md +232 -205
- package/dist/templates/_shared/.claude/settings.json +65 -65
- package/dist/templates/_shared/.claude/{commands/handoff.md → skills/handoff/SKILL.md} +13 -12
- package/dist/templates/_shared/.claude/{commands/handoff-resume.md → skills/handoff-resume/SKILL.md} +13 -12
- package/dist/templates/_shared/.codex/workflows/handoff.md +226 -226
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +226 -226
- package/dist/templates/_shared/handoff-system/CLAUDE.md +15 -3
- package/dist/templates/_shared/handoff-system/lib/document-generator.ts +215 -215
- package/dist/templates/_shared/handoff-system/lib/handoff-reader.ts +158 -158
- package/dist/templates/_shared/handoff-system/scripts/resume_handoff.ts +373 -373
- package/dist/templates/_shared/handoff-system/scripts/save_handoff.ts +469 -469
- package/dist/templates/_shared/handoff-system/workflows/handoff-resume.md +66 -66
- package/dist/templates/_shared/handoff-system/workflows/handoff.md +254 -254
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -2
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -159
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -147
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +128 -128
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -49
- package/dist/templates/_shared/hooks-ts/session_end.ts +196 -196
- package/dist/templates/_shared/hooks-ts/session_start.ts +163 -163
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -48
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -74
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +93 -93
- package/dist/templates/_shared/lib-ts/CLAUDE.md +367 -367
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -138
- package/dist/templates/_shared/lib-ts/base/constants.ts +24 -6
- package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -58
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +582 -582
- package/dist/templates/_shared/lib-ts/base/inference.ts +301 -301
- package/dist/templates/_shared/lib-ts/base/logger.ts +247 -247
- package/dist/templates/_shared/lib-ts/base/state-io.ts +202 -202
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -184
- package/dist/templates/_shared/lib-ts/base/utils.ts +184 -184
- package/dist/templates/_shared/lib-ts/context/CLAUDE.md +134 -0
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +566 -566
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +524 -524
- package/dist/templates/_shared/lib-ts/context/context-store.ts +712 -712
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +312 -312
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -185
- package/dist/templates/_shared/lib-ts/package.json +20 -20
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +102 -102
- package/dist/templates/_shared/lib-ts/templates/plan-context.ts +58 -58
- package/dist/templates/_shared/lib-ts/tsconfig.json +13 -13
- package/dist/templates/_shared/lib-ts/types.ts +186 -186
- package/dist/templates/_shared/scripts/resolve_context.ts +33 -33
- package/dist/templates/_shared/scripts/status_line.ts +687 -690
- package/dist/templates/cc-native/.claude/commands/cc-native/rlm/ask.md +136 -136
- package/dist/templates/cc-native/.claude/commands/cc-native/rlm/index.md +21 -21
- package/dist/templates/cc-native/.claude/commands/cc-native/rlm/overview.md +56 -56
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -10
- package/dist/templates/cc-native/.claude/settings.json +3 -2
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -8
- package/dist/templates/cc-native/CC-NATIVE-README.md +189 -189
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +304 -304
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +143 -143
- package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +213 -213
- package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -70
- package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +64 -0
- package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/format.ts +1 -1
- package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/write.ts +2 -2
- package/dist/templates/cc-native/_cc-native/cc-native.config.json +96 -96
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +14 -24
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +1 -1
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +54 -54
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +51 -51
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -53
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -61
- package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +76 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +9 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +319 -319
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -144
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -57
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -83
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +79 -79
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +4 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -168
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +80 -80
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -41
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/CLAUDE.md +480 -480
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +287 -287
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +148 -148
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +54 -54
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +58 -58
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +208 -208
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +460 -460
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +446 -446
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +280 -280
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +274 -274
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +201 -201
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +278 -278
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +184 -184
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +275 -275
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -18
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +1 -1
- package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +149 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/CLAUDE.md +143 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/PLAN-ORCHESTRATOR.md +213 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-EVOLUTION.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-PATTERNS.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-STRUCTURE.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ASSUMPTION-TRACER.md +56 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CLARITY-AUDITOR.md +53 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-GAPS.md +70 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-ORDERING.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DEVILS-ADVOCATE.md +56 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HANDOFF-READINESS.md +59 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-DEPENDENCY.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-FMEA.md +66 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-PREMORTEM.md +71 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-REVERSIBILITY.md +74 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SCOPE-BOUNDARY.md +77 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SKEPTIC.md +68 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-COSTS.md +67 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-COVERAGE.md +74 -0
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-STRENGTH.md +69 -0
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/agent-selection.ts +3 -3
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/corroboration.ts +1 -1
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/graduation.ts +1 -1
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/orchestrator.ts +2 -2
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/output-builder.ts +3 -3
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/plan-questions.ts +6 -6
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/review-pipeline.ts +15 -15
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/agent.ts +5 -5
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/base/base-agent.ts +4 -4
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/claude-agent.ts +4 -4
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/codex-agent.ts +6 -6
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/gemini-agent.ts +1 -1
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/orchestrator-claude-agent.ts +4 -4
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/types.ts +3 -3
- package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/verdict.ts +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +108 -108
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +0 -21
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +0 -3
- /package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/index.ts +0 -0
- /package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/tracker.ts +0 -0
- /package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/index.ts +0 -0
- /package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/schemas.ts +0 -0
- /package/dist/templates/cc-native/_cc-native/{workflows → plan-review/workflows}/specdev.md +0 -0
|
@@ -1,138 +1,138 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-platform atomic file writes with security.
|
|
3
|
-
* Crash-safe writes by writing to temp file then renaming.
|
|
4
|
-
* NOT for concurrent access — assumes single-session-per-context.
|
|
5
|
-
* See SPEC.md §4
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as crypto from "node:crypto";
|
|
9
|
-
import * as fs from "node:fs";
|
|
10
|
-
import * as _os from "node:os";
|
|
11
|
-
import * as path from "node:path";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Write file atomically with retry logic.
|
|
15
|
-
* Creates temp file, writes, fsyncs, renames.
|
|
16
|
-
* Returns [success, error].
|
|
17
|
-
* See SPEC.md §4.2
|
|
18
|
-
*/
|
|
19
|
-
export function atomicWrite(
|
|
20
|
-
filePath: string,
|
|
21
|
-
content: string,
|
|
22
|
-
maxAttempts = 2,
|
|
23
|
-
backoffMs: number[] = [500, 1000],
|
|
24
|
-
): [boolean, null | string] {
|
|
25
|
-
// Ensure parent directory exists
|
|
26
|
-
const dir = path.dirname(filePath);
|
|
27
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
28
|
-
|
|
29
|
-
const stem = path.basename(filePath, path.extname(filePath));
|
|
30
|
-
|
|
31
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
32
|
-
const tmpName = `.${stem}_${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
33
|
-
const tmpPath = path.join(dir, tmpName);
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
// Write to temp file
|
|
37
|
-
const fd = fs.openSync(tmpPath, "w");
|
|
38
|
-
try {
|
|
39
|
-
fs.writeSync(fd, content, undefined, "utf-8");
|
|
40
|
-
fs.fsyncSync(fd);
|
|
41
|
-
} finally {
|
|
42
|
-
fs.closeSync(fd);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Set restrictive permissions (best-effort)
|
|
46
|
-
try {
|
|
47
|
-
fs.chmodSync(tmpPath, 0o600);
|
|
48
|
-
} catch {
|
|
49
|
-
// May fail on some filesystems
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Atomic rename (cross-platform on modern Node/Bun)
|
|
53
|
-
fs.renameSync(tmpPath, filePath);
|
|
54
|
-
|
|
55
|
-
return [true, null];
|
|
56
|
-
} catch (error: any) {
|
|
57
|
-
// Clean up temp file
|
|
58
|
-
try {
|
|
59
|
-
fs.unlinkSync(tmpPath);
|
|
60
|
-
} catch {
|
|
61
|
-
// Best-effort cleanup
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (attempt < maxAttempts - 1) {
|
|
65
|
-
const waitMs = backoffMs[Math.min(attempt, backoffMs.length - 1)] ?? backoffMs.at(-1) ?? 500;
|
|
66
|
-
sleepSync(waitMs);
|
|
67
|
-
} else {
|
|
68
|
-
const errType = error?.constructor?.name ?? "Error";
|
|
69
|
-
const errMsg = String(error).split("\n")[0]?.slice(0, 200) ?? "";
|
|
70
|
-
return [false, `${errType}: ${errMsg}`];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return [false, "Max retry attempts exceeded"];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Append to file with retry logic.
|
|
80
|
-
* For JSONL files where each line is independent.
|
|
81
|
-
* See SPEC.md §4.3
|
|
82
|
-
*/
|
|
83
|
-
export function atomicAppend(
|
|
84
|
-
filePath: string,
|
|
85
|
-
content: string,
|
|
86
|
-
maxAttempts = 2,
|
|
87
|
-
backoffMs: number[] = [500, 1000],
|
|
88
|
-
): [boolean, null | string] {
|
|
89
|
-
// Ensure parent directory exists
|
|
90
|
-
const dir = path.dirname(filePath);
|
|
91
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
92
|
-
|
|
93
|
-
const isNewFile = !fs.existsSync(filePath);
|
|
94
|
-
|
|
95
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
96
|
-
try {
|
|
97
|
-
const fd = fs.openSync(filePath, "a");
|
|
98
|
-
try {
|
|
99
|
-
fs.writeSync(fd, content, undefined, "utf-8");
|
|
100
|
-
fs.fsyncSync(fd);
|
|
101
|
-
} finally {
|
|
102
|
-
fs.closeSync(fd);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Set permissions on newly created files (best-effort)
|
|
106
|
-
if (isNewFile) {
|
|
107
|
-
try {
|
|
108
|
-
fs.chmodSync(filePath, 0o600);
|
|
109
|
-
} catch {
|
|
110
|
-
// May fail on some filesystems
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return [true, null];
|
|
115
|
-
} catch (error: any) {
|
|
116
|
-
if (attempt < maxAttempts - 1) {
|
|
117
|
-
const waitMs = backoffMs[Math.min(attempt, backoffMs.length - 1)] ?? backoffMs.at(-1) ?? 500;
|
|
118
|
-
sleepSync(waitMs);
|
|
119
|
-
} else {
|
|
120
|
-
const errType = error?.constructor?.name ?? "Error";
|
|
121
|
-
const errMsg = String(error).split("\n")[0]?.slice(0, 200) ?? "";
|
|
122
|
-
return [false, `${errType}: ${errMsg}`];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return [false, "Max retry attempts exceeded"];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Synchronous sleep for retry backoff.
|
|
132
|
-
* Uses Atomics.wait() for CPU-friendly blocking instead of busy-wait.
|
|
133
|
-
*/
|
|
134
|
-
function sleepSync(ms: number): void {
|
|
135
|
-
const sab = new SharedArrayBuffer(4);
|
|
136
|
-
const i32 = new Int32Array(sab);
|
|
137
|
-
Atomics.wait(i32, 0, 0, ms);
|
|
138
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform atomic file writes with security.
|
|
3
|
+
* Crash-safe writes by writing to temp file then renaming.
|
|
4
|
+
* NOT for concurrent access — assumes single-session-per-context.
|
|
5
|
+
* See SPEC.md §4
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as crypto from "node:crypto";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as _os from "node:os";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Write file atomically with retry logic.
|
|
15
|
+
* Creates temp file, writes, fsyncs, renames.
|
|
16
|
+
* Returns [success, error].
|
|
17
|
+
* See SPEC.md §4.2
|
|
18
|
+
*/
|
|
19
|
+
export function atomicWrite(
|
|
20
|
+
filePath: string,
|
|
21
|
+
content: string,
|
|
22
|
+
maxAttempts = 2,
|
|
23
|
+
backoffMs: number[] = [500, 1000],
|
|
24
|
+
): [boolean, null | string] {
|
|
25
|
+
// Ensure parent directory exists
|
|
26
|
+
const dir = path.dirname(filePath);
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
28
|
+
|
|
29
|
+
const stem = path.basename(filePath, path.extname(filePath));
|
|
30
|
+
|
|
31
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
32
|
+
const tmpName = `.${stem}_${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
33
|
+
const tmpPath = path.join(dir, tmpName);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Write to temp file
|
|
37
|
+
const fd = fs.openSync(tmpPath, "w");
|
|
38
|
+
try {
|
|
39
|
+
fs.writeSync(fd, content, undefined, "utf-8");
|
|
40
|
+
fs.fsyncSync(fd);
|
|
41
|
+
} finally {
|
|
42
|
+
fs.closeSync(fd);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Set restrictive permissions (best-effort)
|
|
46
|
+
try {
|
|
47
|
+
fs.chmodSync(tmpPath, 0o600);
|
|
48
|
+
} catch {
|
|
49
|
+
// May fail on some filesystems
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Atomic rename (cross-platform on modern Node/Bun)
|
|
53
|
+
fs.renameSync(tmpPath, filePath);
|
|
54
|
+
|
|
55
|
+
return [true, null];
|
|
56
|
+
} catch (error: any) {
|
|
57
|
+
// Clean up temp file
|
|
58
|
+
try {
|
|
59
|
+
fs.unlinkSync(tmpPath);
|
|
60
|
+
} catch {
|
|
61
|
+
// Best-effort cleanup
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (attempt < maxAttempts - 1) {
|
|
65
|
+
const waitMs = backoffMs[Math.min(attempt, backoffMs.length - 1)] ?? backoffMs.at(-1) ?? 500;
|
|
66
|
+
sleepSync(waitMs);
|
|
67
|
+
} else {
|
|
68
|
+
const errType = error?.constructor?.name ?? "Error";
|
|
69
|
+
const errMsg = String(error).split("\n")[0]?.slice(0, 200) ?? "";
|
|
70
|
+
return [false, `${errType}: ${errMsg}`];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return [false, "Max retry attempts exceeded"];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Append to file with retry logic.
|
|
80
|
+
* For JSONL files where each line is independent.
|
|
81
|
+
* See SPEC.md §4.3
|
|
82
|
+
*/
|
|
83
|
+
export function atomicAppend(
|
|
84
|
+
filePath: string,
|
|
85
|
+
content: string,
|
|
86
|
+
maxAttempts = 2,
|
|
87
|
+
backoffMs: number[] = [500, 1000],
|
|
88
|
+
): [boolean, null | string] {
|
|
89
|
+
// Ensure parent directory exists
|
|
90
|
+
const dir = path.dirname(filePath);
|
|
91
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
92
|
+
|
|
93
|
+
const isNewFile = !fs.existsSync(filePath);
|
|
94
|
+
|
|
95
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
96
|
+
try {
|
|
97
|
+
const fd = fs.openSync(filePath, "a");
|
|
98
|
+
try {
|
|
99
|
+
fs.writeSync(fd, content, undefined, "utf-8");
|
|
100
|
+
fs.fsyncSync(fd);
|
|
101
|
+
} finally {
|
|
102
|
+
fs.closeSync(fd);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Set permissions on newly created files (best-effort)
|
|
106
|
+
if (isNewFile) {
|
|
107
|
+
try {
|
|
108
|
+
fs.chmodSync(filePath, 0o600);
|
|
109
|
+
} catch {
|
|
110
|
+
// May fail on some filesystems
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return [true, null];
|
|
115
|
+
} catch (error: any) {
|
|
116
|
+
if (attempt < maxAttempts - 1) {
|
|
117
|
+
const waitMs = backoffMs[Math.min(attempt, backoffMs.length - 1)] ?? backoffMs.at(-1) ?? 500;
|
|
118
|
+
sleepSync(waitMs);
|
|
119
|
+
} else {
|
|
120
|
+
const errType = error?.constructor?.name ?? "Error";
|
|
121
|
+
const errMsg = String(error).split("\n")[0]?.slice(0, 200) ?? "";
|
|
122
|
+
return [false, `${errType}: ${errMsg}`];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return [false, "Max retry attempts exceeded"];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Synchronous sleep for retry backoff.
|
|
132
|
+
* Uses Atomics.wait() for CPU-friendly blocking instead of busy-wait.
|
|
133
|
+
*/
|
|
134
|
+
function sleepSync(ms: number): void {
|
|
135
|
+
const sab = new SharedArrayBuffer(4);
|
|
136
|
+
const i32 = new Int32Array(sab);
|
|
137
|
+
Atomics.wait(i32, 0, 0, ms);
|
|
138
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
|
-
import * as path from "node:path";
|
|
7
|
+
import * as path from "node:path";
|
|
8
8
|
|
|
9
9
|
import { logWarn } from "./logger.js";
|
|
10
10
|
|
|
@@ -87,9 +87,27 @@ export function validateContextId(contextId: string): string {
|
|
|
87
87
|
return sanitizeContextId(contextId);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Walk up from startDir until a directory containing `.aiwcli/` is found.
|
|
92
|
+
* Returns startDir itself if no anchor is found (fail-safe).
|
|
93
|
+
* See SPEC.md §2.2
|
|
94
|
+
*/
|
|
95
|
+
function findProjectRoot(startDir: string): string {
|
|
96
|
+
let dir = startDir;
|
|
97
|
+
while (true) {
|
|
98
|
+
if (fs.existsSync(path.join(dir, ".aiwcli"))) return dir;
|
|
99
|
+
const parent = path.dirname(dir);
|
|
100
|
+
if (parent === dir) break; // filesystem root reached
|
|
101
|
+
dir = parent;
|
|
102
|
+
}
|
|
103
|
+
return startDir; // fallback: no .aiwcli anchor found
|
|
104
|
+
}
|
|
105
|
+
|
|
90
106
|
/**
|
|
91
107
|
* Get project root from environment or cwd.
|
|
92
|
-
* Priority: CLAUDE_PROJECT_DIR > payload cwd > process.cwd()
|
|
108
|
+
* Priority: CLAUDE_PROJECT_DIR > walk up from payload cwd > walk up from process.cwd()
|
|
109
|
+
* Walks upward to find the nearest .aiwcli/ anchor, so cwd drift (e.g. after
|
|
110
|
+
* `cd packages/cli` in a Bash tool call) doesn't break hook resolution.
|
|
93
111
|
* See SPEC.md §2.2
|
|
94
112
|
*/
|
|
95
113
|
export function getProjectRoot(payloadCwd?: string): string {
|
|
@@ -102,10 +120,10 @@ export function getProjectRoot(payloadCwd?: string): string {
|
|
|
102
120
|
} else {
|
|
103
121
|
return envDir;
|
|
104
122
|
}
|
|
105
|
-
}
|
|
123
|
+
}
|
|
106
124
|
|
|
107
|
-
if (payloadCwd) return payloadCwd;
|
|
108
|
-
return process.cwd();
|
|
125
|
+
if (payloadCwd) return findProjectRoot(payloadCwd);
|
|
126
|
+
return findProjectRoot(process.cwd());
|
|
109
127
|
}
|
|
110
128
|
|
|
111
129
|
// §2.4 — Path functions
|
|
@@ -236,7 +254,7 @@ export function getHandoffFolderPath(
|
|
|
236
254
|
while (fs.existsSync(folder)) {
|
|
237
255
|
folder = path.join(handoffsDir, `${ts}-${counter}`);
|
|
238
256
|
counter++;
|
|
239
|
-
}
|
|
257
|
+
}
|
|
240
258
|
|
|
241
259
|
return folder;
|
|
242
260
|
}
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Capture current git state for session snapshots.
|
|
5
|
-
* All fields are optional — failures are silently ignored.
|
|
6
|
-
*/
|
|
7
|
-
export function getGitState(projectRoot: string): Record<string, any> {
|
|
8
|
-
const gitState: Record<string, any> = {};
|
|
9
|
-
const isWin = process.platform === "win32";
|
|
10
|
-
const opts = {
|
|
11
|
-
cwd: projectRoot,
|
|
12
|
-
timeout: 5000,
|
|
13
|
-
encoding: "utf-8" as const,
|
|
14
|
-
stdio: ["pipe", "pipe", "pipe"] as ["pipe", "pipe", "pipe"],
|
|
15
|
-
shell: isWin,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], opts);
|
|
20
|
-
if (branch) gitState.branch = branch.trim();
|
|
21
|
-
} catch { /* non-fatal */ }
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const status = execFileSync("git", ["status", "--short"], opts);
|
|
25
|
-
if (status) {
|
|
26
|
-
const files = status.trim().split(/\r?\n/)
|
|
27
|
-
.filter(Boolean)
|
|
28
|
-
.slice(0, 10)
|
|
29
|
-
.map(line => line.trimStart().split(/\s+/).slice(1).join(" "));
|
|
30
|
-
if (files.length > 0) gitState.uncommitted_files = files;
|
|
31
|
-
}
|
|
32
|
-
} catch { /* non-fatal */ }
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const log = execFileSync("git", ["log", "-1", "--oneline"], opts);
|
|
36
|
-
if (log) gitState.last_commit_short = log.trim();
|
|
37
|
-
} catch { /* non-fatal */ }
|
|
38
|
-
|
|
39
|
-
return gitState;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Get short git status string for display (e.g., in handoff documents).
|
|
44
|
-
*/
|
|
45
|
-
export function getGitStatusShort(projectRoot?: string): string {
|
|
46
|
-
try {
|
|
47
|
-
const result = execFileSync("git", ["status", "--short"], {
|
|
48
|
-
encoding: "utf-8",
|
|
49
|
-
timeout: 5000,
|
|
50
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
51
|
-
...(projectRoot ? { cwd: projectRoot } : {}),
|
|
52
|
-
shell: process.platform === "win32",
|
|
53
|
-
});
|
|
54
|
-
return result.trim() || "(no changes)";
|
|
55
|
-
} catch {
|
|
56
|
-
return "(git status unavailable)";
|
|
57
|
-
}
|
|
58
|
-
}
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Capture current git state for session snapshots.
|
|
5
|
+
* All fields are optional — failures are silently ignored.
|
|
6
|
+
*/
|
|
7
|
+
export function getGitState(projectRoot: string): Record<string, any> {
|
|
8
|
+
const gitState: Record<string, any> = {};
|
|
9
|
+
const isWin = process.platform === "win32";
|
|
10
|
+
const opts = {
|
|
11
|
+
cwd: projectRoot,
|
|
12
|
+
timeout: 5000,
|
|
13
|
+
encoding: "utf-8" as const,
|
|
14
|
+
stdio: ["pipe", "pipe", "pipe"] as ["pipe", "pipe", "pipe"],
|
|
15
|
+
shell: isWin,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], opts);
|
|
20
|
+
if (branch) gitState.branch = branch.trim();
|
|
21
|
+
} catch { /* non-fatal */ }
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const status = execFileSync("git", ["status", "--short"], opts);
|
|
25
|
+
if (status) {
|
|
26
|
+
const files = status.trim().split(/\r?\n/)
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.slice(0, 10)
|
|
29
|
+
.map(line => line.trimStart().split(/\s+/).slice(1).join(" "));
|
|
30
|
+
if (files.length > 0) gitState.uncommitted_files = files;
|
|
31
|
+
}
|
|
32
|
+
} catch { /* non-fatal */ }
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const log = execFileSync("git", ["log", "-1", "--oneline"], opts);
|
|
36
|
+
if (log) gitState.last_commit_short = log.trim();
|
|
37
|
+
} catch { /* non-fatal */ }
|
|
38
|
+
|
|
39
|
+
return gitState;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get short git status string for display (e.g., in handoff documents).
|
|
44
|
+
*/
|
|
45
|
+
export function getGitStatusShort(projectRoot?: string): string {
|
|
46
|
+
try {
|
|
47
|
+
const result = execFileSync("git", ["status", "--short"], {
|
|
48
|
+
encoding: "utf-8",
|
|
49
|
+
timeout: 5000,
|
|
50
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
51
|
+
...(projectRoot ? { cwd: projectRoot } : {}),
|
|
52
|
+
shell: process.platform === "win32",
|
|
53
|
+
});
|
|
54
|
+
return result.trim() || "(no changes)";
|
|
55
|
+
} catch {
|
|
56
|
+
return "(git status unavailable)";
|
|
57
|
+
}
|
|
58
|
+
}
|