@cairnkeep/cli 2.13.1 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -1
- package/README.md +29 -6
- package/claude/commands/cairn-work.md +19 -0
- package/docs/context-packs.md +13 -3
- package/docs/design/team-mode.md +145 -0
- package/docs/harness-compatibility.md +12 -11
- package/docs/learning/CURRICULUM-MAP.md +8 -4
- package/docs/learning/FEATURE-GUIDE.md +13 -5
- package/docs/learning/README.md +3 -1
- package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
- package/docs/learning/lessons/L01-safe-trial.md +3 -3
- package/docs/learning/lessons/L02-installation.md +2 -2
- package/docs/learning/lessons/L03-first-project.md +1 -1
- package/docs/learning/lessons/L19-validated-skill-improvement.md +1 -1
- package/docs/learning/lessons/L20-mcp-tool-profiles.md +1 -1
- package/docs/learning/lessons/L21-context-packs.md +5 -1
- package/docs/learning/lessons/L22-native-windows.md +2 -2
- package/docs/learning/lessons/L23-guided-setup.md +2 -2
- package/docs/learning/lessons/L24-okf-exchange.md +111 -0
- package/docs/learning/lessons/L25-playbooks.md +114 -0
- package/docs/learning/tracks/operator.md +8 -2
- package/docs/learning/tracks/practitioner.md +5 -3
- package/docs/learning/video-scripts/V24-okf-exchange.md +31 -0
- package/docs/learning/video-scripts/V25-playbooks.md +38 -0
- package/docs/native-windows.md +12 -0
- package/docs/open-knowledge-format.md +100 -0
- package/docs/operating.md +68 -0
- package/docs/privacy-and-data-flow.md +25 -0
- package/docs/quickstart.md +17 -1
- package/docs/storage.md +28 -0
- package/examples/eval/bundled-fake.json +1 -1
- package/kimi/skills/cairn-work/SKILL.md +13 -0
- package/mcp-memory-server/dist/context-pack-cli.js +54 -1
- package/mcp-memory-server/dist/context-pack.js +243 -47
- package/mcp-memory-server/dist/index.js +13 -1
- package/mcp-memory-server/dist/mcp-tool-catalog.js +1 -0
- package/mcp-memory-server/dist/note-store.js +44 -2
- package/mcp-memory-server/dist/okf.js +432 -0
- package/mcp-memory-server/dist/playbook-cli.js +321 -0
- package/mcp-memory-server/dist/playbook-receipt.js +245 -0
- package/mcp-memory-server/dist/playbook-schema.js +56 -0
- package/mcp-memory-server/dist/playbook.js +383 -0
- package/mcp-memory-server/package.json +4 -1
- package/opencode/command/cairn-work.md +13 -0
- package/package.json +2 -1
- package/pi/prompts/cairn-work.md +12 -0
- package/schemas/context-pack.schema.json +9 -0
- package/schemas/playbook.schema.json +29 -0
- package/scripts/bootstrap.sh +17 -0
- package/scripts/cairn-cli.mjs +2 -0
- package/scripts/completion.sh +26 -6
- package/scripts/doctor.sh +14 -1
- package/scripts/playbook-instructions.mjs +108 -0
- package/scripts/setup-core.mjs +1 -0
- package/scripts/setup.mjs +8 -0
- package/scripts/sync-kimi-assets.sh +3 -3
- package/scripts/sync-opencode-memory-assets.sh +2 -1
- package/scripts/sync-pi-assets.sh +2 -1
- package/scripts/test-cli-dispatch.sh +2 -0
- package/scripts/test-completion.sh +11 -2
- package/scripts/test-graph-harness-adapters.sh +9 -3
- package/scripts/test-harness-registry.sh +11 -0
- package/scripts/test-learning-docs.sh +23 -2
- package/scripts/test-package-install.sh +26 -0
- package/scripts/test-playbook-instructions.sh +60 -0
- package/scripts/test-windows-native.mjs +7 -0
- package/scripts/uninstall.sh +16 -1
- package/scripts/windows-platform.mjs +24 -3
- package/templates/playbook-agent-instructions.md.template +14 -0
- package/templates/playbooks.json.template +5 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { constants, existsSync } from "node:fs";
|
|
3
|
+
import { lstat, mkdir, open, realpath, rm, stat } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { atomicReplace, hardenPrivatePath, privatePathIsSafe } from "./platform-security.js";
|
|
6
|
+
import { PLAYBOOK_ACTION_IDS, PLAYBOOK_CHANGE_TYPES, PLAYBOOK_PROFILES, PLAYBOOK_SCHEMA_VERSION, playbookActorSchema, playbookConfigSchema, playbookEvidenceSchema, playbookSignalsSchema, } from "./playbook-schema.js";
|
|
7
|
+
const MAX_CONFIG_BYTES = 64 * 1024;
|
|
8
|
+
const LOCK_ATTEMPTS = 200;
|
|
9
|
+
const LOCK_WAIT_MS = 10;
|
|
10
|
+
const SESSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
11
|
+
export const PLAYBOOK_ACTIONS = Object.freeze([
|
|
12
|
+
{ id: "context.recall", title: "Recall durable context", command: "/recall <task topic>", description: "Read accepted project memory and derived knowledge before committing to an approach." },
|
|
13
|
+
{ id: "context.explore", title: "Explore focused code context", command: "/context-explore <question>", description: "Collect compact citations when the relevant code is unfamiliar or structurally complex." },
|
|
14
|
+
{ id: "work.plan", title: "Write a bounded plan", command: "Write and track a concise implementation plan.", description: "Plan non-trivial work before editing and keep the plan proportional to the task." },
|
|
15
|
+
{ id: "verify.tests", title: "Run relevant verification", command: "Run the smallest sufficient tests, then broader gates when risk warrants.", description: "Verify changed behavior before claiming completion." },
|
|
16
|
+
{ id: "review.repository", title: "Review the repository change", command: "/repo-review", description: "Review bugs, security, and maintainability after material code changes." },
|
|
17
|
+
{ id: "review.security", title: "Run the governed security audit", command: "/security-audit", description: "Audit security-sensitive changes through the existing governed workflow." },
|
|
18
|
+
{ id: "docs.update", title: "Align documentation", command: "Update and verify affected operating, API, compatibility, and learning documentation.", description: "Keep public behavior and operator guidance synchronized with the implementation." },
|
|
19
|
+
{ id: "learning.capture", title: "Capture reviewed learnings", command: "/remember or stage a candidate for /memory-review", description: "Propose stable decisions, pitfalls, and patterns without automatically promoting them." },
|
|
20
|
+
]);
|
|
21
|
+
const PROFILE_MODES = Object.freeze({
|
|
22
|
+
minimal: Object.freeze({
|
|
23
|
+
"context.recall": "may",
|
|
24
|
+
"context.explore": "may",
|
|
25
|
+
"work.plan": "may",
|
|
26
|
+
"verify.tests": "must",
|
|
27
|
+
"review.repository": "may",
|
|
28
|
+
"review.security": "should",
|
|
29
|
+
"docs.update": "should",
|
|
30
|
+
"learning.capture": "may",
|
|
31
|
+
}),
|
|
32
|
+
balanced: Object.freeze({
|
|
33
|
+
"context.recall": "should",
|
|
34
|
+
"context.explore": "should",
|
|
35
|
+
"work.plan": "should",
|
|
36
|
+
"verify.tests": "must",
|
|
37
|
+
"review.repository": "should",
|
|
38
|
+
"review.security": "must",
|
|
39
|
+
"docs.update": "should",
|
|
40
|
+
"learning.capture": "should",
|
|
41
|
+
}),
|
|
42
|
+
strict: Object.freeze({
|
|
43
|
+
"context.recall": "must",
|
|
44
|
+
"context.explore": "must",
|
|
45
|
+
"work.plan": "must",
|
|
46
|
+
"verify.tests": "must",
|
|
47
|
+
"review.repository": "must",
|
|
48
|
+
"review.security": "must",
|
|
49
|
+
"docs.update": "must",
|
|
50
|
+
"learning.capture": "should",
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
function sha256(value) {
|
|
54
|
+
return createHash("sha256").update(value).digest("hex");
|
|
55
|
+
}
|
|
56
|
+
function delay(milliseconds) {
|
|
57
|
+
return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
|
|
58
|
+
}
|
|
59
|
+
async function canonicalRoot(projectRoot) {
|
|
60
|
+
const root = resolve(projectRoot);
|
|
61
|
+
const canonical = await realpath(root);
|
|
62
|
+
const info = await stat(canonical);
|
|
63
|
+
if (!info.isDirectory())
|
|
64
|
+
throw new Error("Playbook project root is not a directory.");
|
|
65
|
+
return canonical;
|
|
66
|
+
}
|
|
67
|
+
export async function playbookProjectIdentity(projectRoot = process.cwd()) {
|
|
68
|
+
const root = await canonicalRoot(projectRoot);
|
|
69
|
+
const info = await stat(root);
|
|
70
|
+
return sha256(`cairn:playbook-project:v1\0${root}\0${String(info.dev)}\0${String(info.ino)}`);
|
|
71
|
+
}
|
|
72
|
+
async function paths(projectRoot) {
|
|
73
|
+
const root = await canonicalRoot(projectRoot);
|
|
74
|
+
const directory = join(root, ".ai");
|
|
75
|
+
const config = join(directory, "playbooks.json");
|
|
76
|
+
const lock = join(directory, ".playbooks.lock");
|
|
77
|
+
if (dirname(directory) !== root || dirname(config) !== directory || dirname(lock) !== directory) {
|
|
78
|
+
throw new Error("Playbook configuration path is outside the project root.");
|
|
79
|
+
}
|
|
80
|
+
return { root, directory, config, lock };
|
|
81
|
+
}
|
|
82
|
+
async function safeDirectory(directory, create) {
|
|
83
|
+
try {
|
|
84
|
+
const info = await lstat(directory);
|
|
85
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
86
|
+
throw new Error("Playbook configuration directory is unsafe.");
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error.code !== "ENOENT")
|
|
90
|
+
throw error;
|
|
91
|
+
if (!create)
|
|
92
|
+
return;
|
|
93
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
94
|
+
const info = await lstat(directory);
|
|
95
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
96
|
+
throw new Error("Playbook configuration directory is unsafe.");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function readBounded(path) {
|
|
100
|
+
let handle;
|
|
101
|
+
try {
|
|
102
|
+
handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error.code === "ENOENT")
|
|
106
|
+
return undefined;
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const info = await handle.stat();
|
|
111
|
+
if (!info.isFile() || info.size > MAX_CONFIG_BYTES)
|
|
112
|
+
throw new Error("Playbook configuration is invalid.");
|
|
113
|
+
const bytes = await handle.readFile();
|
|
114
|
+
if (bytes.byteLength > MAX_CONFIG_BYTES)
|
|
115
|
+
throw new Error("Playbook configuration is invalid.");
|
|
116
|
+
return bytes;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
await handle.close();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function readProjectConfig(projectRoot) {
|
|
123
|
+
const { directory, config } = await paths(projectRoot);
|
|
124
|
+
try {
|
|
125
|
+
await safeDirectory(directory, false);
|
|
126
|
+
if (!existsSync(config))
|
|
127
|
+
return { exists: false, issues: [] };
|
|
128
|
+
const info = await lstat(config);
|
|
129
|
+
if (!info.isFile() || info.isSymbolicLink() || !privatePathIsSafe(config)) {
|
|
130
|
+
return { exists: true, issues: ["unsafe-config"] };
|
|
131
|
+
}
|
|
132
|
+
const bytes = await readBounded(config);
|
|
133
|
+
if (!bytes)
|
|
134
|
+
return { exists: false, issues: [] };
|
|
135
|
+
return { config: playbookConfigSchema.parse(JSON.parse(bytes.toString("utf8"))), exists: true, issues: [] };
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return { exists: existsSync(config), issues: ["invalid-config"] };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function orderedOverrides(overrides) {
|
|
142
|
+
const result = {};
|
|
143
|
+
for (const id of PLAYBOOK_ACTION_IDS)
|
|
144
|
+
if (overrides[id] !== undefined)
|
|
145
|
+
result[id] = overrides[id];
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
function orderedConfig(profile, overrides) {
|
|
149
|
+
return playbookConfigSchema.parse({ schema_version: PLAYBOOK_SCHEMA_VERSION, profile, overrides: orderedOverrides(overrides) });
|
|
150
|
+
}
|
|
151
|
+
export async function resolvePlaybookStatus(options = {}) {
|
|
152
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
153
|
+
const env = options.env ?? process.env;
|
|
154
|
+
const stored = await readProjectConfig(projectRoot);
|
|
155
|
+
const issues = [...stored.issues];
|
|
156
|
+
let profile = stored.config?.profile ?? "balanced";
|
|
157
|
+
let source = stored.config ? "project" : "default";
|
|
158
|
+
const rawEnvironmentProfile = env.CAIRN_PLAYBOOK_PROFILE?.trim();
|
|
159
|
+
if (rawEnvironmentProfile) {
|
|
160
|
+
if (PLAYBOOK_PROFILES.includes(rawEnvironmentProfile)) {
|
|
161
|
+
profile = rawEnvironmentProfile;
|
|
162
|
+
source = "environment";
|
|
163
|
+
}
|
|
164
|
+
else
|
|
165
|
+
issues.push("invalid-environment-profile");
|
|
166
|
+
}
|
|
167
|
+
const overrides = orderedOverrides(stored.config?.overrides ?? {});
|
|
168
|
+
const modes = {};
|
|
169
|
+
for (const id of PLAYBOOK_ACTION_IDS)
|
|
170
|
+
modes[id] = overrides[id] ?? PROFILE_MODES[profile][id];
|
|
171
|
+
const policyDigest = sha256(JSON.stringify({ schema_version: PLAYBOOK_SCHEMA_VERSION, profile, modes: PLAYBOOK_ACTION_IDS.map((id) => [id, modes[id]]) }));
|
|
172
|
+
return {
|
|
173
|
+
schema_version: PLAYBOOK_SCHEMA_VERSION,
|
|
174
|
+
profile,
|
|
175
|
+
source,
|
|
176
|
+
overrides,
|
|
177
|
+
modes,
|
|
178
|
+
policy_digest: policyDigest,
|
|
179
|
+
config_exists: stored.exists,
|
|
180
|
+
issues,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
async function acquireLock(lock) {
|
|
184
|
+
for (let attempt = 0; attempt < LOCK_ATTEMPTS; attempt += 1) {
|
|
185
|
+
try {
|
|
186
|
+
await mkdir(lock, { mode: 0o700 });
|
|
187
|
+
return () => rm(lock, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
if (error.code !== "EEXIST")
|
|
191
|
+
throw error;
|
|
192
|
+
const info = await lstat(lock);
|
|
193
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
194
|
+
throw new Error("Playbook configuration lock is unsafe.");
|
|
195
|
+
await delay(LOCK_WAIT_MS);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
throw new Error("Playbook configuration is locked; retry after the active update finishes.");
|
|
199
|
+
}
|
|
200
|
+
async function atomicConfigWrite(path, value) {
|
|
201
|
+
const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
202
|
+
const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
|
|
203
|
+
let handle;
|
|
204
|
+
try {
|
|
205
|
+
handle = await open(temporary, "wx", 0o600);
|
|
206
|
+
await handle.writeFile(bytes);
|
|
207
|
+
await handle.sync();
|
|
208
|
+
await handle.close();
|
|
209
|
+
handle = undefined;
|
|
210
|
+
hardenPrivatePath(temporary);
|
|
211
|
+
await atomicReplace(temporary, path);
|
|
212
|
+
hardenPrivatePath(path);
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
if (handle)
|
|
216
|
+
await handle.close().catch(() => undefined);
|
|
217
|
+
await rm(temporary, { force: true });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function mutatePolicy(projectRoot, mutation, requireMissing = false) {
|
|
221
|
+
const target = await paths(projectRoot);
|
|
222
|
+
await safeDirectory(target.directory, true);
|
|
223
|
+
const release = await acquireLock(target.lock);
|
|
224
|
+
try {
|
|
225
|
+
const read = await readProjectConfig(target.root);
|
|
226
|
+
if (read.issues.length)
|
|
227
|
+
throw new Error("Existing playbook configuration is invalid or unsafe.");
|
|
228
|
+
if (requireMissing && read.config)
|
|
229
|
+
throw new Error("Playbook configuration already exists.");
|
|
230
|
+
await atomicConfigWrite(target.config, mutation(read.config));
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
await release();
|
|
234
|
+
}
|
|
235
|
+
return resolvePlaybookStatus({ projectRoot: target.root, env: {} });
|
|
236
|
+
}
|
|
237
|
+
export async function initializePlaybook(projectRoot, profile) {
|
|
238
|
+
return mutatePolicy(projectRoot, () => orderedConfig(profile, {}), true);
|
|
239
|
+
}
|
|
240
|
+
export async function setPlaybookProfile(projectRoot, profile) {
|
|
241
|
+
return mutatePolicy(projectRoot, (current) => orderedConfig(profile, current?.overrides ?? {}));
|
|
242
|
+
}
|
|
243
|
+
export async function setPlaybookOverride(projectRoot, action, mode) {
|
|
244
|
+
return mutatePolicy(projectRoot, (current) => orderedConfig(current?.profile ?? "balanced", { ...(current?.overrides ?? {}), [action]: mode }));
|
|
245
|
+
}
|
|
246
|
+
export async function resetPlaybookOverride(projectRoot, action) {
|
|
247
|
+
return mutatePolicy(projectRoot, (current) => {
|
|
248
|
+
const overrides = { ...(current?.overrides ?? {}) };
|
|
249
|
+
delete overrides[action];
|
|
250
|
+
return orderedConfig(current?.profile ?? "balanced", overrides);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
export async function resetPlaybook(projectRoot) {
|
|
254
|
+
const target = await paths(projectRoot);
|
|
255
|
+
await safeDirectory(target.directory, false);
|
|
256
|
+
if (!existsSync(target.directory))
|
|
257
|
+
return resolvePlaybookStatus({ projectRoot: target.root, env: {} });
|
|
258
|
+
const release = await acquireLock(target.lock);
|
|
259
|
+
try {
|
|
260
|
+
if (!existsSync(target.config))
|
|
261
|
+
return resolvePlaybookStatus({ projectRoot: target.root, env: {} });
|
|
262
|
+
const info = await lstat(target.config);
|
|
263
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
264
|
+
throw new Error("Playbook configuration is unsafe.");
|
|
265
|
+
await rm(target.config);
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
await release();
|
|
269
|
+
}
|
|
270
|
+
return resolvePlaybookStatus({ projectRoot: target.root, env: {} });
|
|
271
|
+
}
|
|
272
|
+
export function normalizeChangedPaths(values) {
|
|
273
|
+
if (values.length > 256)
|
|
274
|
+
throw new Error("Too many changed paths.");
|
|
275
|
+
return [...new Set(values.map((raw) => {
|
|
276
|
+
const value = raw.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
277
|
+
if (!value || value.length > 512 || /[\u0000-\u001f\u007f]/.test(value) || isAbsolute(value)
|
|
278
|
+
|| /^[A-Za-z]:\//.test(value) || value.split("/").some((part) => part === ".." || part === "")) {
|
|
279
|
+
throw new Error("Changed paths must be safe project-relative paths.");
|
|
280
|
+
}
|
|
281
|
+
return value;
|
|
282
|
+
}))].sort();
|
|
283
|
+
}
|
|
284
|
+
export function inferChangeTypes(paths, explicit = []) {
|
|
285
|
+
const result = new Set(explicit);
|
|
286
|
+
for (const path of normalizeChangedPaths(paths)) {
|
|
287
|
+
const lower = path.toLowerCase();
|
|
288
|
+
const testFile = /(^|\/)(test|tests|spec|specs|__tests__)(\/|$)|\.(test|spec)\./.test(lower);
|
|
289
|
+
if (testFile)
|
|
290
|
+
result.add("tests");
|
|
291
|
+
if (/(^|\/)(docs?|readme|changelog|license)(\/|\.|$)|\.md$/.test(lower))
|
|
292
|
+
result.add("docs");
|
|
293
|
+
if (/(^|\/)(package(-lock)?\.json|pnpm-lock\.yaml|yarn\.lock|requirements[^/]*\.txt|cargo\.lock|go\.(mod|sum))$/.test(lower))
|
|
294
|
+
result.add("dependencies");
|
|
295
|
+
if (/(^|\/)(auth|security|crypto|permission|permissions|acl|secret|secrets|token|tokens|network|http|cors)(\/|\.|-|_)/.test(lower))
|
|
296
|
+
result.add("security");
|
|
297
|
+
if (/\.(json|ya?ml|toml|ini|conf|config)$|(^|\/)(dockerfile|\.github|\.gitlab-ci\.yml)(\/|$)/.test(lower))
|
|
298
|
+
result.add("config");
|
|
299
|
+
if (!testFile && /\.(c|cc|cpp|cs|go|java|js|jsx|kt|mjs|py|rb|rs|sh|swift|ts|tsx)$/.test(lower))
|
|
300
|
+
result.add("code");
|
|
301
|
+
}
|
|
302
|
+
return PLAYBOOK_CHANGE_TYPES.filter((value) => result.has(value));
|
|
303
|
+
}
|
|
304
|
+
function applicability(id, event, signals) {
|
|
305
|
+
const types = new Set(signals.change_types);
|
|
306
|
+
const materialCode = types.has("code") || types.has("dependencies") || types.has("security");
|
|
307
|
+
switch (id) {
|
|
308
|
+
case "context.recall": return event === "start" ? "Task start benefits from durable project context." : null;
|
|
309
|
+
case "context.explore": return event !== "finish" && (signals.familiarity !== "known" || signals.complexity === "complex")
|
|
310
|
+
? "Focused exploration applies because the context is not fully known or the task is complex." : null;
|
|
311
|
+
case "work.plan": return event !== "finish" && signals.complexity !== "trivial"
|
|
312
|
+
? "A bounded plan applies to standard or complex work." : null;
|
|
313
|
+
case "verify.tests": return event === "finish" && materialCode
|
|
314
|
+
? "Behavior-affecting files changed and require verification." : null;
|
|
315
|
+
case "review.repository": return event === "finish" && materialCode
|
|
316
|
+
? "Material code, dependency, or security changes warrant repository review." : null;
|
|
317
|
+
case "review.security": return event === "finish" && (types.has("security") || types.has("dependencies") || signals.risk === "security" || signals.risk === "high")
|
|
318
|
+
? "Security-sensitive paths, dependencies, or elevated risk trigger the governed audit." : null;
|
|
319
|
+
case "docs.update": return event === "finish" && (signals.public_change || types.has("config") || types.has("dependencies"))
|
|
320
|
+
? "Public behavior, configuration, or dependency behavior may require documentation alignment." : null;
|
|
321
|
+
case "learning.capture": return event === "finish" && (materialCode || signals.complexity !== "trivial")
|
|
322
|
+
? "Material work should offer stable learnings to the reviewed memory gate." : null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function evidenceMap(evidence) {
|
|
326
|
+
const result = new Map();
|
|
327
|
+
for (const entry of evidence) {
|
|
328
|
+
const parsed = playbookEvidenceSchema.parse(entry);
|
|
329
|
+
if (result.has(parsed.action))
|
|
330
|
+
throw new Error(`Duplicate evidence for ${parsed.action}.`);
|
|
331
|
+
result.set(parsed.action, parsed);
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
export async function evaluatePlaybook(options) {
|
|
336
|
+
if (!SESSION_PATTERN.test(options.sessionId))
|
|
337
|
+
throw new Error("Invalid playbook session ID.");
|
|
338
|
+
const actor = playbookActorSchema.parse(options.actor);
|
|
339
|
+
const signals = playbookSignalsSchema.parse({
|
|
340
|
+
...options.signals,
|
|
341
|
+
changed_paths: normalizeChangedPaths(options.signals.changed_paths),
|
|
342
|
+
change_types: PLAYBOOK_CHANGE_TYPES.filter((value) => options.signals.change_types.includes(value)),
|
|
343
|
+
});
|
|
344
|
+
const status = await resolvePlaybookStatus({ projectRoot: options.projectRoot, env: options.env });
|
|
345
|
+
const evidence = evidenceMap(options.evidence ?? []);
|
|
346
|
+
const actions = [];
|
|
347
|
+
for (const definition of PLAYBOOK_ACTIONS) {
|
|
348
|
+
const rationale = applicability(definition.id, options.event, signals);
|
|
349
|
+
const mode = status.modes[definition.id];
|
|
350
|
+
if (!rationale || mode === "off")
|
|
351
|
+
continue;
|
|
352
|
+
const observed = evidence.get(definition.id);
|
|
353
|
+
const outcome = observed?.outcome ?? "pending";
|
|
354
|
+
const blocking = mode === "must" && outcome !== "completed";
|
|
355
|
+
actions.push({ ...definition, mode, rationale, outcome, reason: observed?.reason ?? "", blocking });
|
|
356
|
+
}
|
|
357
|
+
const projectIdentity = await playbookProjectIdentity(options.projectRoot);
|
|
358
|
+
const digestInput = {
|
|
359
|
+
schema_version: PLAYBOOK_SCHEMA_VERSION,
|
|
360
|
+
event: options.event,
|
|
361
|
+
project_identity: projectIdentity,
|
|
362
|
+
profile: status.profile,
|
|
363
|
+
policy_digest: status.policy_digest,
|
|
364
|
+
signals,
|
|
365
|
+
actions: actions.map(({ id, mode, outcome, reason }) => ({ id, mode, outcome, reason })),
|
|
366
|
+
};
|
|
367
|
+
const decisionDigest = sha256(JSON.stringify(digestInput));
|
|
368
|
+
return {
|
|
369
|
+
schema_version: PLAYBOOK_SCHEMA_VERSION,
|
|
370
|
+
event: options.event,
|
|
371
|
+
project_identity: projectIdentity,
|
|
372
|
+
actor,
|
|
373
|
+
session_id: options.sessionId,
|
|
374
|
+
profile: status.profile,
|
|
375
|
+
policy_digest: status.policy_digest,
|
|
376
|
+
decision_digest: decisionDigest,
|
|
377
|
+
signals,
|
|
378
|
+
actions,
|
|
379
|
+
blocking_actions: actions.filter(({ blocking }) => blocking).map(({ id }) => id),
|
|
380
|
+
advisory_actions: actions.filter(({ mode, outcome }) => mode !== "must" && outcome === "pending").map(({ id }) => id),
|
|
381
|
+
issues: status.issues,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
@@ -50,9 +50,11 @@
|
|
|
50
50
|
"check:pi-mcp-bridge": "node scripts/smoke-pi-mcp-bridge.mjs",
|
|
51
51
|
"check:context-pack": "node scripts/smoke-context-pack.mjs",
|
|
52
52
|
"check:work-evidence": "node scripts/smoke-work-evidence.mjs",
|
|
53
|
+
"check:okf": "node scripts/smoke-okf.mjs",
|
|
54
|
+
"check:playbook": "node scripts/smoke-playbook.mjs",
|
|
53
55
|
"check:notes": "npm run build && npm run check:note-signatures && npm run check:note-distillation && npm run check:note-enrichment",
|
|
54
56
|
"test": "npm run test:smoke",
|
|
55
|
-
"test:smoke": "npm run build && npm run check:embeddings && npm run check:extract && npm run check:scope-guard && npm run check:http-guard && npm run check:remote-context && npm run check:explore-guard && npm run check:explore-cache && npm run check:explore-crossref && npm run check:route-guard && npm run check:reviewed-memory && npm run check:trajectory-roundtrip && npm run check:trajectory-redaction && npm run check:trajectory-retention && npm run check:note-signatures && npm run check:note-distillation && npm run check:note-enrichment && npm run check:node-compat && npm run check:typed-nodes && npm run check:memory-import && npm run check:note-mcp && npm run check:node-doctor && npm run check:artifact-store && npm run check:artifact-mcp && npm run check:compaction-capture && npm run check:capability-contract && npm run check:capability-logging && npm run check:capability-harness && npm run check:capability-mcp && npm run check:eval-schema && npm run check:eval-process && npm run check:eval-statistics && npm run check:skill-lifecycle && npm run check:graph-cli && npm run check:mcp-trust && npm run check:pi-mcp-bridge && npm run check:context-pack && npm run check:work-evidence",
|
|
57
|
+
"test:smoke": "npm run build && npm run check:embeddings && npm run check:extract && npm run check:scope-guard && npm run check:http-guard && npm run check:remote-context && npm run check:explore-guard && npm run check:explore-cache && npm run check:explore-crossref && npm run check:route-guard && npm run check:reviewed-memory && npm run check:trajectory-roundtrip && npm run check:trajectory-redaction && npm run check:trajectory-retention && npm run check:note-signatures && npm run check:note-distillation && npm run check:note-enrichment && npm run check:node-compat && npm run check:typed-nodes && npm run check:memory-import && npm run check:note-mcp && npm run check:node-doctor && npm run check:artifact-store && npm run check:artifact-mcp && npm run check:compaction-capture && npm run check:capability-contract && npm run check:capability-logging && npm run check:capability-harness && npm run check:capability-mcp && npm run check:eval-schema && npm run check:eval-process && npm run check:eval-statistics && npm run check:skill-lifecycle && npm run check:graph-cli && npm run check:mcp-trust && npm run check:pi-mcp-bridge && npm run check:context-pack && npm run check:work-evidence && npm run check:okf && npm run check:playbook",
|
|
56
58
|
"test:search": "npm run build && npm run check:search",
|
|
57
59
|
"test:memory": "npm run build && npm run check:embeddings && npm run check:extract && npm run check:search",
|
|
58
60
|
"test:all": "npm run test:memory"
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
"dependencies": {
|
|
61
63
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
62
64
|
"agentfs-sdk": "^0.6.4",
|
|
65
|
+
"yaml": "2.9.0",
|
|
63
66
|
"zod": "^4.4.3"
|
|
64
67
|
},
|
|
65
68
|
"devDependencies": {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Evaluate Cairnkeep's project playbook at task start, scope changes, or finish
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- managed-by:cairnkeep -->
|
|
6
|
+
# Cairnkeep playbook check
|
|
7
|
+
|
|
8
|
+
Run exactly one local command: `cairn playbook check $ARGUMENTS`.
|
|
9
|
+
|
|
10
|
+
Accept `start`, `check`, or `finish` followed only by documented check options.
|
|
11
|
+
Safely quote every argument. Return the decision and explain which `must`,
|
|
12
|
+
`should`, and `may` actions you will follow. This is policy evaluation only: it
|
|
13
|
+
does not execute an action or grant approval.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cairnkeep/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
4
|
"description": "A durable, harness-agnostic memory + context layer for coding agents and MCP clients.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
41
41
|
"agentfs-sdk": "^0.6.4",
|
|
42
|
+
"yaml": "2.9.0",
|
|
42
43
|
"zod": "^4.4.3"
|
|
43
44
|
},
|
|
44
45
|
"scripts": {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Evaluate Cairnkeep's bounded workflow playbook
|
|
3
|
+
argument-hint: "start|check|finish [cairn playbook check options]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- managed-by:cairnkeep -->
|
|
7
|
+
# Cairnkeep playbook check
|
|
8
|
+
|
|
9
|
+
Invoke the shell tool exactly once with `cairn playbook check $ARGUMENTS`.
|
|
10
|
+
Accept only `start`, `check`, or `finish` plus documented check options. Return
|
|
11
|
+
the decision verbatim, then state how you will handle its `must`, `should`, and
|
|
12
|
+
`may` actions. Do not treat the decision as execution or approval.
|
|
@@ -12,6 +12,15 @@
|
|
|
12
12
|
"title": { "type": "string", "minLength": 1, "maxLength": 256 },
|
|
13
13
|
"description": { "type": "string", "minLength": 1, "maxLength": 2048 },
|
|
14
14
|
"license": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
15
|
+
"source_format": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"required": ["name", "version"],
|
|
19
|
+
"properties": {
|
|
20
|
+
"name": { "const": "okf" },
|
|
21
|
+
"version": { "type": "string", "minLength": 1, "maxLength": 32 }
|
|
22
|
+
}
|
|
23
|
+
},
|
|
15
24
|
"files": {
|
|
16
25
|
"type": "array",
|
|
17
26
|
"maxItems": 1024,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/cairnkeep/cairnkeep/main/schemas/playbook.schema.json",
|
|
4
|
+
"title": "Cairnkeep playbook policy v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "profile", "overrides"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": 1 },
|
|
10
|
+
"profile": { "enum": ["minimal", "balanced", "strict"] },
|
|
11
|
+
"overrides": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": {
|
|
15
|
+
"context.recall": { "$ref": "#/$defs/mode" },
|
|
16
|
+
"context.explore": { "$ref": "#/$defs/mode" },
|
|
17
|
+
"work.plan": { "$ref": "#/$defs/mode" },
|
|
18
|
+
"verify.tests": { "$ref": "#/$defs/mode" },
|
|
19
|
+
"review.repository": { "$ref": "#/$defs/mode" },
|
|
20
|
+
"review.security": { "$ref": "#/$defs/mode" },
|
|
21
|
+
"docs.update": { "$ref": "#/$defs/mode" },
|
|
22
|
+
"learning.capture": { "$ref": "#/$defs/mode" }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"$defs": {
|
|
27
|
+
"mode": { "enum": ["must", "should", "may", "off"] }
|
|
28
|
+
}
|
|
29
|
+
}
|
package/scripts/bootstrap.sh
CHANGED
|
@@ -40,6 +40,18 @@ if [[ $untracked -eq 1 ]]; then
|
|
|
40
40
|
exclude_prefix=$(cd "$target" && git rev-parse --show-prefix)
|
|
41
41
|
fi
|
|
42
42
|
|
|
43
|
+
if [[ $untracked -eq 0 ]]; then
|
|
44
|
+
set +e
|
|
45
|
+
node "$CAIRN_ROOT/scripts/playbook-instructions.mjs" "$target" --check >/dev/null 2>&1
|
|
46
|
+
instructions_check=$?
|
|
47
|
+
set -e
|
|
48
|
+
if [[ $instructions_check -gt 1 ]]; then
|
|
49
|
+
echo "Cannot safely reconcile Cairnkeep playbook instructions in $target/AGENTS.md" >&2
|
|
50
|
+
echo "Resolve the managed markers, then retry bootstrap." >&2
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
54
|
+
|
|
43
55
|
install_file() {
|
|
44
56
|
local src="$1" dest="$2" mode="$3"
|
|
45
57
|
if [[ -e "$dest" ]]; then
|
|
@@ -59,6 +71,11 @@ done < <(node "$CAIRN_ROOT/scripts/harness-registry.mjs" bootstrap-assets)
|
|
|
59
71
|
install_file "$TPL/env.example.template" "$target/.ai/env.example" 0644
|
|
60
72
|
install_file "$TPL/trajectory-redaction.json.template" "$target/.ai/trajectory-redaction.json" 0644
|
|
61
73
|
install_file "$TPL/capabilities.json.template" "$target/.ai/capabilities.json" 0600
|
|
74
|
+
install_file "$TPL/playbooks.json.template" "$target/.ai/playbooks.json" 0600
|
|
75
|
+
|
|
76
|
+
if [[ $untracked -eq 0 ]]; then
|
|
77
|
+
node "$CAIRN_ROOT/scripts/playbook-instructions.mjs" "$target" >/dev/null
|
|
78
|
+
fi
|
|
62
79
|
|
|
63
80
|
# Project-scope memory is private runtime data. Keep it out of version control
|
|
64
81
|
# even before the first .agentfs/project.db is created.
|
package/scripts/cairn-cli.mjs
CHANGED
|
@@ -18,6 +18,7 @@ const NODE_COMMANDS = new Map([
|
|
|
18
18
|
["skill", "skill-cli.js"],
|
|
19
19
|
["graph", "graph-cli.js"],
|
|
20
20
|
["evidence", "work-evidence-cli.js"],
|
|
21
|
+
["playbook", "playbook-cli.js"],
|
|
21
22
|
]);
|
|
22
23
|
|
|
23
24
|
const POSIX_COMMANDS = new Map([
|
|
@@ -52,6 +53,7 @@ Usage:
|
|
|
52
53
|
cairn skill <harvest|list|show|review|propose|evaluate|apply|rollback>
|
|
53
54
|
cairn graph <build|query|status|diff|explain|path>
|
|
54
55
|
cairn evidence <list|show|delete|prune|doctor>
|
|
56
|
+
cairn playbook <list|status|init|set|enable|disable|reset|check|record|receipts|instructions|doctor>
|
|
55
57
|
cairn memory <path|export|import>
|
|
56
58
|
cairn audit-timer [--on-calendar SPEC] [--para-root PATH] [--render-only DIR]
|
|
57
59
|
cairn uninstall [--dry-run] [--yes] [--purge-memory] [--purge-packs] [PROJECT ...]
|
package/scripts/completion.sh
CHANGED
|
@@ -13,7 +13,7 @@ _cairn_complete() {
|
|
|
13
13
|
COMPREPLY=()
|
|
14
14
|
current=${COMP_WORDS[COMP_CWORD]}
|
|
15
15
|
previous=${COMP_WORDS[COMP_CWORD-1]:-}
|
|
16
|
-
commands="bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence capabilities mcp-tools pack notes eval skill graph memory audit-timer uninstall completion version help"
|
|
16
|
+
commands="bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence playbook capabilities mcp-tools pack notes eval skill graph memory audit-timer uninstall completion version help"
|
|
17
17
|
if (( COMP_CWORD == 1 )); then
|
|
18
18
|
COMPREPLY=( $(compgen -W "$commands" -- "$current") )
|
|
19
19
|
return
|
|
@@ -28,6 +28,7 @@ _cairn_complete() {
|
|
|
28
28
|
trajectory) COMPREPLY=( $(compgen -W "list show prune --json --dry-run" -- "$current") ) ;;
|
|
29
29
|
artifact) COMPREPLY=( $(compgen -W "list show delete prune --kind --session --json --dry-run --include-protected" -- "$current") ) ;;
|
|
30
30
|
evidence) COMPREPLY=( $(compgen -W "list show delete prune doctor --status --json --dry-run --repair" -- "$current") ) ;;
|
|
31
|
+
playbook) COMPREPLY=( $(compgen -W "list status init set enable disable reset check record receipts instructions doctor minimal balanced strict must should may off start finish install remove context.recall context.explore work.plan verify.tests review.repository review.security docs.update learning.capture --project --json --enforce --complexity --familiarity --risk --public-change --changed --change-type --completed --skipped --failed --actor --actor-kind --session --policy --decision --event --action --outcome --reason" -- "$current") ) ;;
|
|
31
32
|
capabilities)
|
|
32
33
|
case "${COMP_WORDS[2]:-}" in
|
|
33
34
|
enable|disable)
|
|
@@ -56,7 +57,7 @@ _cairn_complete() {
|
|
|
56
57
|
esac
|
|
57
58
|
;;
|
|
58
59
|
mcp-tools) COMPREPLY=( $(compgen -W "list status set reset full read-only custom --tool --project --json" -- "$current") ) ;;
|
|
59
|
-
pack) COMPREPLY=( $(compgen -W "init lock validate install list show remove enable disable update skills approve-skill revoke-skill --id --version --title --description --license --ref --project --project-id --check --apply --confirm --json" -- "$current") ) ;;
|
|
60
|
+
pack) COMPREPLY=( $(compgen -W "init lock validate install import-okf validate-okf export-okf list show remove enable disable update skills approve-skill revoke-skill --id --version --title --description --license --ref --project --project-id --output --file --note --check --apply --confirm --json" -- "$current") ) ;;
|
|
60
61
|
notes) COMPREPLY=( $(compgen -W "distill search-error promote doctor --project --session --all-projects --para-root --text --component --with --confirm --repair --json" -- "$current") ) ;;
|
|
61
62
|
eval)
|
|
62
63
|
case "${COMP_WORDS[2]:-}" in
|
|
@@ -96,6 +97,7 @@ _cairn() {
|
|
|
96
97
|
'trajectory:inspect and prune local session trajectories'
|
|
97
98
|
'artifact:inspect, delete, and prune local artifacts'
|
|
98
99
|
'evidence:inspect and manage local Git-linked work evidence'
|
|
100
|
+
'playbook:evaluate and record bounded agent workflow policy'
|
|
99
101
|
'capabilities:inspect and manage project capability state'
|
|
100
102
|
'mcp-tools:inspect and restrict MCP tool exposure'
|
|
101
103
|
'pack:manage immutable context packs and skill approvals'
|
|
@@ -132,8 +134,9 @@ _cairn() {
|
|
|
132
134
|
esac
|
|
133
135
|
;;
|
|
134
136
|
evidence) _values 'work evidence command' list show delete prune doctor '--status[filter records]:status:(pending complete)' '--dry-run[report without mutation]' '--repair[remove safe temporary remnants]' '--json[emit JSON]' ;;
|
|
137
|
+
playbook) _values 'playbook command' list status init set enable disable reset check record receipts instructions doctor minimal balanced strict must should may off start finish install remove context.recall context.explore work.plan verify.tests review.repository review.security docs.update learning.capture '--project[project root]:directory:_files -/' '--json[emit JSON]' '--enforce[fail when must evidence is missing]' '--changed[changed project path]:file:_files' '--change-type[change type]:type:(code tests docs config dependencies security)' '--complexity[task complexity]:level:(trivial standard complex)' '--familiarity[context familiarity]:level:(known mixed unfamiliar)' '--risk[task risk]:level:(low normal high security)' '--public-change[public behavior changed]' '--completed[completed action]:action:' '--skipped[skipped action and reason]:evidence:' '--failed[failed action and reason]:evidence:' '--actor[local actor label]:actor:' '--actor-kind[local actor kind]:kind:(user agent service)' '--session[session label]:session:' '--policy[policy digest]:digest:' '--decision[decision digest]:digest:' '--event[lifecycle event]:event:(start check finish)' '--action[canonical action]:action:(context.recall context.explore work.plan verify.tests review.repository review.security docs.update learning.capture)' '--outcome[outcome]:outcome:(completed skipped failed)' '--reason[bounded reason]:reason:' ;;
|
|
135
138
|
mcp-tools) _values 'MCP tool profile command' list status set reset full read-only custom '--tool[allow an exact tool]:tool:' '--project[project root]:directory:_files -/' '--json[emit JSON]' ;;
|
|
136
|
-
pack) _values 'context pack command' init lock validate install list show remove enable disable update skills approve-skill revoke-skill '--ref[pinned Git ref]:ref:' '--project[project root]:directory:_files -/' '--project-id[remote project ID]:project ID:' '--check[inspect update]' '--apply[apply update]' '--confirm[confirm digest]:digest:' '--json[emit JSON]' ;;
|
|
139
|
+
pack) _values 'context pack command' init lock validate install import-okf validate-okf export-okf list show remove enable disable update skills approve-skill revoke-skill '--ref[pinned Git ref]:ref:' '--project[project root]:directory:_files -/' '--project-id[remote project ID]:project ID:' '--output[export output directory]:directory:_files -/' '--file[explicit project Markdown file]:file:_files' '--note[promoted shared note ID]:note ID:' '--check[inspect update or export]' '--apply[apply update or export]' '--confirm[confirm digest]:digest:' '--json[emit JSON]' ;;
|
|
137
140
|
capabilities)
|
|
138
141
|
case $words[3] in
|
|
139
142
|
list|status) _arguments '--json[emit JSON]' ;;
|
|
@@ -182,8 +185,8 @@ EOF
|
|
|
182
185
|
fish)
|
|
183
186
|
sed "s/__CAIRN_HARNESSES__/$harnesses/g" <<'EOF'
|
|
184
187
|
complete -c cairn -f
|
|
185
|
-
for command in bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence capabilities mcp-tools pack notes eval skill graph memory audit-timer uninstall completion version help
|
|
186
|
-
complete -c cairn -n "not __fish_seen_subcommand_from bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence capabilities mcp-tools pack notes eval skill graph memory audit-timer uninstall completion version help" -a $command
|
|
188
|
+
for command in bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence playbook capabilities mcp-tools pack notes eval skill graph memory audit-timer uninstall completion version help
|
|
189
|
+
complete -c cairn -n "not __fish_seen_subcommand_from bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence playbook capabilities mcp-tools pack notes eval skill graph memory audit-timer uninstall completion version help" -a $command
|
|
187
190
|
end
|
|
188
191
|
complete -c cairn -n "__fish_seen_subcommand_from sync" -l apply
|
|
189
192
|
complete -c cairn -n "__fish_seen_subcommand_from sync" -l live-root -r
|
|
@@ -212,6 +215,23 @@ complete -c cairn -n "__fish_seen_subcommand_from evidence; and __fish_seen_subc
|
|
|
212
215
|
complete -c cairn -n "__fish_seen_subcommand_from evidence; and __fish_seen_subcommand_from list show delete prune doctor" -l json
|
|
213
216
|
complete -c cairn -n "__fish_seen_subcommand_from evidence; and __fish_seen_subcommand_from delete prune" -l dry-run
|
|
214
217
|
complete -c cairn -n "__fish_seen_subcommand_from evidence; and __fish_seen_subcommand_from doctor" -l repair
|
|
218
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and not __fish_seen_subcommand_from list status init set enable disable reset check record receipts instructions doctor" -a "list status init set enable disable reset check record receipts instructions doctor"
|
|
219
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook" -l project -r
|
|
220
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook" -l json
|
|
221
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from init set" -a "minimal balanced strict"
|
|
222
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from check" -a "start check finish"
|
|
223
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from check" -l enforce
|
|
224
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from check" -l changed -r
|
|
225
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from enable disable reset" -a "context.recall context.explore work.plan verify.tests review.repository review.security docs.update learning.capture"
|
|
226
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from enable" -a "must should may"
|
|
227
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from instructions" -a "install check remove"
|
|
228
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from check" -l complexity -r -a "trivial standard complex"
|
|
229
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from check" -l familiarity -r -a "known mixed unfamiliar"
|
|
230
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from check" -l risk -r -a "low normal high security"
|
|
231
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from check" -l public-change
|
|
232
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from check" -l completed -r
|
|
233
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from check" -l skipped -r
|
|
234
|
+
complete -c cairn -n "__fish_seen_subcommand_from playbook; and __fish_seen_subcommand_from check" -l failed -r
|
|
215
235
|
complete -c cairn -n "__fish_seen_subcommand_from capabilities; and not __fish_seen_subcommand_from list status enable disable reset logging" -a "list status enable disable reset logging"
|
|
216
236
|
complete -c cairn -n "__fish_seen_subcommand_from capabilities; and __fish_seen_subcommand_from enable disable; and not __fish_seen_subcommand_from memory.write memory.search notes.distill wiki graph security.audit route.check context.explore" -a "memory.write memory.search notes.distill wiki graph security.audit route.check context.explore"
|
|
217
237
|
complete -c cairn -n "__fish_seen_subcommand_from capabilities; and __fish_seen_subcommand_from reset; and not __fish_seen_subcommand_from memory.write memory.search notes.distill wiki graph security.audit route.check context.explore" -a "memory.write memory.search notes.distill wiki graph security.audit route.check context.explore"
|
|
@@ -223,7 +243,7 @@ complete -c cairn -n "__fish_seen_subcommand_from mcp-tools; and __fish_seen_sub
|
|
|
223
243
|
complete -c cairn -n "__fish_seen_subcommand_from mcp-tools" -l project -r
|
|
224
244
|
complete -c cairn -n "__fish_seen_subcommand_from mcp-tools" -l tool -r
|
|
225
245
|
complete -c cairn -n "__fish_seen_subcommand_from mcp-tools" -l json
|
|
226
|
-
complete -c cairn -n "__fish_seen_subcommand_from pack" -a "init lock validate install list show remove enable disable update skills approve-skill revoke-skill"
|
|
246
|
+
complete -c cairn -n "__fish_seen_subcommand_from pack" -a "init lock validate install import-okf validate-okf export-okf list show remove enable disable update skills approve-skill revoke-skill"
|
|
227
247
|
complete -c cairn -n "__fish_seen_subcommand_from pack" -l project -r
|
|
228
248
|
complete -c cairn -n "__fish_seen_subcommand_from pack" -l project-id -r
|
|
229
249
|
complete -c cairn -n "__fish_seen_subcommand_from pack" -l ref -r
|