cairn-ai 0.2.5 → 0.2.6
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 +1 -1
- package/.agents/workflows/cairn-plan.md +6 -2
- package/.agents/workflows/cairn-review.md +1 -1
- package/.agents/workflows/cairn-work.md +5 -1
- package/.claude/agents/explorer.md +1 -1
- package/.claude/agents/worker.md +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/README.ko.md +2 -0
- package/README.md +8 -4
- package/agents/explorer.md +2 -2
- package/agents/worker.md +6 -2
- package/commands/cairn-memory.md +1 -1
- package/commands/cairn-plan.md +7 -3
- package/commands/cairn-review.md +1 -1
- package/commands/cairn-uninstall.md +1 -1
- package/commands/cairn-work.md +5 -1
- package/docs/model-guidance/README.md +14 -2
- package/docs/model-guidance/claude.md +2 -0
- package/docs/model-guidance/codex.md +3 -1
- package/package.json +2 -2
- package/scripts/cairn-cleanup.mjs +61 -0
- package/scripts/cairn-goal.mjs +143 -24
- package/scripts/cairn-paths.mjs +47 -1
- package/scripts/cairn-state.mjs +41 -15
- package/scripts/cairn-state.sh +6 -6
- package/scripts/cairn.mjs +32 -24
- package/skills/cairn-memory/SKILL.md +3 -3
- package/skills/cairn-plan/SKILL.md +28 -9
- package/skills/cairn-review/SKILL.md +4 -4
- package/skills/cairn-work/SKILL.md +33 -20
- package/templates/PLAN.md +3 -1
- package/templates/work-plan.md +21 -0
package/scripts/cairn-goal.mjs
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import { lstatSync, readFileSync, readlinkSync, realpathSync, readdirSync } from "node:fs";
|
|
5
|
-
import { readFile, rename, rm } from "node:fs/promises";
|
|
6
|
-
import { join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { lstat, readFile, rename, rm } from "node:fs/promises";
|
|
6
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
8
|
import { assertNoSymlinkComponents, safeMkdir, safeWriteFile, withStateLock } from "./cairn-safe-fs.mjs";
|
|
9
|
+
import { cairnHome, goalStateDirectory, worktreeId } from "./cairn-paths.mjs";
|
|
10
|
+
|
|
11
|
+
export { worktreeId } from "./cairn-paths.mjs";
|
|
9
12
|
|
|
10
13
|
export const GOAL_STATE_VERSION = 2;
|
|
11
14
|
export const GOAL_STATUSES = new Set(["active", "paused", "blocked", "cancelled", "completed"]);
|
|
@@ -33,17 +36,17 @@ const taskTransitions = {
|
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
export function goalStatePath(root = process.cwd()) {
|
|
36
|
-
return join(
|
|
39
|
+
return join(goalStateDirectory(root), "state.json");
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export async function readGoalState({ root = process.cwd() } = {}) {
|
|
40
43
|
try {
|
|
41
44
|
const path = goalStatePath(root);
|
|
42
|
-
await assertNoSymlinkComponents(
|
|
45
|
+
await assertNoSymlinkComponents(cairnHome(), path);
|
|
43
46
|
const text = await readFile(path, "utf8");
|
|
44
47
|
return validateState(migrateState(JSON.parse(text)));
|
|
45
48
|
} catch (error) {
|
|
46
|
-
if (error?.code === "ENOENT") return
|
|
49
|
+
if (error?.code === "ENOENT") return migrateLegacyGoalState(root);
|
|
47
50
|
if (error instanceof SyntaxError) throw new Error(`Cairn goal state is invalid JSON: ${error.message}`);
|
|
48
51
|
throw error;
|
|
49
52
|
}
|
|
@@ -54,8 +57,15 @@ export async function startGoal({ root = process.cwd(), goal, planId, tasks, com
|
|
|
54
57
|
const normalizedPlanId = boundedRequiredText(planId, "planId", PLAN_ID_MAX_LENGTH);
|
|
55
58
|
if (!Array.isArray(tasks) || tasks.length === 0) throw new Error("tasks must contain at least one task");
|
|
56
59
|
|
|
57
|
-
return
|
|
58
|
-
|
|
60
|
+
return withGoalStateLock(root, async () => {
|
|
61
|
+
let existing = await readGoalState({ root });
|
|
62
|
+
const currentWorktreeId = worktreeId(root);
|
|
63
|
+
if (existing && existing.goal.worktreeId !== currentWorktreeId) {
|
|
64
|
+
const path = goalStatePath(root);
|
|
65
|
+
await assertNoSymlinkComponents(cairnHome(), path, { allowMissing: false });
|
|
66
|
+
await rm(path);
|
|
67
|
+
existing = null;
|
|
68
|
+
}
|
|
59
69
|
if (existing && !terminalGoalStatuses.has(existing.goal.status)) {
|
|
60
70
|
throw new Error(`An active Cairn goal already exists (${existing.goal.id}); pause, block, cancel, or complete it first`);
|
|
61
71
|
}
|
|
@@ -75,6 +85,7 @@ export async function startGoal({ root = process.cwd(), goal, planId, tasks, com
|
|
|
75
85
|
completionCriteria: normalizeCriteria(completionCriteria),
|
|
76
86
|
requiredEvidence: normalizeCriteria(requiredEvidence),
|
|
77
87
|
evidencePolicy: validEvidencePolicy(evidencePolicy),
|
|
88
|
+
worktreeId: currentWorktreeId,
|
|
78
89
|
ownerSessionId: optionalText(ownerSessionId, "ownerSessionId"),
|
|
79
90
|
blocker: null,
|
|
80
91
|
createdAt: now,
|
|
@@ -88,18 +99,36 @@ export async function startGoal({ root = process.cwd(), goal, planId, tasks, com
|
|
|
88
99
|
});
|
|
89
100
|
}
|
|
90
101
|
|
|
102
|
+
export async function reconcileWorktreeState({ root = process.cwd() } = {}) {
|
|
103
|
+
const initial = await readGoalState({ root });
|
|
104
|
+
if (!initial || initial.goal.worktreeId === worktreeId(root)) {
|
|
105
|
+
return { state: initial, removed: false };
|
|
106
|
+
}
|
|
107
|
+
return withGoalStateLock(root, async () => {
|
|
108
|
+
const state = await readGoalState({ root });
|
|
109
|
+
if (!state || state.goal.worktreeId === worktreeId(root)) {
|
|
110
|
+
return { state, removed: false };
|
|
111
|
+
}
|
|
112
|
+
const staleWorktreeId = state.goal.worktreeId;
|
|
113
|
+
const path = goalStatePath(root);
|
|
114
|
+
await assertNoSymlinkComponents(cairnHome(), path, { allowMissing: false });
|
|
115
|
+
await rm(path);
|
|
116
|
+
return { state: null, removed: true, staleWorktreeId };
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
91
120
|
export async function writeGoalState({ root = process.cwd(), state } = {}) {
|
|
92
|
-
return
|
|
121
|
+
return withGoalStateLock(root, () => writeGoalStateUnlocked({ root, state }));
|
|
93
122
|
}
|
|
94
123
|
|
|
95
124
|
async function writeGoalStateUnlocked({ root = process.cwd(), state } = {}) {
|
|
96
125
|
const validated = validateState(state);
|
|
97
126
|
const path = goalStatePath(root);
|
|
98
|
-
await
|
|
99
|
-
await assertNoSymlinkComponents(
|
|
127
|
+
await ensureGoalStateDirectory(root);
|
|
128
|
+
await assertNoSymlinkComponents(cairnHome(), path);
|
|
100
129
|
const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
101
130
|
try {
|
|
102
|
-
await safeWriteFile(
|
|
131
|
+
await safeWriteFile(cairnHome(), temporaryPath, `${JSON.stringify(validated, null, 2)}\n`, "utf8");
|
|
103
132
|
await rename(temporaryPath, path);
|
|
104
133
|
} catch (error) {
|
|
105
134
|
await rm(temporaryPath, { force: true }).catch(() => {});
|
|
@@ -109,21 +138,38 @@ async function writeGoalStateUnlocked({ root = process.cwd(), state } = {}) {
|
|
|
109
138
|
|
|
110
139
|
export async function transitionGoal({ root = process.cwd(), status, blocker } = {}) {
|
|
111
140
|
const nextStatus = validGoalStatus(status);
|
|
141
|
+
if (terminalGoalStatuses.has(nextStatus)) {
|
|
142
|
+
return withGoalStateLock(root, async () => {
|
|
143
|
+
const state = await readGoalState({ root });
|
|
144
|
+
if (!state) throw new Error("No Cairn goal state exists; start a goal first");
|
|
145
|
+
const next = applyGoalTransition(structuredClone(state), nextStatus, blocker, root);
|
|
146
|
+
next.revision += 1;
|
|
147
|
+
validateState(next);
|
|
148
|
+
const path = goalStatePath(root);
|
|
149
|
+
await assertNoSymlinkComponents(cairnHome(), path, { allowMissing: false });
|
|
150
|
+
await rm(path);
|
|
151
|
+
return next;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
112
154
|
return mutateGoal(root, (state) => {
|
|
113
|
-
|
|
114
|
-
if (currentStatus === nextStatus) return state;
|
|
115
|
-
if (!goalTransitions[currentStatus].has(nextStatus)) {
|
|
116
|
-
throw new Error(`Cannot transition goal from ${currentStatus} to ${nextStatus}`);
|
|
117
|
-
}
|
|
118
|
-
if (nextStatus === "completed") ensureGoalCanComplete(state, root);
|
|
119
|
-
if (nextStatus === "blocked") state.goal.blocker = requiredText(blocker, "blocker");
|
|
120
|
-
if (nextStatus === "active") state.goal.blocker = null;
|
|
121
|
-
state.goal.status = nextStatus;
|
|
122
|
-
state.goal.updatedAt = timestamp();
|
|
123
|
-
return state;
|
|
155
|
+
return applyGoalTransition(state, nextStatus, blocker, root);
|
|
124
156
|
});
|
|
125
157
|
}
|
|
126
158
|
|
|
159
|
+
function applyGoalTransition(state, nextStatus, blocker, root) {
|
|
160
|
+
const currentStatus = state.goal.status;
|
|
161
|
+
if (currentStatus === nextStatus) return state;
|
|
162
|
+
if (!goalTransitions[currentStatus].has(nextStatus)) {
|
|
163
|
+
throw new Error(`Cannot transition goal from ${currentStatus} to ${nextStatus}`);
|
|
164
|
+
}
|
|
165
|
+
if (nextStatus === "completed") ensureGoalCanComplete(state, root);
|
|
166
|
+
if (nextStatus === "blocked") state.goal.blocker = requiredText(blocker, "blocker");
|
|
167
|
+
if (nextStatus === "active") state.goal.blocker = null;
|
|
168
|
+
state.goal.status = nextStatus;
|
|
169
|
+
state.goal.updatedAt = timestamp();
|
|
170
|
+
return state;
|
|
171
|
+
}
|
|
172
|
+
|
|
127
173
|
export async function setTaskStatus({ root = process.cwd(), taskId, status, blocker } = {}) {
|
|
128
174
|
const nextStatus = validTaskStatus(status);
|
|
129
175
|
const normalizedTaskId = requiredText(taskId, "taskId");
|
|
@@ -153,6 +199,34 @@ export async function setTaskStatus({ root = process.cwd(), taskId, status, bloc
|
|
|
153
199
|
});
|
|
154
200
|
}
|
|
155
201
|
|
|
202
|
+
export async function replanGoal({ root = process.cwd(), tasks } = {}) {
|
|
203
|
+
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
204
|
+
throw new Error("replan tasks must contain at least one incomplete task");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return mutateGoal(root, (state) => {
|
|
208
|
+
assertGoalMutable(state);
|
|
209
|
+
if (state.goal.status !== "active") throw new Error("Only an active goal can be replanned");
|
|
210
|
+
|
|
211
|
+
const completedTasks = state.tasks.filter((task) => task.status === "completed");
|
|
212
|
+
const completedIds = new Set(completedTasks.map((task) => task.id));
|
|
213
|
+
const replacementTasks = tasks.map((task, index) => {
|
|
214
|
+
const source = typeof task === "string" ? task : { ...task, status: undefined, blocker: undefined };
|
|
215
|
+
const normalized = normalizeTask(source, index, index === 0 ? "active" : "pending");
|
|
216
|
+
if (completedIds.has(normalized.id)) {
|
|
217
|
+
throw new Error(`Replanned task conflicts with completed task id: ${normalized.id}`);
|
|
218
|
+
}
|
|
219
|
+
return normalized;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
state.tasks = [...completedTasks, ...replacementTasks];
|
|
223
|
+
state.receipts = state.receipts.filter((receipt) => receipt.scope === "task" && completedIds.has(receipt.taskId));
|
|
224
|
+
state.goal.blocker = null;
|
|
225
|
+
state.goal.updatedAt = timestamp();
|
|
226
|
+
return state;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
156
230
|
export async function assignTask({ root = process.cwd(), taskId, agentId = null } = {}) {
|
|
157
231
|
const normalizedTaskId = requiredText(taskId, "taskId");
|
|
158
232
|
const normalizedAgentId = agentId === null ? null : requiredText(agentId, "agentId");
|
|
@@ -403,6 +477,11 @@ export async function handleGoalCli(args = process.argv.slice(2), { stdout = con
|
|
|
403
477
|
emit(JSON.stringify(state));
|
|
404
478
|
return state;
|
|
405
479
|
}
|
|
480
|
+
if (command === "replan") {
|
|
481
|
+
const state = await replanGoal({ root, tasks: parseTasks(options.tasks ?? positional.slice(0)) });
|
|
482
|
+
emit(JSON.stringify(state));
|
|
483
|
+
return state;
|
|
484
|
+
}
|
|
406
485
|
if (command === "assign") {
|
|
407
486
|
const state = await assignTask({ root, taskId: options.task ?? positional[0], agentId: options.agent ?? positional[1] ?? null });
|
|
408
487
|
emit(JSON.stringify(state));
|
|
@@ -452,11 +531,11 @@ export async function handleGoalCli(args = process.argv.slice(2), { stdout = con
|
|
|
452
531
|
emit(JSON.stringify(state));
|
|
453
532
|
return state;
|
|
454
533
|
}
|
|
455
|
-
throw new Error("Usage: cairn-goal start|status|task|assign|receipt|verify|pause|resume|block|cancel|complete [--root PATH]");
|
|
534
|
+
throw new Error("Usage: cairn-goal start|status|task|replan|assign|receipt|verify|pause|resume|block|cancel|complete [--root PATH]");
|
|
456
535
|
}
|
|
457
536
|
|
|
458
537
|
async function mutateGoal(root, mutate) {
|
|
459
|
-
return
|
|
538
|
+
return withGoalStateLock(root, async () => {
|
|
460
539
|
const state = await readGoalState({ root });
|
|
461
540
|
if (!state) throw new Error("No Cairn goal state exists; start a goal first");
|
|
462
541
|
const next = await mutate(structuredClone(state));
|
|
@@ -466,6 +545,44 @@ async function mutateGoal(root, mutate) {
|
|
|
466
545
|
});
|
|
467
546
|
}
|
|
468
547
|
|
|
548
|
+
async function withGoalStateLock(root, operation) {
|
|
549
|
+
await ensureGoalStateDirectory(root);
|
|
550
|
+
return withStateLock(goalStateDirectory(root), operation);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async function ensureGoalStateDirectory(root) {
|
|
554
|
+
const home = cairnHome();
|
|
555
|
+
let existingAncestor = home;
|
|
556
|
+
while (true) {
|
|
557
|
+
try {
|
|
558
|
+
const stat = await lstat(existingAncestor);
|
|
559
|
+
if (!stat.isDirectory()) throw new Error(`Cairn home ancestor is not a directory: ${existingAncestor}`);
|
|
560
|
+
break;
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (error?.code !== "ENOENT") throw error;
|
|
563
|
+
const parent = dirname(existingAncestor);
|
|
564
|
+
if (parent === existingAncestor) throw error;
|
|
565
|
+
existingAncestor = parent;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
await safeMkdir(existingAncestor, relative(existingAncestor, goalStateDirectory(root)));
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async function migrateLegacyGoalState(root) {
|
|
572
|
+
const legacyPath = join(resolve(root), ".cairn", "state.json");
|
|
573
|
+
try {
|
|
574
|
+
await assertNoSymlinkComponents(root, legacyPath, { allowMissing: false });
|
|
575
|
+
const state = validateState(migrateState(JSON.parse(await readFile(legacyPath, "utf8"))));
|
|
576
|
+
await writeGoalStateUnlocked({ root, state });
|
|
577
|
+
await rm(legacyPath);
|
|
578
|
+
return state;
|
|
579
|
+
} catch (error) {
|
|
580
|
+
if (error?.code === "ENOENT") return null;
|
|
581
|
+
if (error instanceof SyntaxError) throw new Error(`Cairn goal state is invalid JSON: ${error.message}`);
|
|
582
|
+
throw error;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
469
586
|
function normalizeTask(task, index, defaultStatus) {
|
|
470
587
|
const source = typeof task === "string" ? { title: task } : task;
|
|
471
588
|
if (!source || typeof source !== "object") throw new Error(`tasks[${index}] must be a string or object`);
|
|
@@ -516,6 +633,7 @@ function validateState(value) {
|
|
|
516
633
|
if (!Array.isArray(goal.requiredEvidence) || goal.requiredEvidence.length === 0) throw new Error("goal.requiredEvidence must be a non-empty array");
|
|
517
634
|
goal.requiredEvidence.forEach((kind, index) => requiredText(kind, `goal.requiredEvidence[${index}]`));
|
|
518
635
|
if (goal.ownerSessionId !== null && goal.ownerSessionId !== undefined) requiredText(goal.ownerSessionId, "goal.ownerSessionId");
|
|
636
|
+
if (goal.worktreeId !== null && goal.worktreeId !== undefined) requiredText(goal.worktreeId, "goal.worktreeId");
|
|
519
637
|
if (goal.blocker !== null && goal.blocker !== undefined) requiredText(goal.blocker, "goal.blocker");
|
|
520
638
|
if (!Array.isArray(value.tasks) || value.tasks.length === 0) throw new Error("Cairn goal state must contain tasks");
|
|
521
639
|
if (!Array.isArray(value.receipts)) throw new Error("Cairn goal state evidence records (receipts) must be an array");
|
|
@@ -916,6 +1034,7 @@ function allowedOptions(command) {
|
|
|
916
1034
|
start: ["goal", "plan", "tasks", "criteria", "requiredEvidence", "session", "evidencePolicy", "evidence-policy"],
|
|
917
1035
|
status: [],
|
|
918
1036
|
task: ["task", "status", "reason"],
|
|
1037
|
+
replan: ["tasks"],
|
|
919
1038
|
assign: ["task", "agent"],
|
|
920
1039
|
receipt: ["task", "kind", "scope", "command", "exitCode", "exit-code", "exit", "timestamp", "goalId", "goal-id", "plan"],
|
|
921
1040
|
verify: ["task", "kind", "scope", "watch", "timeout-ms"],
|
package/scripts/cairn-paths.mjs
CHANGED
|
@@ -1,9 +1,55 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
2
4
|
import { dirname, join, resolve } from "node:path";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
6
|
|
|
5
7
|
export const RUNTIME_LOCATOR_SCHEMA_VERSION = 1;
|
|
6
8
|
|
|
9
|
+
export function cairnHome(env = process.env) {
|
|
10
|
+
const userHome = env.HOME ?? env.USERPROFILE ?? homedir();
|
|
11
|
+
return resolve(env.CAIRN_HOME ?? join(userHome, ".cairn"));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function repositoryId(root = process.cwd()) {
|
|
15
|
+
return `project-${hashPath(gitLayout(root).commonDir)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function worktreeId(root = process.cwd()) {
|
|
19
|
+
return `worktree-${hashPath(gitLayout(root).gitDir)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function goalStateDirectory(root = process.cwd(), env = process.env) {
|
|
23
|
+
return join(cairnHome(env), "projects", repositoryId(root), "worktrees", worktreeId(root));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function gitLayout(root) {
|
|
27
|
+
const workspaceRoot = realpathSync(resolve(root));
|
|
28
|
+
const gitEntry = join(workspaceRoot, ".git");
|
|
29
|
+
try {
|
|
30
|
+
const stat = lstatSync(gitEntry);
|
|
31
|
+
const gitDir = stat.isDirectory() ? realpathSync(gitEntry) : resolveGitFile(workspaceRoot, gitEntry);
|
|
32
|
+
const commonFile = join(gitDir, "commondir");
|
|
33
|
+
const commonDir = existsSync(commonFile)
|
|
34
|
+
? realpathSync(resolve(gitDir, readFileSync(commonFile, "utf8").trim()))
|
|
35
|
+
: gitDir;
|
|
36
|
+
return { gitDir, commonDir };
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error?.code !== "ENOENT") throw error;
|
|
39
|
+
return { gitDir: workspaceRoot, commonDir: workspaceRoot };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveGitFile(workspaceRoot, gitEntry) {
|
|
44
|
+
const match = readFileSync(gitEntry, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
|
|
45
|
+
if (!match) throw new Error(`Invalid Git worktree file: ${gitEntry}`);
|
|
46
|
+
return realpathSync(resolve(workspaceRoot, match[1]));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function hashPath(value) {
|
|
50
|
+
return createHash("sha256").update(value).digest("hex");
|
|
51
|
+
}
|
|
52
|
+
|
|
7
53
|
export function resolvePluginRoot(moduleUrl = import.meta.url) {
|
|
8
54
|
return resolve(dirname(fileURLToPath(moduleUrl)), "..");
|
|
9
55
|
}
|
package/scripts/cairn-state.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { realpathSync } from "node:fs";
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
-
import { evaluateStop, isGoalOwnedBySession, readGoalState } from "./cairn-goal.mjs";
|
|
6
|
+
import { evaluateStop, isGoalOwnedBySession, readGoalState, reconcileWorktreeState } from "./cairn-goal.mjs";
|
|
7
7
|
import { resolveRepoRoot } from "./cairn-paths.mjs";
|
|
8
8
|
import { assertNoSymlinkComponents, safeMkdir, safeWriteFile } from "./cairn-safe-fs.mjs";
|
|
9
9
|
|
|
@@ -65,11 +65,13 @@ This file is a short index of active and completed work plans.
|
|
|
65
65
|
|
|
66
66
|
## Planning Rules
|
|
67
67
|
|
|
68
|
-
-
|
|
69
|
-
- For implementation or
|
|
68
|
+
- Read project-root \`MEMORY.md\` first when it exists; if it is absent, continue without repository memory.
|
|
69
|
+
- For non-trivial implementation or continuation of planned work, first write or restore a plan with \`triage-plan\` active and synchronize it before exploration. Known-target Git/GitHub operations stay plan/goal-free unless they require code edits, conflict resolution, destructive recovery, release/deploy, or design.
|
|
70
70
|
- Plans must be decision-complete before implementation.
|
|
71
71
|
- Run complexity triage before applying agent, plugin, or delegated workflow guidance.
|
|
72
72
|
- Record the selected Light Path or Heavy Path and the checked Heavy Path signals in \`docs/plan/<topic>.md\`.
|
|
73
|
+
- Record request, planning, and code checkpoints. When the route changes before editing, synchronize the plan artifact, repository goal task roadmap through \`goal replan\`, and native UI plan. After editing starts, a new Heavy Path signal promotes Light Path to Heavy Path; stop further edits, mark affected evidence stale, synchronize all three roadmaps, and repeat the code checkpoint.
|
|
74
|
+
- Keep models inherited. Record requested/effective reasoning effort per task: Light planning/implementation/verification=\`medium\`; Heavy planning/review/implementation=\`high\`; final verification/review=\`xhigh\`. A route change also synchronizes the reasoning effort profile, preserving completed profiles and recalculating incomplete profiles. Unsupported host/value means effective=\`inherited\` with no model/global config change.
|
|
73
75
|
- Split implementation into small module tasks.
|
|
74
76
|
- Detect repository stack and required LSP/check tools before implementation.
|
|
75
77
|
- Record missing required tools and suggested commands. Run only pinned, supported installation steps after explicit user approval.
|
|
@@ -97,11 +99,13 @@ const planTemplateKo = `# PLAN
|
|
|
97
99
|
|
|
98
100
|
## Planning Rules
|
|
99
101
|
|
|
100
|
-
-
|
|
101
|
-
- 구현 또는
|
|
102
|
+
- 프로젝트 루트 \`MEMORY.md\`가 있으면 먼저 읽고, 없으면 저장소 메모리 없이 계속 진행합니다.
|
|
103
|
+
- 비단순 구현 또는 계획된 작업 재개는 먼저 \`triage-plan\`이 active인 계획을 쓰거나 복원하고 탐색 전에 동기화합니다. 대상이 확정된 Git/GitHub 운영은 코드 수정·충돌 해결·파괴적 복구·릴리스/배포·설계가 필요하지 않으면 plan/goal 없이 실행합니다.
|
|
102
104
|
- 계획은 구현 전에 의사결정이 완료된 상태여야 합니다.
|
|
103
105
|
- 에이전트, 플러그인, 위임 워크플로 지침을 적용하기 전에 복잡도 트리아지를 실행합니다.
|
|
104
106
|
- 선택한 Light Path 또는 Heavy Path와 확인한 Heavy Path 신호를 \`docs/plan/<topic>.md\`에 기록합니다.
|
|
107
|
+
- 요청, 계획, 코드 체크포인트를 기록합니다. 편집 전에 경로가 바뀌면 plan artifact, 저장소 goal task roadmap, native UI plan을 동기화합니다. 편집 뒤 새 Heavy Path 신호가 나오면 추가 편집을 중단하고 관련 증거를 stale로 표시한 뒤 세 roadmap과 코드 체크포인트를 다시 맞춥니다.
|
|
108
|
+
- 모델은 상속하고 task별 requested/effective reasoning effort를 기록합니다. Light 계획/구현/검증은 \`medium\`, Heavy 계획/검토/구현은 \`high\`, 최종 검증/검토는 \`xhigh\`입니다. 경로 변경 시 reasoning effort profile도 동기화하고 완료 profile은 보존하며 미완료 profile은 재계산합니다. 미지원 host/value는 model/global config 변경 없이 effective=\`inherited\`입니다.
|
|
105
109
|
- 구현은 작은 모듈 작업으로 나눕니다.
|
|
106
110
|
- 구현 전에 저장소 스택과 필요한 LSP/check 도구를 감지합니다.
|
|
107
111
|
- 필요한 도구가 없으면 누락 상태와 제안 명령을 기록합니다. 명시적 사용자 승인 뒤에만 고정된 지원 설치 단계를 실행합니다.
|
|
@@ -157,11 +161,17 @@ export async function runStateResult(event = "manual", {
|
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
if (event === "session-start" || event === "user-prompt-submit") {
|
|
160
|
-
const
|
|
164
|
+
const reconciled = await reconcileWorktreeState({ root: resolvedRoot });
|
|
165
|
+
const state = reconciled.state;
|
|
161
166
|
if (state?.goal?.status === "active" && !isGoalOwnedBySession(state, payload?.session_id)) {
|
|
162
167
|
return foreignSessionContextResult({ ko, event });
|
|
163
168
|
}
|
|
164
|
-
|
|
169
|
+
const result = contextResult({ ko, state, event });
|
|
170
|
+
if (!reconciled.removed) return result;
|
|
171
|
+
const notice = ko
|
|
172
|
+
? "Cairn worktree 점검: 현재 worktree에 속하지 않는 stale Cairn 상태를 제거했습니다."
|
|
173
|
+
: "Cairn worktree check: removed stale Cairn state that did not belong to this worktree.";
|
|
174
|
+
return prependContext(result, notice);
|
|
165
175
|
}
|
|
166
176
|
|
|
167
177
|
if (event === "post-tool-use") {
|
|
@@ -181,7 +191,7 @@ export async function runStateResult(event = "manual", {
|
|
|
181
191
|
}
|
|
182
192
|
|
|
183
193
|
if (event === "stop" || event === "subagent-stop") {
|
|
184
|
-
const state = await
|
|
194
|
+
const { state } = await reconcileWorktreeState({ root: resolvedRoot });
|
|
185
195
|
if (!isGoalOwnedBySession(state, payload?.session_id)) return { status: 0, message: "", hookOutput: {} };
|
|
186
196
|
const gate = evaluateStop(state, {
|
|
187
197
|
subagent: event === "subagent-stop",
|
|
@@ -223,18 +233,34 @@ async function writeIfMissing(root, path, content) {
|
|
|
223
233
|
}
|
|
224
234
|
}
|
|
225
235
|
|
|
236
|
+
function prependContext(result, notice) {
|
|
237
|
+
const specific = result.hookOutput?.hookSpecificOutput;
|
|
238
|
+
if (!specific) return { ...result, message: `${notice}\n${result.message}` };
|
|
239
|
+
return {
|
|
240
|
+
...result,
|
|
241
|
+
message: `${notice}\n${result.message}`,
|
|
242
|
+
hookOutput: {
|
|
243
|
+
...result.hookOutput,
|
|
244
|
+
hookSpecificOutput: {
|
|
245
|
+
...specific,
|
|
246
|
+
additionalContext: `${notice}\n${specific.additionalContext}`,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
226
252
|
function contextResult({ ko, state, event }) {
|
|
227
|
-
const base = ko ? "Cairn kernel: 루트 MEMORY.md
|
|
253
|
+
const base = ko ? "Cairn kernel: 루트 MEMORY.md는 선택 사항이며, 있으면 읽으세요." : "Cairn kernel: root MEMORY.md is optional; read it when present.";
|
|
228
254
|
const idlePolicy = event === "user-prompt-submit"
|
|
229
255
|
? (ko
|
|
230
|
-
? "
|
|
231
|
-
: " For implementation
|
|
256
|
+
? " 비단순 구현이나 계획된 작업 재개는 cairn-plan과 plan/task를 복원·생성하세요. 대상이 확정된 Git/GitHub 상태·fetch·checkout·merge·push·PR 작업은 코드 수정·충돌 해결·파괴적 복구·릴리스/배포·설계가 필요하지 않으면 plan/goal 없이 실행하세요. 상담·설명·계획 전용도 goal 없이 처리하세요."
|
|
257
|
+
: " For non-trivial implementation or planned-work continuation, load cairn-plan and restore/create its plan and task. Known-target Git/GitHub operations stay plan/goal-free unless they require code edits, conflict resolution, destructive recovery, release/deploy, or design. Consultation, explanation, and plan-only requests are also goal-free.")
|
|
232
258
|
: (ko
|
|
233
259
|
? " 중요 작업 전에 cairn-plan을 로드하고 active plan과 current task를 복원하세요."
|
|
234
260
|
: " Before non-trivial work, load cairn-plan and restore the active plan and current task.");
|
|
235
261
|
const failClosed = ko
|
|
236
|
-
? " state
|
|
237
|
-
: "
|
|
262
|
+
? " active state와 그 skill·plan·required task reference가 없거나 읽을 수 없거나 일치하지 않을 때만 중단하세요."
|
|
263
|
+
: " Stop only if active state and its skill, plan, or required task ref is missing, unreadable, or inconsistent.";
|
|
238
264
|
if (!state || state.goal.status !== "active") return contextHookResult({ event, message: `${base}${idlePolicy}${failClosed}` });
|
|
239
265
|
const task = state.tasks.find((item) => item.status === "active")
|
|
240
266
|
?? state.tasks.find((item) => item.status === "pending")
|
|
@@ -273,8 +299,8 @@ function contextResult({ ko, state, event }) {
|
|
|
273
299
|
|
|
274
300
|
function foreignSessionContextResult({ ko, event }) {
|
|
275
301
|
const message = ko
|
|
276
|
-
? "Cairn kernel: 루트 MEMORY.md
|
|
277
|
-
: "Cairn kernel:
|
|
302
|
+
? "Cairn kernel: 루트 MEMORY.md는 선택 사항이며, 있으면 읽으세요. 저장소 goal을 다른 session이 소유합니다. cairn goal status로 상태를 확인하고 소유권이 해결될 때까지 Cairn 작업을 시작·수정·위임·완료하지 마세요. 이 capsule은 goal, plan, task 상세를 노출하지 않습니다."
|
|
303
|
+
: "Cairn kernel: root MEMORY.md is optional; read it when present. A repository goal is owned by another session. Inspect cairn goal status; until ownership is resolved, do not start, edit, delegate, or complete Cairn work. This capsule does not expose goal, plan, or task details.";
|
|
278
304
|
return contextHookResult({ event, message });
|
|
279
305
|
}
|
|
280
306
|
|
package/scripts/cairn-state.sh
CHANGED
|
@@ -84,8 +84,8 @@ if [ ! -f "$plan" ]; then
|
|
|
84
84
|
|
|
85
85
|
## Planning Rules
|
|
86
86
|
|
|
87
|
-
-
|
|
88
|
-
- 구현 또는
|
|
87
|
+
- 프로젝트 루트 `MEMORY.md`가 있으면 먼저 읽고, 없으면 저장소 메모리 없이 계속 진행합니다.
|
|
88
|
+
- 비단순 구현 또는 계획된 작업 재개는 먼저 `triage-plan`이 active인 계획을 쓰거나 복원하고 탐색 전에 동기화합니다. 대상이 확정된 Git/GitHub 운영은 코드 수정·충돌 해결·파괴적 복구·릴리스/배포·설계가 필요하지 않으면 plan/goal 없이 실행합니다.
|
|
89
89
|
- 계획은 구현 전에 의사결정이 완료된 상태여야 합니다.
|
|
90
90
|
- 에이전트, 플러그인, 위임 워크플로 지침을 적용하기 전에 복잡도 트리아지를 실행합니다.
|
|
91
91
|
- 선택한 Light Path 또는 Heavy Path와 확인한 Heavy Path 신호를 `docs/plan/<topic>.md`에 기록합니다.
|
|
@@ -117,8 +117,8 @@ This file is a short index of active and completed work plans.
|
|
|
117
117
|
|
|
118
118
|
## Planning Rules
|
|
119
119
|
|
|
120
|
-
-
|
|
121
|
-
- For implementation or
|
|
120
|
+
- Read project-root `MEMORY.md` first when it exists; if it is absent, continue without repository memory.
|
|
121
|
+
- For non-trivial implementation or continuation of planned work, first write or restore a plan with `triage-plan` active and synchronize it before exploration. Known-target Git/GitHub operations stay plan/goal-free unless they require code edits, conflict resolution, destructive recovery, release/deploy, or design.
|
|
122
122
|
- Plans must be decision-complete before implementation.
|
|
123
123
|
- Split implementation into small module tasks.
|
|
124
124
|
- Detect repository stack and required LSP/check tools before implementation.
|
|
@@ -138,9 +138,9 @@ fi
|
|
|
138
138
|
case "$event" in
|
|
139
139
|
session-start|user-prompt-submit)
|
|
140
140
|
if is_ko; then
|
|
141
|
-
printf '%s\n' "Cairn 컨텍스트:
|
|
141
|
+
printf '%s\n' "Cairn 컨텍스트: 프로젝트 루트 MEMORY.md가 있으면 읽고, 없으면 저장소 메모리 없이 계속 진행합니다."
|
|
142
142
|
else
|
|
143
|
-
printf '%s\n' "Cairn context:
|
|
143
|
+
printf '%s\n' "Cairn context: read project-root MEMORY.md first when present; if absent, continue without repository memory."
|
|
144
144
|
fi
|
|
145
145
|
;;
|
|
146
146
|
post-tool-use)
|
package/scripts/cairn.mjs
CHANGED
|
@@ -31,6 +31,11 @@ export async function main(command = "help", args = [], { runner = spawnSync } =
|
|
|
31
31
|
process.exitCode = result.status ?? 1;
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
+
if (command === "cleanup") {
|
|
35
|
+
const result = runner(process.execPath, [join(scriptDir, "cairn-cleanup.mjs"), ...args], { stdio: "inherit" });
|
|
36
|
+
process.exitCode = result.status ?? 1;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
34
39
|
if (["memory", "plan", "work", "review"].includes(command)) {
|
|
35
40
|
console.log(message(command));
|
|
36
41
|
return;
|
|
@@ -68,65 +73,68 @@ function isCliEntry() {
|
|
|
68
73
|
const planExecutionPolicy = "The user-called/main agent is the orchestrator; implementation edits go to worker subagents whenever subagent tools are available. When the subagent tool provides a progress-reporting channel, subagents report status to the orchestrator when starting work, when deciding or confirming direction, during periodic progress, and when finishing; the orchestrator must immediately relay received status events to the user. If no mid-run reporting channel exists, the orchestrator relays observable events such as assignment, waiting, and final completion. When a delegated subagent finishes, it must provide its final report before leaving; after the orchestrator captures the final report and evidence, the orchestrator must close or release the completed subagent, then review the final report and evidence before marking the work complete. If subagent tools are unavailable, the main agent takes over implementation directly and records that takeover in evidence.";
|
|
69
74
|
const workExecutionPolicy = "The user-called/main agent orchestrates, while worker subagents perform implementation edits whenever subagent tools are available. When the subagent tool provides a progress-reporting channel, subagents report status to the orchestrator when starting work, when deciding or confirming direction, during periodic progress, and when finishing; the orchestrator must immediately relay received status events to the user. If no mid-run reporting channel exists, the orchestrator relays observable events such as assignment, waiting, and final completion. When a delegated subagent finishes, it must provide its final report before leaving; after the orchestrator captures the final report and evidence, the orchestrator must close or release the completed subagent, then review the final report and evidence before marking the work complete. If subagent tools are unavailable, the main agent takes over implementation directly and records that takeover in evidence.";
|
|
70
75
|
const tokenEfficientWorkPolicy = "Use a test contract for requirements, invariants, boundaries, and failure modes before the minimum implementation. The tool exit code is authoritative. Run goal verify -- <argv> and expand only failures. Inspect package lifecycle scripts: content-producing or unknown scripts must never use --ignore-scripts; only absent or proven content-neutral scripts may use it while prior full-check evidence remains fresh.";
|
|
76
|
+
const stagedTriagePolicy = "Use request, planning, and code checkpoints. Before the first edit, evidence may change either route; synchronize the plan artifact, repository goal task roadmap through goal replan, and native UI plan. After editing starts, a new Heavy Path signal promotes Light Path to Heavy Path: stop further edits, mark affected evidence stale, synchronize all three roadmaps, and repeat the code checkpoint.";
|
|
77
|
+
const stagedTriagePolicyKo = "요청, 계획, 코드 체크포인트를 사용합니다. 첫 편집 전에는 증거에 따라 어느 경로로든 바꿀 수 있으며 plan artifact, 저장소 goal task roadmap, native UI plan을 동기화합니다. 편집 뒤 새 Heavy Path 신호가 나오면 Light Path를 Heavy Path로 승격하고 추가 편집을 중단하며 관련 증거를 stale로 표시한 뒤 세 roadmap과 코드 체크포인트를 다시 맞춥니다.";
|
|
78
|
+
const reasoningEffortPolicy = "Inherit models; route reasoning effort by task: Light planning/implementation/verification=medium; Heavy planning/review/implementation=high; final verification/review=xhigh. Only new task dispatch may use a supported host option. Unsupported host/value means effective=inherited; never change model/global config.";
|
|
71
79
|
const artifactLocalePolicy = "Write user-visible responses and generated or updated documentation, plans, and memory artifacts in the OS locale unless the user asks for another language.";
|
|
72
80
|
const artifactLocalePolicyKo = "사용자가 다른 언어를 요청하지 않는 한, 사용자에게 보이는 응답과 생성 또는 갱신하는 문서, 계획, 메모리 산출물은 OS locale 언어로 작성합니다.";
|
|
73
81
|
|
|
74
82
|
const messages = {
|
|
75
83
|
en: {
|
|
76
84
|
memory: `Use the cairn-memory skill. Keep MEMORY.md as a short index and write details to docs/memory/<domain>.md. ${artifactLocalePolicy}`,
|
|
77
|
-
plan: `Use the cairn-plan skill before non-trivial implementation.
|
|
78
|
-
work: `Use cairn-work only after a plan exists and
|
|
85
|
+
plan: `Use the cairn-plan skill before non-trivial implementation. Read project-root MEMORY.md first when present; if absent, continue without repository memory. Understand the whole work first, then classify it into executable tasks and sub-tasks. Keep PLAN.md as an index, write detailed plans to docs/plan/<topic>.md, and record Light/Heavy Path triage before any mutation. ${stagedTriagePolicy} ${reasoningEffortPolicy} ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
86
|
+
work: `Use cairn-work only after a plan exists. Read root MEMORY.md when present and continue without it when absent. ${tokenEfficientWorkPolicy} Execute one task through its recorded Light/Heavy Path. ${stagedTriagePolicy} ${reasoningEffortPolicy} ${workExecutionPolicy} ${artifactLocalePolicy} Resume active work after side questions unless asked to pause, stop, or switch.`,
|
|
79
87
|
review: `Use the cairn-review skill. Compare evidence against PLAN.md and MEMORY.md. ${artifactLocalePolicy}`,
|
|
80
|
-
usage: "Usage: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck|goal|task",
|
|
88
|
+
usage: "Usage: cairn install|upgrade|doctor|uninstall|cleanup|init|memory|plan|work|review|toolcheck|goal|task",
|
|
81
89
|
},
|
|
82
90
|
ko: {
|
|
83
91
|
memory: `cairn-memory 스킬을 사용하세요. MEMORY.md는 짧은 색인으로, 상세 내용은 docs/memory/<domain>.md에 기록합니다. ${artifactLocalePolicyKo}`,
|
|
84
|
-
plan: `중요 구현 전에는 cairn-plan 스킬을 사용하세요.
|
|
85
|
-
work: `계획이
|
|
92
|
+
plan: `중요 구현 전에는 cairn-plan 스킬을 사용하세요. 프로젝트 루트 MEMORY.md가 있으면 먼저 읽고, 없으면 저장소 메모리 없이 계속 진행합니다. 먼저 전체 작업을 파악한 뒤 실행 가능한 task와 필요한 sub-task로 분류합니다. PLAN.md는 색인으로, 상세 계획은 docs/plan/<topic>.md에 기록하며 파일 변경 전 Light/Heavy Path 트리아지를 남깁니다. ${stagedTriagePolicyKo} ${reasoningEffortPolicy} ${artifactLocalePolicyKo} ${planExecutionPolicy}`,
|
|
93
|
+
work: `계획이 있으면 cairn-work를 사용하세요. 루트 MEMORY.md가 있으면 읽고 없으면 무시하고 진행합니다. ${tokenEfficientWorkPolicy} 기록된 Light/Heavy Path로 한 task를 수행합니다. ${stagedTriagePolicyKo} ${reasoningEffortPolicy} ${workExecutionPolicy} ${artifactLocalePolicyKo} 곁가지 질문 뒤에는 일시중지·중단·전환 요청이 없으면 active work를 재개합니다.`,
|
|
86
94
|
review: `cairn-review 스킬을 사용하세요. PLAN.md와 MEMORY.md 기준으로 증거를 비교합니다. ${artifactLocalePolicyKo}`,
|
|
87
|
-
usage: "사용법: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck|goal|task",
|
|
95
|
+
usage: "사용법: cairn install|upgrade|doctor|uninstall|cleanup|init|memory|plan|work|review|toolcheck|goal|task",
|
|
88
96
|
},
|
|
89
97
|
ja: {
|
|
90
98
|
memory: `cairn-memory スキルを使用してください。MEMORY.md は短い索引にし、詳細は docs/memory/<domain>.md に記録します。${artifactLocalePolicy}`,
|
|
91
|
-
plan: `cairn-plan スキルを使用してください。PLAN.md は索引にし、詳細計画は docs/plan/<topic>.md に記録します。${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
92
|
-
work: `cairn-work スキルを使用してください。1つのモジュールスライスを実行し、モジュール証拠と表面証拠を取得します。${tokenEfficientWorkPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
99
|
+
plan: `cairn-plan スキルを使用してください。PLAN.md は索引にし、詳細計画は docs/plan/<topic>.md に記録します。${reasoningEffortPolicy} ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
100
|
+
work: `cairn-work スキルを使用してください。1つのモジュールスライスを実行し、モジュール証拠と表面証拠を取得します。${tokenEfficientWorkPolicy} ${reasoningEffortPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
93
101
|
review: `cairn-review スキルを使用してください。PLAN.md と MEMORY.md に照らして証拠を比較します。${artifactLocalePolicy}`,
|
|
94
|
-
usage: "使い方: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck|goal|task",
|
|
102
|
+
usage: "使い方: cairn install|upgrade|doctor|uninstall|cleanup|init|memory|plan|work|review|toolcheck|goal|task",
|
|
95
103
|
},
|
|
96
104
|
zh: {
|
|
97
105
|
memory: `使用 cairn-memory skill。将 MEMORY.md 保持为简短索引,并将详细内容写入 docs/memory/<domain>.md。${artifactLocalePolicy}`,
|
|
98
|
-
plan: `使用 cairn-plan skill。将 PLAN.md 保持为索引,并将详细计划写入 docs/plan/<topic>.md。${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
99
|
-
work: `使用 cairn-work skill。执行一个模块切片,并收集模块证据。${tokenEfficientWorkPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
106
|
+
plan: `使用 cairn-plan skill。将 PLAN.md 保持为索引,并将详细计划写入 docs/plan/<topic>.md。${reasoningEffortPolicy} ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
107
|
+
work: `使用 cairn-work skill。执行一个模块切片,并收集模块证据。${tokenEfficientWorkPolicy} ${reasoningEffortPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
100
108
|
review: `使用 cairn-review skill。根据 PLAN.md 和 MEMORY.md 对比证据。${artifactLocalePolicy}`,
|
|
101
|
-
usage: "用法: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck|goal|task",
|
|
109
|
+
usage: "用法: cairn install|upgrade|doctor|uninstall|cleanup|init|memory|plan|work|review|toolcheck|goal|task",
|
|
102
110
|
},
|
|
103
111
|
es: {
|
|
104
112
|
memory: `Usa la skill cairn-memory. Mantén MEMORY.md como un índice breve y escribe los detalles en docs/memory/<domain>.md. ${artifactLocalePolicy}`,
|
|
105
|
-
plan: `Usa la skill cairn-plan. Mantén PLAN.md como índice y escribe los planes detallados en docs/plan/<topic>.md. ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
106
|
-
work: `Usa la skill cairn-work. Ejecuta un solo segmento de módulo y captura evidencia de módulo y de superficie. ${tokenEfficientWorkPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
113
|
+
plan: `Usa la skill cairn-plan. Mantén PLAN.md como índice y escribe los planes detallados en docs/plan/<topic>.md. ${reasoningEffortPolicy} ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
114
|
+
work: `Usa la skill cairn-work. Ejecuta un solo segmento de módulo y captura evidencia de módulo y de superficie. ${tokenEfficientWorkPolicy} ${reasoningEffortPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
107
115
|
review: `Usa la skill cairn-review. Compara la evidencia con PLAN.md y MEMORY.md. ${artifactLocalePolicy}`,
|
|
108
|
-
usage: "Uso: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck|goal|task",
|
|
116
|
+
usage: "Uso: cairn install|upgrade|doctor|uninstall|cleanup|init|memory|plan|work|review|toolcheck|goal|task",
|
|
109
117
|
},
|
|
110
118
|
fr: {
|
|
111
119
|
memory: `Utilisez la skill cairn-memory. Gardez MEMORY.md comme index court et écrivez les détails dans docs/memory/<domain>.md. ${artifactLocalePolicy}`,
|
|
112
|
-
plan: `Utilisez la skill cairn-plan. Gardez MEMORY.md comme index et écrivez les plans détaillés dans docs/plan/<topic>.md. ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
113
|
-
work: `Utilisez la skill cairn-work. Exécutez une tranche de module et capturez les preuves de module et de surface. ${tokenEfficientWorkPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
120
|
+
plan: `Utilisez la skill cairn-plan. Gardez MEMORY.md comme index et écrivez les plans détaillés dans docs/plan/<topic>.md. ${reasoningEffortPolicy} ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
121
|
+
work: `Utilisez la skill cairn-work. Exécutez une tranche de module et capturez les preuves de module et de surface. ${tokenEfficientWorkPolicy} ${reasoningEffortPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
114
122
|
review: `Utilisez la skill cairn-review. Comparez les preuves avec PLAN.md et MEMORY.md. ${artifactLocalePolicy}`,
|
|
115
|
-
usage: "Utilisation: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck|goal|task",
|
|
123
|
+
usage: "Utilisation: cairn install|upgrade|doctor|uninstall|cleanup|init|memory|plan|work|review|toolcheck|goal|task",
|
|
116
124
|
},
|
|
117
125
|
de: {
|
|
118
126
|
memory: `Verwenden Sie die Skill cairn-memory. Halten Sie MEMORY.md als kurzen Index und schreiben Sie Details nach docs/memory/<domain>.md. ${artifactLocalePolicy}`,
|
|
119
|
-
plan: `Verwenden Sie die Skill cairn-plan. Halten Sie MEMORY.md als Index und schreiben Sie Detailpläne nach docs/plan/<topic>.md. ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
120
|
-
work: `Verwenden Sie die Skill cairn-work. Führen Sie einen Modulabschnitt aus und erfassen Sie Modul- und Oberflächenbelege. ${tokenEfficientWorkPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
127
|
+
plan: `Verwenden Sie die Skill cairn-plan. Halten Sie MEMORY.md als Index und schreiben Sie Detailpläne nach docs/plan/<topic>.md. ${reasoningEffortPolicy} ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
128
|
+
work: `Verwenden Sie die Skill cairn-work. Führen Sie einen Modulabschnitt aus und erfassen Sie Modul- und Oberflächenbelege. ${tokenEfficientWorkPolicy} ${reasoningEffortPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
121
129
|
review: `Verwenden Sie die Skill cairn-review. Vergleichen Sie Belege mit PLAN.md und MEMORY.md. ${artifactLocalePolicy}`,
|
|
122
|
-
usage: "Verwendung: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck|goal|task",
|
|
130
|
+
usage: "Verwendung: cairn install|upgrade|doctor|uninstall|cleanup|init|memory|plan|work|review|toolcheck|goal|task",
|
|
123
131
|
},
|
|
124
132
|
pt: {
|
|
125
133
|
memory: `Use a skill cairn-memory. Mantenha MEMORY.md como um índice curto e escreva detalhes em docs/memory/<domain>.md. ${artifactLocalePolicy}`,
|
|
126
|
-
plan: `Use a skill cairn-plan. Mantenha MEMORY.md como índice e escreva planos detalhados em docs/plan/<topic>.md. ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
127
|
-
work: `Use a skill cairn-work. Execute uma fatia de módulo e capture evidências de módulo e de superfície. ${tokenEfficientWorkPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
134
|
+
plan: `Use a skill cairn-plan. Mantenha MEMORY.md como índice e escreva planos detalhados em docs/plan/<topic>.md. ${reasoningEffortPolicy} ${artifactLocalePolicy} ${planExecutionPolicy}`,
|
|
135
|
+
work: `Use a skill cairn-work. Execute uma fatia de módulo e capture evidências de módulo e de superfície. ${tokenEfficientWorkPolicy} ${reasoningEffortPolicy} ${workExecutionPolicy} ${artifactLocalePolicy}`,
|
|
128
136
|
review: `Use a skill cairn-review. Compare evidências com PLAN.md e MEMORY.md. ${artifactLocalePolicy}`,
|
|
129
|
-
usage: "Uso: cairn install|upgrade|doctor|uninstall|init|memory|plan|work|review|toolcheck|goal|task",
|
|
137
|
+
usage: "Uso: cairn install|upgrade|doctor|uninstall|cleanup|init|memory|plan|work|review|toolcheck|goal|task",
|
|
130
138
|
},
|
|
131
139
|
};
|
|
132
140
|
|