cairn-ai 0.1.2 → 0.1.5

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 (43) hide show
  1. package/.agents/workflows/cairn-doctor.md +1 -1
  2. package/.agents/workflows/cairn-install.md +1 -1
  3. package/.agents/workflows/cairn-memory.md +2 -2
  4. package/.agents/workflows/cairn-plan.md +15 -14
  5. package/.agents/workflows/cairn-review.md +2 -2
  6. package/.agents/workflows/cairn-toolcheck.md +2 -2
  7. package/.agents/workflows/cairn-uninstall.md +1 -1
  8. package/.agents/workflows/cairn-upgrade.md +1 -1
  9. package/.agents/workflows/cairn-work.md +14 -13
  10. package/.claude/agents/explorer.md +2 -0
  11. package/.claude/agents/worker.md +2 -0
  12. package/.claude/commands/cairn-doctor.md +1 -1
  13. package/.claude/commands/cairn-install.md +1 -1
  14. package/.claude/commands/cairn-plan.md +1 -1
  15. package/.claude/commands/cairn-toolcheck.md +2 -2
  16. package/.claude/commands/cairn-uninstall.md +1 -1
  17. package/.claude/commands/cairn-upgrade.md +1 -1
  18. package/.claude/commands/cairn-work.md +1 -1
  19. package/.codex-plugin/plugin.json +5 -3
  20. package/agents/explorer.md +2 -0
  21. package/agents/worker.md +2 -0
  22. package/commands/cairn-doctor.md +1 -1
  23. package/commands/cairn-install.md +1 -1
  24. package/commands/cairn-memory.md +3 -3
  25. package/commands/cairn-plan.md +16 -15
  26. package/commands/cairn-review.md +3 -3
  27. package/commands/cairn-toolcheck.md +2 -2
  28. package/commands/cairn-uninstall.md +1 -1
  29. package/commands/cairn-upgrade.md +1 -1
  30. package/commands/cairn-work.md +15 -14
  31. package/hooks/hooks.json +5 -5
  32. package/package.json +4 -4
  33. package/scripts/cairn +1 -102
  34. package/scripts/cairn-lifecycle.mjs +12 -6
  35. package/scripts/cairn-state.mjs +106 -0
  36. package/scripts/cairn-state.sh +3 -2
  37. package/scripts/cairn-toolcheck.mjs +54 -14
  38. package/scripts/cairn.mjs +115 -0
  39. package/skills/cairn-memory/SKILL.md +4 -2
  40. package/skills/cairn-plan/SKILL.md +23 -16
  41. package/skills/cairn-review/SKILL.md +3 -1
  42. package/skills/cairn-work/SKILL.md +22 -19
  43. package/templates/work-plan.md +9 -0
package/scripts/cairn CHANGED
@@ -1,10 +1,6 @@
1
1
  #!/bin/sh
2
2
  set -eu
3
3
 
4
- cmd="${1:-help}"
5
- shift || true
6
- locale="${LC_ALL:-${LC_MESSAGES:-${LANG:-en}}}"
7
-
8
4
  script_path="$0"
9
5
  link_target="$(readlink "$script_path" 2>/dev/null || true)"
10
6
  if [ -n "$link_target" ]; then
@@ -15,101 +11,4 @@ if [ -n "$link_target" ]; then
15
11
  fi
16
12
  script_dir="$(cd "$(dirname "$script_path")" && pwd)"
17
13
 
18
- locale_family() {
19
- case "$locale" in
20
- ko*|KO*) printf '%s\n' ko ;;
21
- ja*|JA*) printf '%s\n' ja ;;
22
- zh*|ZH*) printf '%s\n' zh ;;
23
- es*|ES*) printf '%s\n' es ;;
24
- fr*|FR*) printf '%s\n' fr ;;
25
- de*|DE*) printf '%s\n' de ;;
26
- pt*|PT*) printf '%s\n' pt ;;
27
- *) printf '%s\n' en ;;
28
- esac
29
- }
30
-
31
- say() {
32
- key="$1"
33
- family="$(locale_family)"
34
- case "$family:$key" in
35
- ko:memory) printf '%s\n' "cairn-memory 스킬을 사용하세요. MEMORY.md는 짧은 색인으로, 상세 내용은 docs/memory/<domain>.md에 기록합니다." ;;
36
- ko:plan) printf '%s\n' "cairn-plan 스킬을 사용하세요. PLAN.md는 색인으로, 상세 계획은 docs/plan/<topic>.md에 기록합니다." ;;
37
- ko:work) printf '%s\n' "cairn-work 스킬을 사용하세요. 하나의 모듈 조각을 실행하고 모듈 증거와 표면 증거를 확보합니다." ;;
38
- ko:review) printf '%s\n' "cairn-review 스킬을 사용하세요. PLAN.md와 MEMORY.md 기준으로 증거를 비교합니다." ;;
39
- ko:toolcheck) printf '%s\n' "저장소 도구를 점검하려면 cairn toolcheck 를 실행하세요. 누락 도구 설치까지 시도하려면 cairn toolcheck --install 을 사용합니다." ;;
40
- ko:usage) printf '%s\n' "사용법: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck" ;;
41
- ja:memory) printf '%s\n' "cairn-memory スキルを使用してください。MEMORY.md は短い索引にし、詳細は docs/memory/<domain>.md に記録します。" ;;
42
- ja:plan) printf '%s\n' "cairn-plan スキルを使用してください。PLAN.md は索引にし、詳細計画は docs/plan/<topic>.md に記録します。" ;;
43
- ja:work) printf '%s\n' "cairn-work スキルを使用してください。1つのモジュールスライスを実行し、モジュール証拠と表面証拠を取得します。" ;;
44
- ja:review) printf '%s\n' "cairn-review スキルを使用してください。PLAN.md と MEMORY.md に照らして証拠を比較します。" ;;
45
- ja:toolcheck) printf '%s\n' "リポジトリツールを確認するには cairn toolcheck を実行してください。不足ツールのインストールも試すには cairn toolcheck --install を使用します。" ;;
46
- ja:usage) printf '%s\n' "使い方: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck" ;;
47
- zh:memory) printf '%s\n' "使用 cairn-memory skill。将 MEMORY.md 保持为简短索引,并将详细内容写入 docs/memory/<domain>.md。" ;;
48
- zh:plan) printf '%s\n' "使用 cairn-plan skill。将 PLAN.md 保持为索引,并将详细计划写入 docs/plan/<topic>.md。" ;;
49
- zh:work) printf '%s\n' "使用 cairn-work skill。执行一个模块切片,并收集模块证据和表面证据。" ;;
50
- zh:review) printf '%s\n' "使用 cairn-review skill。根据 PLAN.md 和 MEMORY.md 对比证据。" ;;
51
- zh:toolcheck) printf '%s\n' "运行 cairn toolcheck 检查仓库工具。使用 cairn toolcheck --install 尝试安装缺失工具。" ;;
52
- zh:usage) printf '%s\n' "用法: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck" ;;
53
- es:memory) printf '%s\n' "Usa la skill cairn-memory. Mantén MEMORY.md como un índice breve y escribe los detalles en docs/memory/<domain>.md." ;;
54
- es:plan) printf '%s\n' "Usa la skill cairn-plan. Mantén PLAN.md como índice y escribe los planes detallados en docs/plan/<topic>.md." ;;
55
- es:work) printf '%s\n' "Usa la skill cairn-work. Ejecuta un solo segmento de módulo y captura evidencia de módulo y de superficie." ;;
56
- es:review) printf '%s\n' "Usa la skill cairn-review. Compara la evidencia con PLAN.md y MEMORY.md." ;;
57
- es:toolcheck) printf '%s\n' "Ejecuta cairn toolcheck para revisar herramientas del repositorio. Usa cairn toolcheck --install para intentar instalar herramientas faltantes." ;;
58
- es:usage) printf '%s\n' "Uso: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck" ;;
59
- fr:memory) printf '%s\n' "Utilisez la skill cairn-memory. Gardez MEMORY.md comme index court et écrivez les détails dans docs/memory/<domain>.md." ;;
60
- fr:plan) printf '%s\n' "Utilisez la skill cairn-plan. Gardez PLAN.md comme index et écrivez les plans détaillés dans docs/plan/<topic>.md." ;;
61
- fr:work) printf '%s\n' "Utilisez la skill cairn-work. Exécutez une tranche de module et capturez les preuves de module et de surface." ;;
62
- fr:review) printf '%s\n' "Utilisez la skill cairn-review. Comparez les preuves avec PLAN.md et MEMORY.md." ;;
63
- fr:toolcheck) printf '%s\n' "Exécutez cairn toolcheck pour vérifier les outils du dépôt. Utilisez cairn toolcheck --install pour tenter d'installer les outils manquants." ;;
64
- fr:usage) printf '%s\n' "Utilisation: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck" ;;
65
- de:memory) printf '%s\n' "Verwenden Sie die Skill cairn-memory. Halten Sie MEMORY.md als kurzen Index und schreiben Sie Details nach docs/memory/<domain>.md." ;;
66
- de:plan) printf '%s\n' "Verwenden Sie die Skill cairn-plan. Halten Sie PLAN.md als Index und schreiben Sie Detailpläne nach docs/plan/<topic>.md." ;;
67
- de:work) printf '%s\n' "Verwenden Sie die Skill cairn-work. Führen Sie einen Modulabschnitt aus und erfassen Sie Modul- und Oberflächenbelege." ;;
68
- de:review) printf '%s\n' "Verwenden Sie die Skill cairn-review. Vergleichen Sie Belege mit PLAN.md und MEMORY.md." ;;
69
- de:toolcheck) printf '%s\n' "Führen Sie cairn toolcheck aus, um Repository-Tools zu prüfen. Verwenden Sie cairn toolcheck --install, um fehlende Tools zu installieren." ;;
70
- de:usage) printf '%s\n' "Verwendung: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck" ;;
71
- pt:memory) printf '%s\n' "Use a skill cairn-memory. Mantenha MEMORY.md como um índice curto e escreva detalhes em docs/memory/<domain>.md." ;;
72
- pt:plan) printf '%s\n' "Use a skill cairn-plan. Mantenha PLAN.md como índice e escreva planos detalhados em docs/plan/<topic>.md." ;;
73
- pt:work) printf '%s\n' "Use a skill cairn-work. Execute uma fatia de módulo e capture evidências de módulo e de superfície." ;;
74
- pt:review) printf '%s\n' "Use a skill cairn-review. Compare evidências com PLAN.md e MEMORY.md." ;;
75
- pt:toolcheck) printf '%s\n' "Execute cairn toolcheck para verificar ferramentas do repositório. Use cairn toolcheck --install para tentar instalar ferramentas ausentes." ;;
76
- pt:usage) printf '%s\n' "Uso: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck" ;;
77
- *)
78
- case "$key" in
79
- memory) printf '%s\n' "Use the cairn-memory skill. Keep MEMORY.md as a short index and write details to docs/memory/<domain>.md." ;;
80
- plan) printf '%s\n' "Use the cairn-plan skill. Keep PLAN.md as an index and write detailed plans to docs/plan/<topic>.md." ;;
81
- work) printf '%s\n' "Use the cairn-work skill. Execute one module slice and capture module and surface evidence." ;;
82
- review) printf '%s\n' "Use the cairn-review skill. Compare evidence against PLAN.md and MEMORY.md." ;;
83
- toolcheck) printf '%s\n' "Run cairn toolcheck to inspect repository tools. Use cairn toolcheck --install to attempt missing tool installation." ;;
84
- usage) printf '%s\n' "Usage: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck" ;;
85
- esac
86
- ;;
87
- esac
88
- }
89
-
90
- case "$cmd" in
91
- install|upgrade|doctor|uninstall)
92
- node "$script_dir/cairn-lifecycle.mjs" "$cmd" "$@"
93
- ;;
94
- init)
95
- sh "$script_dir/cairn-state.sh" manual
96
- ;;
97
- memory)
98
- say memory
99
- ;;
100
- plan)
101
- say plan
102
- ;;
103
- work)
104
- say work
105
- ;;
106
- review)
107
- say review
108
- ;;
109
- toolcheck)
110
- node "$script_dir/cairn-toolcheck.mjs" "$@"
111
- ;;
112
- *)
113
- say usage
114
- ;;
115
- esac
14
+ exec node "$script_dir/cairn.mjs" "$@"
@@ -2,14 +2,16 @@
2
2
  import { createHash } from "node:crypto";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { cp, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
5
- import { dirname, join, resolve } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { dirname, join, relative, resolve } from "node:path";
6
7
  import { fileURLToPath, pathToFileURL } from "node:url";
7
8
 
8
9
  const pluginRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9
- const codexHome = resolve(process.env.CODEX_HOME ?? join(process.env.HOME ?? ".", ".codex"));
10
- const claudeHome = resolve(process.env.CLAUDE_HOME ?? join(process.env.HOME ?? ".", ".claude"));
11
- const antigravityHome = resolve(process.env.ANTIGRAVITY_HOME ?? join(process.env.HOME ?? ".", ".agents"));
12
- const antigravityCliHome = resolve(process.env.ANTIGRAVITY_CLI_HOME ?? join(process.env.HOME ?? ".", ".gemini", "antigravity-cli"));
10
+ const homeRoot = process.env.HOME ?? process.env.USERPROFILE ?? homedir() ?? ".";
11
+ const codexHome = resolve(process.env.CODEX_HOME ?? join(homeRoot, ".codex"));
12
+ const claudeHome = resolve(process.env.CLAUDE_HOME ?? join(homeRoot, ".claude"));
13
+ const antigravityHome = resolve(process.env.ANTIGRAVITY_HOME ?? join(homeRoot, ".agents"));
14
+ const antigravityCliHome = resolve(process.env.ANTIGRAVITY_CLI_HOME ?? join(homeRoot, ".gemini", "antigravity-cli"));
13
15
  const configPath = process.env.CODEX_CONFIG_PATH ?? join(codexHome, "config.toml");
14
16
  const marketplaceName = "cairn";
15
17
  const pluginName = "cairn";
@@ -87,7 +89,7 @@ async function copyPlugin() {
87
89
  await mkdir(dirname(installedPluginRoot), { recursive: true });
88
90
  await cp(pluginRoot, installedPluginRoot, {
89
91
  recursive: true,
90
- filter: (source) => !source.includes(`${pluginRoot}/.git`) && !source.includes(`${pluginRoot}/node_modules`),
92
+ filter: (source) => !pathHasSegment(relative(pluginRoot, source), ".git") && !pathHasSegment(relative(pluginRoot, source), "node_modules"),
91
93
  });
92
94
  const manifestPath = join(installedPluginRoot, ".codex-plugin", "plugin.json");
93
95
  const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
@@ -257,6 +259,10 @@ function append(config, block) {
257
259
  return `${config.trimEnd()}${config.trimEnd().length === 0 ? "" : "\n\n"}${block.trimEnd()}\n`;
258
260
  }
259
261
 
262
+ export function pathHasSegment(path, segment) {
263
+ return path.split(/[\\/]+/).includes(segment);
264
+ }
265
+
260
266
  export function commandNames() {
261
267
  return ["install", "upgrade", "doctor", "uninstall", "memory", "plan", "work", "review", "toolcheck"];
262
268
  }
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ const memoryTemplate = `# MEMORY
7
+
8
+ This file is a short index of persistent repository knowledge.
9
+
10
+ ## Domain Knowledge
11
+
12
+ - Link detailed notes under \`docs/memory/\`.
13
+
14
+ ## Policy
15
+
16
+ - Prefer precise repository exploration before implementation.
17
+ - Preserve proper nouns, file names, variable names, service names, alert names, MCP tool names, and agent names exactly as written.
18
+ - Read only the detailed memory files needed for the current task.
19
+ - Write user-visible responses and artifacts in the OS locale unless the user asks for another language.
20
+
21
+ ## Update Rules
22
+
23
+ - Keep root files short.
24
+ - Move detailed domain knowledge to \`docs/memory/<domain>.md\`.
25
+ - Record facts with source paths, commands, and observed behavior.
26
+ `;
27
+
28
+ const planTemplate = `# PLAN
29
+
30
+ This file is a short index of active and completed work plans.
31
+
32
+ ## Active Plans
33
+
34
+ - Link detailed plans under \`docs/plan/\`.
35
+
36
+ ## Completed Plans
37
+
38
+ - Move completed topics here with evidence links.
39
+
40
+ ## Planning Rules
41
+
42
+ - Every agent must start assigned work by reading the project-root \`MEMORY.md\` for domain knowledge and repository policy.
43
+ - Plans must be decision-complete before implementation.
44
+ - Run complexity triage before applying agent, plugin, or delegated workflow guidance.
45
+ - Record the selected Light Path or Heavy Path and the checked Heavy Path signals in \`docs/plan/<topic>.md\`.
46
+ - Split implementation into small module slices.
47
+ - Detect repository stack and required LSP/check tools before implementation.
48
+ - Install or bootstrap missing required tools before declaring them unavailable.
49
+ - Each slice normally passes exactly two gates.
50
+ - Module acceptance verification.
51
+ - Surface integration verification.
52
+ - Run dry-run or check mode before external-state mutation when available.
53
+ - Use at most two verification passes per slice by default.
54
+ - If a gate fails, diagnose once, shrink or split the slice, and rerun both gates.
55
+ - After two failed passes, record the blocker in \`docs/plan/<topic>.md\`.
56
+ `;
57
+
58
+ if (isCliEntry()) {
59
+ const event = process.argv[2] ?? "manual";
60
+ console.log(await runState(event));
61
+ }
62
+
63
+ export async function runState(event = "manual", { root = process.env.HARNESS_REPO_ROOT ?? process.cwd(), locale = localeValue() } = {}) {
64
+ const resolvedRoot = resolve(root);
65
+ await mkdir(join(resolvedRoot, "docs", "memory"), { recursive: true });
66
+ await mkdir(join(resolvedRoot, "docs", "plan"), { recursive: true });
67
+ await writeIfMissing(join(resolvedRoot, "MEMORY.md"), memoryTemplate);
68
+ await writeIfMissing(join(resolvedRoot, "PLAN.md"), planTemplate);
69
+
70
+ const ko = locale.toLowerCase().startsWith("ko");
71
+ if (event === "session-start" || event === "user-prompt-submit") {
72
+ return ko
73
+ ? "Cairn 컨텍스트: 모든 에이전트는 작업 시작 시 도메인 지식과 정책 색인인 프로젝트 루트 MEMORY.md를 먼저 읽어야 합니다."
74
+ : "Cairn context: every agent must start by reading the project-root MEMORY.md for domain knowledge and repository policy.";
75
+ }
76
+ if (event === "post-tool-use") {
77
+ return ko
78
+ ? "Cairn 점검: 외부 상태 변경에는 dry-run/check 증거가 필요하고, 동작이 바뀌었다면 docs/plan 증거를 갱신하세요."
79
+ : "Cairn check: external-state changes need dry-run/check evidence; if behavior changed, update docs/plan evidence.";
80
+ }
81
+ if (event === "stop" || event === "subagent-stop") {
82
+ return ko
83
+ ? "Cairn 종료 게이트: 완료에는 docs/plan의 dry-run/check, 모듈 수용, 표면 통합 증거가 필요합니다."
84
+ : "Cairn stop gate: completion requires dry-run/check, module acceptance, and surface integration evidence in docs/plan.";
85
+ }
86
+ return ko
87
+ ? "Cairn이 MEMORY.md, PLAN.md, docs/memory, docs/plan을 초기화했습니다."
88
+ : "Cairn initialized MEMORY.md, PLAN.md, docs/memory, and docs/plan.";
89
+ }
90
+
91
+ async function writeIfMissing(path, content) {
92
+ try {
93
+ await readFile(path, "utf8");
94
+ } catch {
95
+ await writeFile(path, content);
96
+ }
97
+ }
98
+
99
+ function localeValue() {
100
+ return [process.env.LC_ALL, process.env.LC_MESSAGES, process.env.LANG]
101
+ .find((value) => typeof value === "string" && value.length > 0) ?? Intl.DateTimeFormat().resolvedOptions().locale;
102
+ }
103
+
104
+ function isCliEntry() {
105
+ return process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
106
+ }
@@ -51,6 +51,7 @@ This file is a short index of active and completed work plans.
51
51
 
52
52
  ## Planning Rules
53
53
 
54
+ - Every agent must start assigned work by reading the project-root `MEMORY.md` for domain knowledge and repository policy.
54
55
  - Plans must be decision-complete before implementation.
55
56
  - Split implementation into small module slices.
56
57
  - Detect repository stack and required LSP/check tools before implementation.
@@ -75,9 +76,9 @@ is_ko() {
75
76
  case "$event" in
76
77
  session-start|user-prompt-submit)
77
78
  if is_ko; then
78
- printf '%s\n' "Cairn 컨텍스트: 먼저 MEMORY.md와 PLAN.md를 읽고, 필요한 경우 toolcheck로 LSP/check 도구 준비 상태를 확인하세요."
79
+ printf '%s\n' "Cairn 컨텍스트: 모든 에이전트는 작업 시작 시 도메인 지식과 정책 색인인 프로젝트 루트 MEMORY.md를 먼저 읽어야 합니다."
79
80
  else
80
- printf '%s\n' "Cairn context: read MEMORY.md and PLAN.md first, then run toolcheck when LSP/check tool readiness is needed."
81
+ printf '%s\n' "Cairn context: every agent must start by reading the project-root MEMORY.md for domain knowledge and repository policy."
81
82
  fi
82
83
  ;;
83
84
  post-tool-use)
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { readdir, stat } from "node:fs/promises";
3
- import { join } from "node:path";
3
+ import { join, relative, sep } from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { pathToFileURL } from "node:url";
6
6
 
@@ -60,7 +60,7 @@ export async function collectEntries(base) {
60
60
  for (const entry of await readdir(dir)) {
61
61
  if ([".git", ".cairn", "node_modules", "vendor", "target", ".venv", "__pycache__"].includes(entry)) continue;
62
62
  const path = join(dir, entry);
63
- const relativePath = path.slice(base.length + 1);
63
+ const relativePath = normalizePath(relative(base, path));
64
64
  if (relativePath === "test/fixtures" || relativePath.startsWith("test/fixtures/")) continue;
65
65
  const info = await stat(path);
66
66
  if (info.isDirectory()) await walk(path);
@@ -85,7 +85,7 @@ export function detectStacks(paths) {
85
85
  return [...stacks];
86
86
  }
87
87
 
88
- export function buildRequirements(stacks, entries = [], commandAvailable = commandOk) {
88
+ export function buildRequirements(stacks, entries = [], commandAvailable = commandOk, platform = process.platform) {
89
89
  const requirements = [];
90
90
  if (stacks.includes("javascript")) {
91
91
  requirements.push(req("node", ["--version"], "JavaScript runtime for package scripts and CLI checks"));
@@ -98,8 +98,8 @@ export function buildRequirements(stacks, entries = [], commandAvailable = comma
98
98
  if (stacks.includes("python")) {
99
99
  const uv = commandAvailable("uv", ["--version"]);
100
100
  const project = entries.includes("pyproject.toml");
101
- const basedpyrightInstall = pythonInstall(uv, project, "basedpyright");
102
- const ruffInstall = pythonInstall(uv, project, "ruff");
101
+ const basedpyrightInstall = pythonInstall(uv, project, "basedpyright", platform);
102
+ const ruffInstall = pythonInstall(uv, project, "ruff", platform);
103
103
  requirements.push(req("basedpyright", ["--version"], "Python LSP/type checker", basedpyrightInstall));
104
104
  requirements.push(req("ruff", ["--version"], "Python lint and format checker", ruffInstall));
105
105
  }
@@ -114,7 +114,7 @@ export function buildRequirements(stacks, entries = [], commandAvailable = comma
114
114
  }
115
115
  if (stacks.includes("java")) {
116
116
  addJvmRequirements(requirements);
117
- requirements.push(req("jdtls", ["--version"], "Java LSP server", { command: "bash", args: ["-lc", "mkdir -p .cairn/tools && curl -L https://download.eclipse.org/jdtls/milestones/latest/jdt-language-server-latest.tar.gz | tar -xz -C .cairn/tools"] }));
117
+ requirements.push(req("jdtls", ["--version"], "Java LSP server", jdtlsInstall(platform)));
118
118
  addJvmBuildToolRequirements(requirements, entries);
119
119
  }
120
120
  if (stacks.includes("kotlin")) {
@@ -151,9 +151,12 @@ export function addJvmRequirements(requirements) {
151
151
  }
152
152
 
153
153
  export function addJvmBuildToolRequirements(requirements, entries = []) {
154
- if (entries.includes("gradlew")) pushUnique(requirements, req("./gradlew", ["--version"], "Gradle wrapper availability"));
154
+ if (entries.includes("gradlew.bat")) pushUnique(requirements, req("gradlew.bat", ["--version"], "Gradle wrapper availability"));
155
+ else if (entries.includes("gradlew")) pushUnique(requirements, req("./gradlew", ["--version"], "Gradle wrapper availability"));
155
156
  else if (entries.includes("build.gradle") || entries.includes("build.gradle.kts")) pushUnique(requirements, req("gradle", ["--version"], "Gradle availability"));
156
- if (entries.includes("mvnw")) pushUnique(requirements, req("./mvnw", ["--version"], "Maven wrapper availability"));
157
+ if (entries.includes("mvnw.cmd")) pushUnique(requirements, req("mvnw.cmd", ["--version"], "Maven wrapper availability"));
158
+ else if (entries.includes("mvnw.bat")) pushUnique(requirements, req("mvnw.bat", ["--version"], "Maven wrapper availability"));
159
+ else if (entries.includes("mvnw")) pushUnique(requirements, req("./mvnw", ["--version"], "Maven wrapper availability"));
157
160
  else if (entries.includes("pom.xml")) pushUnique(requirements, req("mvn", ["--version"], "Maven availability"));
158
161
  }
159
162
 
@@ -165,12 +168,24 @@ export function composerInstall(packages) {
165
168
  return { command: "composer", args: ["require", "--dev", ...packages] };
166
169
  }
167
170
 
168
- export function pythonInstall(uv, project, tool) {
171
+ export function pythonInstall(uv, project, tool, platform = process.platform) {
169
172
  if (uv && project) return { command: "uv", args: ["add", "--dev", tool] };
170
173
  if (uv) return { command: "uv", args: ["tool", "install", tool] };
174
+ if (platform === "win32") return { command: "py", args: ["-3", "-m", "pip", "install", "--user", tool] };
171
175
  return { command: "python3", args: ["-m", "pip", "install", "--user", tool] };
172
176
  }
173
177
 
178
+ export function jdtlsInstall(platform = process.platform) {
179
+ const url = "https://download.eclipse.org/jdtls/milestones/latest/jdt-language-server-latest.tar.gz";
180
+ if (platform === "win32") {
181
+ return {
182
+ command: "powershell",
183
+ args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", `New-Item -ItemType Directory -Force .cairn/tools | Out-Null; Invoke-WebRequest -Uri ${url} -OutFile .cairn/tools/jdtls.tar.gz; tar -xzf .cairn/tools/jdtls.tar.gz -C .cairn/tools`],
184
+ };
185
+ }
186
+ return { command: "bash", args: ["-lc", `mkdir -p .cairn/tools && curl -L ${url} | tar -xz -C .cairn/tools`] };
187
+ }
188
+
174
189
  export function req(command, args, reason, installCommand = null) {
175
190
  return { name: command, command, args, reason, install: installCommand };
176
191
  }
@@ -190,16 +205,41 @@ export function installDev(pm, packages) {
190
205
  }
191
206
 
192
207
  export function commandOk(command, args, root = process.cwd(), runner = run) {
193
- if (runner(command, args).status === 0) return true;
194
- if (runner(join(root, "node_modules", ".bin", command), args).status === 0) return true;
195
- if (runner(join(root, "vendor", "bin", command), args).status === 0) return true;
196
- return runner(join(root, ".cairn", "tools", "bin", command), args).status === 0;
208
+ return commandCandidates(command, root).some((candidate) => runner(candidate, args).status === 0);
209
+ }
210
+
211
+ export function commandCandidates(command, root = process.cwd(), platform = process.platform) {
212
+ const candidates = [
213
+ command,
214
+ join(root, "node_modules", ".bin", command),
215
+ join(root, "vendor", "bin", command),
216
+ join(root, ".cairn", "tools", "bin", command),
217
+ ];
218
+ if (platform !== "win32") return candidates;
219
+ const extensions = [".cmd", ".bat", ".exe"];
220
+ return candidates.flatMap((candidate) => hasWindowsExecutableExtension(candidate) ? [candidate] : [candidate, ...extensions.map((extension) => `${candidate}${extension}`)]);
197
221
  }
198
222
 
199
223
  export function run(command, args) {
200
- return spawnSync(command, args, { stdio: "ignore", encoding: "utf8" });
224
+ return spawnSync(command, args, {
225
+ stdio: "ignore",
226
+ encoding: "utf8",
227
+ shell: shouldUseShell(command),
228
+ });
229
+ }
230
+
231
+ export function shouldUseShell(command, platform = process.platform) {
232
+ return platform === "win32" && /\.(cmd|bat)$/i.test(command);
201
233
  }
202
234
 
203
235
  function isCliEntry() {
204
236
  return process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
205
237
  }
238
+
239
+ function normalizePath(path) {
240
+ return path.split(sep).join("/");
241
+ }
242
+
243
+ function hasWindowsExecutableExtension(command) {
244
+ return /\.(cmd|bat|exe)$/i.test(command);
245
+ }
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { spawnSync } from "node:child_process";
5
+ import { runState } from "./cairn-state.mjs";
6
+
7
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
8
+
9
+ export async function main(command = "help", args = [], { runner = spawnSync } = {}) {
10
+ if (["install", "upgrade", "doctor", "uninstall"].includes(command)) {
11
+ const result = runner(process.execPath, [join(scriptDir, "cairn-lifecycle.mjs"), command, ...args], { stdio: "inherit" });
12
+ process.exitCode = result.status ?? 1;
13
+ return;
14
+ }
15
+ if (command === "init") {
16
+ console.log(await runState("manual"));
17
+ return;
18
+ }
19
+ if (command === "toolcheck") {
20
+ const result = runner(process.execPath, [join(scriptDir, "cairn-toolcheck.mjs"), ...args], { stdio: "inherit" });
21
+ process.exitCode = result.status ?? 1;
22
+ return;
23
+ }
24
+ if (["memory", "plan", "work", "review"].includes(command)) {
25
+ console.log(message(command));
26
+ return;
27
+ }
28
+ console.log(message("usage"));
29
+ }
30
+
31
+ export function message(key, locale = localeValue()) {
32
+ const family = localeFamily(locale);
33
+ return messages[family]?.[key] ?? messages.en[key];
34
+ }
35
+
36
+ export function localeFamily(locale = localeValue()) {
37
+ const normalized = locale.toLowerCase();
38
+ for (const family of ["ko", "ja", "zh", "es", "fr", "de", "pt"]) {
39
+ if (normalized.startsWith(family)) return family;
40
+ }
41
+ return "en";
42
+ }
43
+
44
+ function localeValue() {
45
+ return [process.env.LC_ALL, process.env.LC_MESSAGES, process.env.LANG]
46
+ .find((value) => typeof value === "string" && value.length > 0) ?? Intl.DateTimeFormat().resolvedOptions().locale;
47
+ }
48
+
49
+ function isCliEntry() {
50
+ return process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
51
+ }
52
+
53
+ const messages = {
54
+ en: {
55
+ memory: "Use the cairn-memory skill. Keep MEMORY.md as a short index and write details to docs/memory/<domain>.md.",
56
+ plan: "Use the cairn-plan skill before non-trivial implementation. Every agent must first read the project-root MEMORY.md for domain knowledge and repository policy. Keep PLAN.md as an index, write detailed plans to docs/plan/<topic>.md, and record Light/Heavy Path triage before any mutation.",
57
+ work: "Use the cairn-work skill only after a plan exists. Every agent must first read the project-root MEMORY.md for domain knowledge and repository policy. Execute one module slice using the recorded Light/Heavy Path route, and capture dry-run/check, module, and surface evidence.",
58
+ review: "Use the cairn-review skill. Compare evidence against PLAN.md and MEMORY.md.",
59
+ usage: "Usage: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck",
60
+ },
61
+ ko: {
62
+ memory: "cairn-memory 스킬을 사용하세요. MEMORY.md는 짧은 색인으로, 상세 내용은 docs/memory/<domain>.md에 기록합니다.",
63
+ plan: "중요 구현 전에는 cairn-plan 스킬을 사용하세요. 모든 에이전트는 도메인 지식과 저장소 정책을 위해 프로젝트 루트 MEMORY.md를 먼저 읽어야 합니다. PLAN.md는 색인으로, 상세 계획은 docs/plan/<topic>.md에 기록하며 파일 변경 전 Light/Heavy Path 트리아지를 남깁니다.",
64
+ work: "계획이 있을 때만 cairn-work 스킬을 사용하세요. 모든 에이전트는 도메인 지식과 저장소 정책을 위해 프로젝트 루트 MEMORY.md를 먼저 읽어야 합니다. 기록된 Light/Heavy Path 경로로 하나의 모듈 조각을 실행하며 dry-run/check, 모듈, 표면 증거를 확보합니다.",
65
+ review: "cairn-review 스킬을 사용하세요. PLAN.md와 MEMORY.md 기준으로 증거를 비교합니다.",
66
+ usage: "사용법: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck",
67
+ },
68
+ ja: {
69
+ memory: "cairn-memory スキルを使用してください。MEMORY.md は短い索引にし、詳細は docs/memory/<domain>.md に記録します。",
70
+ plan: "cairn-plan スキルを使用してください。PLAN.md は索引にし、詳細計画は docs/plan/<topic>.md に記録します。",
71
+ work: "cairn-work スキルを使用してください。1つのモジュールスライスを実行し、モジュール証拠と表面証拠を取得します。",
72
+ review: "cairn-review スキルを使用してください。PLAN.md と MEMORY.md に照らして証拠を比較します。",
73
+ usage: "使い方: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck",
74
+ },
75
+ zh: {
76
+ memory: "使用 cairn-memory skill。将 MEMORY.md 保持为简短索引,并将详细内容写入 docs/memory/<domain>.md。",
77
+ plan: "使用 cairn-plan skill。将 PLAN.md 保持为索引,并将详细计划写入 docs/plan/<topic>.md。",
78
+ work: "使用 cairn-work skill。执行一个模块切片,并收集模块证据。",
79
+ review: "使用 cairn-review skill。根据 PLAN.md 和 MEMORY.md 对比证据。",
80
+ usage: "用法: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck",
81
+ },
82
+ es: {
83
+ memory: "Usa la skill cairn-memory. Mantén MEMORY.md como un índice breve y escribe los detalles en docs/memory/<domain>.md.",
84
+ plan: "Usa la skill cairn-plan. Mantén PLAN.md como índice y escribe los planes detallados en docs/plan/<topic>.md.",
85
+ work: "Usa la skill cairn-work. Ejecuta un solo segmento de módulo y captura evidencia de módulo y de superficie.",
86
+ review: "Usa la skill cairn-review. Compara la evidencia con PLAN.md y MEMORY.md.",
87
+ usage: "Uso: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck",
88
+ },
89
+ fr: {
90
+ memory: "Utilisez la skill cairn-memory. Gardez MEMORY.md comme index court et écrivez les détails dans docs/memory/<domain>.md.",
91
+ plan: "Utilisez la skill cairn-plan. Gardez MEMORY.md comme index et écrivez les plans détaillés dans docs/plan/<topic>.md.",
92
+ work: "Utilisez la skill cairn-work. Exécutez une tranche de module et capturez les preuves de module et de surface.",
93
+ review: "Utilisez la skill cairn-review. Comparez les preuves avec PLAN.md et MEMORY.md.",
94
+ usage: "Utilisation: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck",
95
+ },
96
+ de: {
97
+ memory: "Verwenden Sie die Skill cairn-memory. Halten Sie MEMORY.md als kurzen Index und schreiben Sie Details nach docs/memory/<domain>.md.",
98
+ plan: "Verwenden Sie die Skill cairn-plan. Halten Sie MEMORY.md als Index und schreiben Sie Detailpläne nach docs/plan/<topic>.md.",
99
+ work: "Verwenden Sie die Skill cairn-work. Führen Sie einen Modulabschnitt aus und erfassen Sie Modul- und Oberflächenbelege.",
100
+ review: "Verwenden Sie die Skill cairn-review. Vergleichen Sie Belege mit PLAN.md und MEMORY.md.",
101
+ usage: "Verwendung: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck",
102
+ },
103
+ pt: {
104
+ memory: "Use a skill cairn-memory. Mantenha MEMORY.md como um índice curto e escreva detalhes em docs/memory/<domain>.md.",
105
+ plan: "Use a skill cairn-plan. Mantenha MEMORY.md como índice e escreva planos detalhados em docs/plan/<topic>.md.",
106
+ work: "Use a skill cairn-work. Execute uma fatia de módulo e capture evidências de módulo e de superfície.",
107
+ review: "Use a skill cairn-review. Compare evidências com PLAN.md e MEMORY.md.",
108
+ usage: "Uso: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck",
109
+ },
110
+ };
111
+
112
+ if (isCliEntry()) {
113
+ const [command = "help", ...args] = process.argv.slice(2);
114
+ await main(command, args);
115
+ }
@@ -11,9 +11,11 @@ Use this when domain knowledge, repository policy, or pre-plan context compressi
11
11
 
12
12
  The root `MEMORY.md` is an index, not detailed knowledge. Record details in `docs/memory/<domain>.md`.
13
13
 
14
+ Every delegated agent must read the project-root `MEMORY.md` before doing its assigned discovery, evidence, or update task.
15
+
14
16
  ## Procedure
15
17
 
16
- 1. Run `scripts/cairn-state.sh manual` if it exists. Otherwise ensure `MEMORY.md`, `PLAN.md`, `docs/memory`, and `docs/plan` directly.
18
+ 1. Run `node scripts/cairn-state.mjs manual` if it exists. Otherwise ensure `MEMORY.md`, `PLAN.md`, `docs/memory`, and `docs/plan` directly.
17
19
  2. Explore the repository before asking the user.
18
20
  3. Delegate when it materially improves speed or quality instead of asking the user.
19
21
  - `explorer`: read-only domain discovery, boundaries, invariants, policies, contradictions, and stale facts when available.
@@ -28,7 +30,7 @@ The root `MEMORY.md` is an index, not detailed knowledge. Record details in `doc
28
30
  TASK: Explore persistent domain knowledge for <domain>.
29
31
  EXPECTED OUTCOME: Return facts, source paths, and proof commands concisely.
30
32
  REQUIRED TOOLS: Read-only file search, LSP/symbol tools, command checks.
31
- MUST DO: Preserve proper nouns exactly. Cite local paths. Separate facts from inference. Use the OS locale for user-visible text.
33
+ MUST DO: Read the project-root `MEMORY.md` before doing assigned work. Preserve proper nouns exactly. Cite local paths. Separate facts from inference. Use the OS locale for user-visible text.
32
34
  MUST NOT DO: Edit files. Ask the user. Guess missing names.
33
35
  CONTEXT: Current task, repository root, existing MEMORY.md entries.
34
36
  ```