cairn-ai 0.2.5 → 0.2.7
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/.agents/workflows/cairn-memory.md +2 -2
- package/.agents/workflows/cairn-plan.md +7 -3
- package/.agents/workflows/cairn-review.md +2 -2
- package/.agents/workflows/cairn-work.md +7 -3
- package/.claude/agents/explorer.md +2 -2
- package/.claude/agents/worker.md +2 -2
- package/.claude/commands/cairn-memory.md +1 -1
- package/.claude/commands/cairn-plan.md +1 -1
- package/.claude/commands/cairn-review.md +1 -1
- package/.claude/commands/cairn-work.md +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/README.de.md +3 -2
- package/README.es.md +3 -2
- package/README.fr.md +3 -2
- package/README.ja.md +3 -2
- package/README.ko.md +8 -5
- package/README.md +10 -7
- package/README.pt.md +3 -2
- package/README.zh.md +3 -2
- package/agents/explorer.md +2 -2
- package/agents/worker.md +6 -2
- package/commands/cairn-install.md +2 -1
- package/commands/cairn-memory.md +2 -2
- package/commands/cairn-plan.md +8 -4
- package/commands/cairn-review.md +2 -2
- package/commands/cairn-uninstall.md +1 -1
- package/commands/cairn-work.md +7 -3
- package/docs/model-guidance/README.md +16 -4
- package/docs/model-guidance/claude.md +2 -0
- package/docs/model-guidance/codex.md +4 -2
- package/package.json +5 -3
- package/scripts/cairn-cleanup.mjs +67 -0
- package/scripts/cairn-goal-cli.mjs +233 -0
- package/scripts/cairn-goal-evidence.mjs +179 -0
- package/scripts/cairn-goal-operations-evidence.mjs +124 -0
- package/scripts/cairn-goal-operations-mutations.mjs +63 -0
- package/scripts/cairn-goal-operations.mjs +177 -0
- package/scripts/cairn-goal-policy.mjs +33 -0
- package/scripts/cairn-goal-state-validation.mjs +199 -0
- package/scripts/cairn-goal-state.mjs +89 -0
- package/scripts/cairn-goal.mjs +34 -961
- package/scripts/cairn-lifecycle-commands.mjs +200 -0
- package/scripts/cairn-lifecycle-config.mjs +149 -0
- package/scripts/cairn-lifecycle-integrity.mjs +218 -0
- package/scripts/cairn-lifecycle-legacy.mjs +142 -0
- package/scripts/cairn-lifecycle-lock.mjs +205 -0
- package/scripts/cairn-lifecycle-mirror.mjs +91 -0
- package/scripts/cairn-lifecycle-staging.mjs +152 -0
- package/scripts/cairn-lifecycle-transaction-journal.mjs +210 -0
- package/scripts/cairn-lifecycle-transaction-targets.mjs +119 -0
- package/scripts/cairn-lifecycle-transaction.mjs +12 -0
- package/scripts/cairn-lifecycle.mjs +103 -1185
- package/scripts/cairn-paths.mjs +82 -6
- package/scripts/cairn-safe-fs.mjs +61 -35
- package/scripts/cairn-state-context.mjs +148 -0
- package/scripts/cairn-state-hook.mjs +24 -0
- package/scripts/cairn-state-init.mjs +137 -0
- package/scripts/cairn-state.mjs +15 -269
- package/scripts/cairn-toolcheck-detect.mjs +229 -0
- package/scripts/cairn-toolcheck-runtime.mjs +178 -0
- package/scripts/cairn-toolcheck.mjs +79 -424
- package/scripts/cairn.mjs +49 -27
- package/skills/cairn-memory/SKILL.md +4 -4
- package/skills/cairn-plan/SKILL.md +29 -10
- package/skills/cairn-review/SKILL.md +5 -5
- package/skills/cairn-work/SKILL.md +35 -22
- package/templates/PLAN.md +3 -1
- package/templates/work-plan.md +21 -0
- package/scripts/cairn-state.sh +0 -167
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
3
|
+
import { delimiter, isAbsolute, join, posix, resolve, win32 } from "node:path";
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_TIMEOUT_MS = 10_000;
|
|
6
|
+
|
|
7
|
+
export function inspectCommand(requirement, {
|
|
8
|
+
root = process.cwd(),
|
|
9
|
+
runner = run,
|
|
10
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
11
|
+
localChecker = localExecutableAvailable,
|
|
12
|
+
platform = process.platform,
|
|
13
|
+
} = {}) {
|
|
14
|
+
const candidates = commandCandidates(requirement.command, root, platform);
|
|
15
|
+
const localOnly = requirement.localOnly || isRepositoryCommand(requirement.command, root);
|
|
16
|
+
|
|
17
|
+
if (!localOnly) {
|
|
18
|
+
let diagnostic = null;
|
|
19
|
+
for (const candidate of systemCommandCandidates(requirement.command, platform)) {
|
|
20
|
+
diagnostic = execute(candidate, requirement.args, { root, runner, timeoutMs });
|
|
21
|
+
if (diagnostic.status === 0) {
|
|
22
|
+
return {
|
|
23
|
+
ok: true,
|
|
24
|
+
availability: "verified",
|
|
25
|
+
source: "system",
|
|
26
|
+
candidate,
|
|
27
|
+
diagnostic,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const candidate of candidates.slice(1)) {
|
|
33
|
+
if (localChecker(candidate)) {
|
|
34
|
+
return {
|
|
35
|
+
ok: true,
|
|
36
|
+
availability: "discovered",
|
|
37
|
+
source: "repository",
|
|
38
|
+
candidate,
|
|
39
|
+
diagnostic,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
availability: diagnostic?.timedOut ? "timeout" : "missing",
|
|
47
|
+
source: null,
|
|
48
|
+
candidate: null,
|
|
49
|
+
diagnostic,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const candidate of repositoryCandidates(requirement.command, root)) {
|
|
54
|
+
if (localChecker(candidate)) {
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
availability: "discovered",
|
|
58
|
+
source: "repository",
|
|
59
|
+
candidate,
|
|
60
|
+
diagnostic: null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
availability: "missing",
|
|
68
|
+
source: null,
|
|
69
|
+
candidate: null,
|
|
70
|
+
diagnostic: null,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function commandOk(command, args, root = process.cwd(), runner = run) {
|
|
75
|
+
return inspectCommand({ name: command, command, args, reason: "tool availability" }, { root, runner }).ok;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function commandCandidates(command, root = process.cwd(), platform = process.platform) {
|
|
79
|
+
const candidates = isRepositoryCommand(command, root)
|
|
80
|
+
? repositoryCandidates(command, root)
|
|
81
|
+
: [
|
|
82
|
+
command,
|
|
83
|
+
join(root, "node_modules", ".bin", command),
|
|
84
|
+
join(root, "vendor", "bin", command),
|
|
85
|
+
join(root, ".cairn", "tools", "bin", command),
|
|
86
|
+
];
|
|
87
|
+
if (platform !== "win32") return [...new Set(candidates)];
|
|
88
|
+
const extensions = [".cmd", ".bat", ".exe"];
|
|
89
|
+
return [...new Set(candidates.flatMap((candidate) => hasWindowsExecutableExtension(candidate) ? [candidate] : [candidate, ...extensions.map((extension) => `${candidate}${extension}`)]))];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function systemCommandCandidates(command, platform = process.platform) {
|
|
93
|
+
if (platform !== "win32" || hasWindowsExecutableExtension(command) || isAbsolute(command)) return [command];
|
|
94
|
+
return [command, `${command}.cmd`, `${command}.bat`, `${command}.exe`];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function localExecutableAvailable(command, platform = process.platform) {
|
|
98
|
+
try {
|
|
99
|
+
const info = statSync(command);
|
|
100
|
+
if (!info.isFile()) return false;
|
|
101
|
+
accessSync(command, platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
102
|
+
return true;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function run(command, args, { cwd = process.cwd(), timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
109
|
+
const startedAt = Date.now();
|
|
110
|
+
const result = spawnSync(command, args, {
|
|
111
|
+
cwd,
|
|
112
|
+
stdio: "ignore",
|
|
113
|
+
encoding: "utf8",
|
|
114
|
+
shell: shouldUseShell(command),
|
|
115
|
+
timeout: timeoutMs,
|
|
116
|
+
env: safeEnvironment(cwd),
|
|
117
|
+
});
|
|
118
|
+
return { ...result, durationMs: Date.now() - startedAt };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function shouldUseShell(command, platform = process.platform) {
|
|
122
|
+
return platform === "win32" && /\.(cmd|bat)$/i.test(command);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function execute(command, args, { root, runner, timeoutMs }) {
|
|
126
|
+
const startedAt = Date.now();
|
|
127
|
+
try {
|
|
128
|
+
const result = runner(command, args, { cwd: root, timeoutMs });
|
|
129
|
+
return {
|
|
130
|
+
status: Number.isInteger(result?.status) ? result.status : null,
|
|
131
|
+
signal: result?.signal ?? null,
|
|
132
|
+
errorCode: result?.error?.code ?? null,
|
|
133
|
+
timedOut: result?.error?.code === "ETIMEDOUT",
|
|
134
|
+
durationMs: Number.isFinite(result?.durationMs) ? result.durationMs : Date.now() - startedAt,
|
|
135
|
+
};
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return {
|
|
138
|
+
status: null,
|
|
139
|
+
signal: null,
|
|
140
|
+
errorCode: error && typeof error === "object" && "code" in error ? String(error.code) : "RUNNER_ERROR",
|
|
141
|
+
timedOut: false,
|
|
142
|
+
durationMs: Date.now() - startedAt,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function repositoryCandidates(command, root) {
|
|
148
|
+
const normalizedCommand = command.replace(/^\.\//, "");
|
|
149
|
+
const direct = isAbsolute(command) ? command : resolve(root, normalizedCommand);
|
|
150
|
+
return [...new Set([direct])];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function isRepositoryCommand(command, root) {
|
|
154
|
+
if (/^(?:\.\.?[\\/])/.test(command)) return true;
|
|
155
|
+
if (/^(?:gradlew|gradlew\.bat|mvnw|mvnw\.cmd|mvnw\.bat)$/i.test(command)) return true;
|
|
156
|
+
return isAbsolute(command) && isWithin(root, command);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function isWithin(root, candidate, platform = process.platform) {
|
|
160
|
+
const pathApi = platform === "win32" ? win32 : posix;
|
|
161
|
+
const path = pathApi.relative(pathApi.resolve(root), pathApi.resolve(candidate));
|
|
162
|
+
return path === "" || (!pathApi.isAbsolute(path) && !path.startsWith(`..${pathApi.sep}`) && path !== "..");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function safeEnvironment(root) {
|
|
166
|
+
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
167
|
+
const safePath = (process.env[pathKey] ?? "")
|
|
168
|
+
.split(delimiter)
|
|
169
|
+
.filter(Boolean)
|
|
170
|
+
.filter((entry) => isAbsolute(entry) && !isWithin(root, entry))
|
|
171
|
+
.join(delimiter);
|
|
172
|
+
return { ...process.env, [pathKey]: safePath };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
function hasWindowsExecutableExtension(command) {
|
|
177
|
+
return /\.(cmd|bat|exe)$/i.test(command);
|
|
178
|
+
}
|