@cairnkeep/cli 2.15.4 → 2.17.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 +44 -0
- package/CONTRIBUTING.md +12 -0
- package/README.md +85 -590
- package/docs/README.md +87 -0
- package/docs/agents.md +193 -0
- package/docs/context-intelligence.md +143 -0
- package/docs/context-packs.md +33 -0
- package/docs/domain-knowledge.md +29 -0
- package/docs/ecosystem.md +23 -0
- package/docs/learning/CURRICULUM-MAP.md +3 -1
- package/docs/learning/FEATURE-GUIDE.md +23 -4
- package/docs/learning/README.md +4 -0
- package/docs/learning/lessons/L00-why-cairnkeep.md +3 -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 +9 -10
- 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 +1 -1
- package/docs/learning/lessons/L22-native-windows.md +2 -2
- package/docs/learning/lessons/L23-guided-setup.md +18 -5
- package/docs/learning/lessons/L24-okf-exchange.md +1 -1
- package/docs/learning/lessons/L25-playbooks.md +1 -1
- package/docs/learning/lessons/L26-context-intelligence.md +180 -0
- package/docs/learning/tracks/evidence-and-evaluation.md +1 -0
- package/docs/learning/tracks/operator.md +1 -0
- package/docs/learning/tracks/practitioner.md +1 -0
- package/docs/learning/video-scripts/V26-context-intelligence.md +144 -0
- package/docs/native-windows.md +5 -0
- package/docs/operating.md +65 -3
- package/docs/privacy-and-data-flow.md +22 -0
- package/docs/quickstart.md +12 -4
- package/docs/releasing.md +27 -19
- package/docs/storage.md +21 -0
- package/docs/work-evidence.md +14 -0
- package/examples/eval/bundled-fake.json +1 -1
- package/mcp-memory-server/dist/context-pack-cli.js +3 -1
- package/mcp-memory-server/dist/context-pack-retrieval.js +413 -0
- package/mcp-memory-server/dist/context-pack.js +171 -3
- package/mcp-memory-server/dist/domain-retrieval-provider.js +257 -0
- package/mcp-memory-server/dist/index.js +73 -387
- package/mcp-memory-server/dist/mcp-tool-catalog.js +2 -0
- package/mcp-memory-server/dist/memory-extraction.js +87 -0
- package/mcp-memory-server/dist/memory-proposal-cli.js +110 -0
- package/mcp-memory-server/dist/memory-proposal-schema.js +78 -0
- package/mcp-memory-server/dist/memory-proposal-store.js +273 -0
- package/mcp-memory-server/dist/platform-security.js +32 -26
- package/mcp-memory-server/dist/retrieval-benchmark.js +199 -0
- package/mcp-memory-server/dist/reviewed-memory-store.js +281 -0
- package/mcp-memory-server/dist/work-evidence-schema.js +17 -0
- package/mcp-memory-server/dist/work-evidence-store.js +57 -1
- package/mcp-memory-server/package.json +8 -1
- package/package.json +1 -1
- package/schemas/context-usage-receipt.schema.json +41 -0
- package/schemas/memory-proposal.schema.json +65 -0
- package/schemas/retrieval-benchmark.schema.json +64 -0
- package/scripts/cairn-cli.mjs +3 -0
- package/scripts/completion.sh +17 -6
- package/scripts/doctor.sh +4 -2
- package/scripts/setup.mjs +92 -13
- package/scripts/terminal-prompts.mjs +172 -0
- package/scripts/test-completion.sh +8 -1
- package/scripts/test-docs-navigation.sh +63 -0
- package/scripts/test-doctor.sh +21 -0
- package/scripts/test-phase17-artifact-lifecycle.sh +1 -1
- package/scripts/test-release-sbom.sh +44 -16
- package/scripts/test-setup-preflight.mjs +130 -0
- package/scripts/test-windows-native.mjs +30 -0
- package/scripts/verify-docs-parity.sh +28 -28
- package/scripts/windows-platform.mjs +8 -5
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
5
|
+
cd "$ROOT"
|
|
6
|
+
|
|
7
|
+
fail() {
|
|
8
|
+
echo "FATAL: $*" >&2
|
|
9
|
+
exit 1
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
require_term() {
|
|
13
|
+
local file="$1" term="$2"
|
|
14
|
+
grep -qF "$term" "$file" || fail "$file is missing required navigation term: $term"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
check_local_links() {
|
|
18
|
+
local file="$1" base target resolved
|
|
19
|
+
base=$(dirname "$file")
|
|
20
|
+
while IFS= read -r target; do
|
|
21
|
+
case "$target" in
|
|
22
|
+
http://*|https://*|mailto:*|'#'*|'') continue ;;
|
|
23
|
+
esac
|
|
24
|
+
target=${target%%#*}
|
|
25
|
+
resolved="$base/$target"
|
|
26
|
+
[[ -e "$resolved" ]] || fail "$file links to missing local target: $target"
|
|
27
|
+
done < <(grep -oE '\]\([^)]+' "$file" | sed 's/^](//' || true)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
readme_lines=$(wc -l < README.md | tr -d ' ')
|
|
31
|
+
[[ "$readme_lines" -le 140 ]] || fail "README.md must remain a lean landing page (found $readme_lines lines; limit 140)"
|
|
32
|
+
|
|
33
|
+
require_term README.md '## For coding agents'
|
|
34
|
+
require_term README.md 'docs/README.md'
|
|
35
|
+
require_term README.md 'docs/agents.md'
|
|
36
|
+
require_term README.md 'docs/quickstart.md'
|
|
37
|
+
require_term README.md 'docs/learning/README.md'
|
|
38
|
+
|
|
39
|
+
if grep -qE '\b(CAIRN_|MCP_HTTP_)' README.md; then
|
|
40
|
+
fail 'README.md contains detailed environment configuration; keep it in versioned focused docs'
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
for guide in \
|
|
44
|
+
quickstart.md agents.md operating.md harness-compatibility.md \
|
|
45
|
+
mcp-tool-profiles.md context-packs.md storage.md privacy-and-data-flow.md \
|
|
46
|
+
work-evidence.md native-windows.md containers.md ecosystem.md \
|
|
47
|
+
learning/README.md; do
|
|
48
|
+
require_term docs/README.md "$guide"
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
for term in \
|
|
52
|
+
'memory_search' 'locator' 'memory_write' 'cairn playbook check start' \
|
|
53
|
+
'cairn playbook check finish' 'cairn mcp-tools set read-only' \
|
|
54
|
+
'cannot enable a disabled feature' 'not evidence'; do
|
|
55
|
+
require_term docs/agents.md "$term"
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
check_local_links README.md
|
|
59
|
+
check_local_links docs/README.md
|
|
60
|
+
check_local_links docs/agents.md
|
|
61
|
+
check_local_links docs/ecosystem.md
|
|
62
|
+
|
|
63
|
+
echo "PASS: lean README, agent contract, documentation index, and local navigation"
|
package/scripts/test-doctor.sh
CHANGED
|
@@ -30,6 +30,27 @@ grep -q "capability configuration (not present — managed overrides are unused)
|
|
|
30
30
|
grep -q "capability callback namespace (not present — callback logging is unused)" "$tmp/out1" ||
|
|
31
31
|
fail "expected absent capability callback diagnostics"
|
|
32
32
|
|
|
33
|
+
# Top-level repair must reach the context-pack doctor; the pack CLI itself
|
|
34
|
+
# owns the safe derived-cache deletion policy.
|
|
35
|
+
real_node=$(command -v node)
|
|
36
|
+
mkdir -p "$tmp/doctor-node-shim" "$HOME/.cairnkeep/packs"
|
|
37
|
+
cat >"$tmp/doctor-node-shim/node" <<'EOF'
|
|
38
|
+
#!/usr/bin/env bash
|
|
39
|
+
if [[ "${1:-}" == */context-pack-cli.js && "${2:-}" == doctor ]]; then
|
|
40
|
+
printf '%s\n' "$@" >"$CAIRN_TEST_PACK_ARGS"
|
|
41
|
+
printf '%s\n' '{"ok":true,"objects":0,"projects":0,"issues":[],"temporary_remnants":[]}'
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
exec "$CAIRN_TEST_REAL_NODE" "$@"
|
|
45
|
+
EOF
|
|
46
|
+
chmod 755 "$tmp/doctor-node-shim/node"
|
|
47
|
+
( cd "$proj" && PATH="$tmp/doctor-node-shim:$PATH" CAIRN_TEST_REAL_NODE="$real_node" \
|
|
48
|
+
CAIRN_TEST_PACK_ARGS="$tmp/context-pack-doctor.args" "$doctor" --repair ) >"$tmp/out-pack-repair" 2>&1 ||
|
|
49
|
+
fail "doctor --repair failed while forwarding context-pack repair:\n$(cat "$tmp/out-pack-repair")"
|
|
50
|
+
grep -qx -- '--repair' "$tmp/context-pack-doctor.args" ||
|
|
51
|
+
fail "top-level doctor --repair did not reach the context-pack doctor"
|
|
52
|
+
rm -rf "$HOME/.cairnkeep/packs"
|
|
53
|
+
|
|
33
54
|
# 2. An unsupported runtime is diagnosed before the server probe.
|
|
34
55
|
mkdir -p "$tmp/old-node"
|
|
35
56
|
cat > "$tmp/old-node/node" <<'EOF'
|
|
@@ -244,7 +244,7 @@ check_uninstall_bytes() {
|
|
|
244
244
|
|
|
245
245
|
check_docs_contract() {
|
|
246
246
|
local public_doc
|
|
247
|
-
for public_doc in "$ROOT/
|
|
247
|
+
for public_doc in "$ROOT/docs/operating.md"; do
|
|
248
248
|
require_term "$public_doc" 'CAIRN_COMPACTION_CAPTURE' "$(basename "$public_doc") compaction flag"
|
|
249
249
|
require_term "$public_doc" 'CAIRN_ARTIFACT_STORE' "$(basename "$public_doc") artifact-store flag"
|
|
250
250
|
require_term "$public_doc" 'CAIRN_ARTIFACT_HTTP' "$(basename "$public_doc") artifact HTTP flag"
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
5
|
-
|
|
5
|
+
CANDIDATE_WORKFLOW="$ROOT/.github/workflows/ci.yml"
|
|
6
|
+
PUBLISH_WORKFLOW="$ROOT/.github/workflows/publish.yml"
|
|
6
7
|
TEMP_DIR=$(mktemp -d)
|
|
7
8
|
trap 'rm -f "$TEMP_DIR/cairnkeep.cdx.json"; rmdir "$TEMP_DIR"' EXIT
|
|
8
9
|
|
|
@@ -11,12 +12,13 @@ fail() {
|
|
|
11
12
|
exit 1
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
node - "$ROOT/package.json" "$ROOT/package-lock.json" "$
|
|
15
|
+
node - "$ROOT/package.json" "$ROOT/package-lock.json" "$CANDIDATE_WORKFLOW" "$PUBLISH_WORKFLOW" <<'NODE'
|
|
15
16
|
const fs = require("node:fs");
|
|
16
17
|
|
|
17
18
|
const manifest = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
18
19
|
const lock = JSON.parse(fs.readFileSync(process.argv[3], "utf8"));
|
|
19
|
-
const
|
|
20
|
+
const candidateWorkflow = fs.readFileSync(process.argv[4], "utf8");
|
|
21
|
+
const publishWorkflow = fs.readFileSync(process.argv[5], "utf8");
|
|
20
22
|
const expected = "6.0.0";
|
|
21
23
|
|
|
22
24
|
if (manifest.devDependencies?.["@cyclonedx/cyclonedx-npm"] !== expected) {
|
|
@@ -29,24 +31,50 @@ if (lock.packages?.["node_modules/@cyclonedx/cyclonedx-npm"]?.version !== expect
|
|
|
29
31
|
throw new Error(`package-lock.json must resolve @cyclonedx/cyclonedx-npm ${expected}`);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
if (!candidateWorkflow.includes("release-candidate:")
|
|
35
|
+
|| !candidateWorkflow.includes("- name: Build release candidate")
|
|
36
|
+
|| !candidateWorkflow.includes("name: release-candidate-${{ steps.candidate.outputs.tree }}")) {
|
|
37
|
+
throw new Error("CI must build and upload a tree-addressed release candidate after its required jobs");
|
|
38
|
+
}
|
|
39
|
+
if (!publishWorkflow.includes("- name: Download the CI-verified release candidate")
|
|
40
|
+
|| !publishWorkflow.includes("jq -r '.conclusion'")
|
|
41
|
+
|| !publishWorkflow.includes("== success")
|
|
42
|
+
|| !publishWorkflow.includes("sha256sum --check SHA256SUMS")
|
|
43
|
+
|| !publishWorkflow.includes("candidate.tree !== tree")) {
|
|
44
|
+
throw new Error("release publication must verify the successful CI run, checksums, version, and exact tree");
|
|
45
|
+
}
|
|
46
|
+
const publishStep = publishWorkflow.indexOf("- name: Publish to npm with provenance");
|
|
47
|
+
const attachStep = publishWorkflow.indexOf("- name: Attach package and SBOM to GitHub release");
|
|
48
|
+
if (publishStep < 0 || attachStep < 0 || publishStep >= attachStep) {
|
|
49
|
+
throw new Error("verified artifacts must publish to npm before release attachment");
|
|
50
|
+
}
|
|
51
|
+
if (!publishWorkflow.includes('npm publish "./artifacts/cairnkeep-cli-$VERSION.tgz"')) {
|
|
52
|
+
throw new Error("npm publication must use an explicit relative tarball path");
|
|
53
|
+
}
|
|
54
|
+
if (!publishWorkflow.includes('gh release upload "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY"')) {
|
|
55
|
+
throw new Error("release attachment must name the repository outside a checkout");
|
|
56
|
+
}
|
|
57
|
+
if (publishWorkflow.includes("check:public")) {
|
|
58
|
+
throw new Error("release workflow must promote the verified candidate without repeating the public suite");
|
|
59
|
+
}
|
|
60
|
+
if (!publishWorkflow.includes("name: containers (${{ matrix.target }})")
|
|
61
|
+
|| !publishWorkflow.includes("- target: server")
|
|
62
|
+
|| !publishWorkflow.includes("- target: workspace")
|
|
63
|
+
|| !publishWorkflow.includes("needs: prepare")) {
|
|
64
|
+
throw new Error("npm and both container targets must fan out after candidate preparation");
|
|
37
65
|
}
|
|
38
66
|
NODE
|
|
39
67
|
|
|
40
|
-
grep -Fq './node_modules/.bin/cyclonedx-npm' "$
|
|
41
|
-
grep -Fq -- '--package-lock-only' "$
|
|
42
|
-
grep -Fq -- '--omit dev' "$
|
|
43
|
-
grep -Fq -- '--output-reproducible' "$
|
|
44
|
-
grep -Fq -- '--spec-version 1.6' "$
|
|
45
|
-
grep -Fq -- '--validate' "$
|
|
46
|
-
if grep -Fq 'npm sbom' "$
|
|
68
|
+
grep -Fq './node_modules/.bin/cyclonedx-npm' "$CANDIDATE_WORKFLOW" || fail "candidate workflow does not use the locked CycloneDX generator"
|
|
69
|
+
grep -Fq -- '--package-lock-only' "$CANDIDATE_WORKFLOW" || fail "candidate workflow does not use the package lock"
|
|
70
|
+
grep -Fq -- '--omit dev' "$CANDIDATE_WORKFLOW" || fail "candidate workflow does not omit development dependencies"
|
|
71
|
+
grep -Fq -- '--output-reproducible' "$CANDIDATE_WORKFLOW" || fail "candidate workflow does not request reproducible output"
|
|
72
|
+
grep -Fq -- '--spec-version 1.6' "$CANDIDATE_WORKFLOW" || fail "candidate workflow does not emit CycloneDX 1.6"
|
|
73
|
+
grep -Fq -- '--validate' "$CANDIDATE_WORKFLOW" || fail "candidate workflow does not validate the generated SBOM"
|
|
74
|
+
if grep -Fq 'npm sbom' "$CANDIDATE_WORKFLOW" "$PUBLISH_WORKFLOW"; then
|
|
47
75
|
fail "release workflow still uses npm sbom"
|
|
48
76
|
fi
|
|
49
|
-
if grep -Fqi 'sbomsmith' "$
|
|
77
|
+
if grep -Fqi 'sbomsmith' "$CANDIDATE_WORKFLOW" "$PUBLISH_WORKFLOW"; then
|
|
50
78
|
fail "release workflow must not depend on an external converter"
|
|
51
79
|
fi
|
|
52
80
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import assert from "node:assert/strict";
|
|
4
|
+
import { EventEmitter } from "node:events";
|
|
4
5
|
import {
|
|
5
6
|
existsSync,
|
|
6
7
|
lstatSync,
|
|
@@ -20,6 +21,7 @@ const RED_MARKER = "PHASE26_RED:SETUP_CORE_MISSING";
|
|
|
20
21
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
21
22
|
const setupCorePath = join(here, "setup-core.mjs");
|
|
22
23
|
const setupPath = join(here, "setup.mjs");
|
|
24
|
+
const terminalPromptsPath = join(here, "terminal-prompts.mjs");
|
|
23
25
|
const MANAGED_PATHS = [".ai", ".planning", ".agentfs"];
|
|
24
26
|
|
|
25
27
|
function assertNoManagedPaths(target, label) {
|
|
@@ -81,6 +83,10 @@ async function loadSetup() {
|
|
|
81
83
|
return import(pathToFileURL(setupPath).href);
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
async function loadTerminalPrompts() {
|
|
87
|
+
return import(pathToFileURL(terminalPromptsPath).href);
|
|
88
|
+
}
|
|
89
|
+
|
|
84
90
|
function assertExports(core) {
|
|
85
91
|
for (const name of [
|
|
86
92
|
"parseSetupArgs",
|
|
@@ -225,6 +231,126 @@ async function runInteractive(setup, args, responses) {
|
|
|
225
231
|
return { status, prompts, output: output.read(), error: error.read() };
|
|
226
232
|
}
|
|
227
233
|
|
|
234
|
+
class FakeTTY extends EventEmitter {
|
|
235
|
+
constructor() {
|
|
236
|
+
super();
|
|
237
|
+
this.isTTY = true;
|
|
238
|
+
this.isRaw = false;
|
|
239
|
+
this.paused = true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
setRawMode(value) { this.isRaw = value; }
|
|
243
|
+
isPaused() { return this.paused; }
|
|
244
|
+
resume() { this.paused = false; }
|
|
245
|
+
pause() { this.paused = true; }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function testTerminalSelectors(prompts) {
|
|
249
|
+
const input = new FakeTTY();
|
|
250
|
+
const output = captureStream();
|
|
251
|
+
output.stream.isTTY = true;
|
|
252
|
+
assert.equal(prompts.supportsTerminalPrompts(input, output.stream, { TERM: "xterm-256color" }), true);
|
|
253
|
+
assert.equal(prompts.supportsTerminalPrompts(input, output.stream, { TERM: "dumb" }), false);
|
|
254
|
+
|
|
255
|
+
const multi = prompts.selectManyPrompt({
|
|
256
|
+
message: "Coding harnesses",
|
|
257
|
+
choices: [
|
|
258
|
+
{ value: "claude", label: "Claude Code" },
|
|
259
|
+
{ value: "codex", label: "Codex CLI" },
|
|
260
|
+
],
|
|
261
|
+
input,
|
|
262
|
+
output: output.stream,
|
|
263
|
+
});
|
|
264
|
+
setImmediate(() => {
|
|
265
|
+
input.emit("keypress", " ", { name: "space" });
|
|
266
|
+
input.emit("keypress", "", { name: "down" });
|
|
267
|
+
input.emit("keypress", " ", { name: "space" });
|
|
268
|
+
input.emit("keypress", "", { name: "return" });
|
|
269
|
+
});
|
|
270
|
+
assert.deepEqual(await multi, ["claude", "codex"]);
|
|
271
|
+
assert.equal(input.isRaw, false);
|
|
272
|
+
assert.equal(input.isPaused(), true);
|
|
273
|
+
assert.match(output.read(), /\[x\] Claude Code/);
|
|
274
|
+
assert.match(output.read(), /Coding harnesses: Claude Code, Codex CLI/);
|
|
275
|
+
|
|
276
|
+
const single = prompts.selectOnePrompt({
|
|
277
|
+
message: "Memory mode",
|
|
278
|
+
choices: [
|
|
279
|
+
{ value: "local", label: "Local" },
|
|
280
|
+
{ value: "none", label: "None" },
|
|
281
|
+
],
|
|
282
|
+
initialValue: "local",
|
|
283
|
+
input,
|
|
284
|
+
output: output.stream,
|
|
285
|
+
});
|
|
286
|
+
setImmediate(() => {
|
|
287
|
+
input.emit("keypress", "", { name: "down" });
|
|
288
|
+
input.emit("keypress", "", { name: "return" });
|
|
289
|
+
});
|
|
290
|
+
assert.equal(await single, "none");
|
|
291
|
+
assert.match(output.read(), /Memory mode: None/);
|
|
292
|
+
|
|
293
|
+
const cancelled = prompts.selectOnePrompt({
|
|
294
|
+
message: "Cancel me",
|
|
295
|
+
choices: [{ value: "only", label: "Only choice" }],
|
|
296
|
+
input,
|
|
297
|
+
output: output.stream,
|
|
298
|
+
});
|
|
299
|
+
setImmediate(() => input.emit("keypress", "", { name: "escape" }));
|
|
300
|
+
await assert.rejects(cancelled, (error) => error?.status === 130 && /cancelled/i.test(error.message));
|
|
301
|
+
assert.equal(input.isRaw, false);
|
|
302
|
+
assert.equal(input.isPaused(), true);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function testStructuredSetupSelectors(setup, fixture) {
|
|
306
|
+
const selections = [];
|
|
307
|
+
const output = captureStream();
|
|
308
|
+
const error = captureStream();
|
|
309
|
+
const status = await setup.runSetup([fixture.empty], {
|
|
310
|
+
isTTY: true,
|
|
311
|
+
input: { isTTY: true },
|
|
312
|
+
output: output.stream,
|
|
313
|
+
error: error.stream,
|
|
314
|
+
selectOne: async (options) => {
|
|
315
|
+
selections.push(options);
|
|
316
|
+
if (options.message === "Git integration") return "init";
|
|
317
|
+
if (options.message === "Memory mode") return "local";
|
|
318
|
+
return "no";
|
|
319
|
+
},
|
|
320
|
+
selectMany: async (options) => {
|
|
321
|
+
selections.push(options);
|
|
322
|
+
return ["codex", "opencode"];
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
assert.equal(status, 0);
|
|
326
|
+
assert.equal(error.read(), "");
|
|
327
|
+
assert.match(output.read(), /Setup cancelled; no files were changed/);
|
|
328
|
+
assert.deepEqual(selections.map(({ message }) => message), [
|
|
329
|
+
"Git integration",
|
|
330
|
+
"Coding harnesses",
|
|
331
|
+
"Memory mode",
|
|
332
|
+
"Apply this setup plan?",
|
|
333
|
+
]);
|
|
334
|
+
const harnessPrompt = selections.find(({ message }) => message === "Coding harnesses");
|
|
335
|
+
assert.deepEqual(harnessPrompt.choices.map(({ value }) => value), ["claude", "opencode", "pi", "kimi", "qwen", "codex"]);
|
|
336
|
+
assert.match(harnessPrompt.choices.find(({ value }) => value === "codex").label, /Codex CLI/);
|
|
337
|
+
assertNoManagedPaths(fixture.empty, "cancelled structured setup");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function testRedirectedOutputIsNoninteractive(setup, fixture) {
|
|
341
|
+
const output = captureStream();
|
|
342
|
+
const error = captureStream();
|
|
343
|
+
const status = await setup.runSetup([fixture.empty, "--yes"], {
|
|
344
|
+
input: { isTTY: true },
|
|
345
|
+
output: output.stream,
|
|
346
|
+
error: error.stream,
|
|
347
|
+
});
|
|
348
|
+
assert.equal(status, 2);
|
|
349
|
+
assert.equal(output.read(), "");
|
|
350
|
+
assert.match(error.read(), /git|harness|memory|usage/i);
|
|
351
|
+
assertNoManagedPaths(fixture.empty, "redirected setup output");
|
|
352
|
+
}
|
|
353
|
+
|
|
228
354
|
async function testInteractiveGitRecommendation(setup, fixture) {
|
|
229
355
|
const accepted = await runInteractive(setup, [], [fixture.missing, "init", "pi", "local", "yes"]);
|
|
230
356
|
assert.equal(accepted.status, 0);
|
|
@@ -271,6 +397,10 @@ async function main() {
|
|
|
271
397
|
testChoiceContract(core, fixture);
|
|
272
398
|
testPreflightContract(core, fixture);
|
|
273
399
|
const setup = await loadSetup();
|
|
400
|
+
const terminalPrompts = await loadTerminalPrompts();
|
|
401
|
+
await testTerminalSelectors(terminalPrompts);
|
|
402
|
+
await testStructuredSetupSelectors(setup, fixture);
|
|
403
|
+
await testRedirectedOutputIsNoninteractive(setup, fixture);
|
|
274
404
|
await testInteractiveGitRecommendation(setup, fixture);
|
|
275
405
|
console.log("PASS: setup target, Git, syntax, choice, and no-write preflight contract");
|
|
276
406
|
} finally {
|
|
@@ -21,6 +21,7 @@ import { HARNESS_IDS } from "./harness-registry.mjs";
|
|
|
21
21
|
const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
22
22
|
const sandbox = mkdtempSync(join(tmpdir(), "cairn-windows-native-"));
|
|
23
23
|
const originalBase = process.env.CAIRN_AGENTFS_BASE_DIR;
|
|
24
|
+
const originalPackBase = process.env.CAIRN_PACK_BASE_DIR;
|
|
24
25
|
const originalHome = process.env.HOME;
|
|
25
26
|
const originalProfile = process.env.USERPROFILE;
|
|
26
27
|
try {
|
|
@@ -69,6 +70,33 @@ try {
|
|
|
69
70
|
assert.match(powershellCompletion(), /playbook/);
|
|
70
71
|
assert.match(powershellCompletion(), /review\.security/);
|
|
71
72
|
assert.match(powershellCompletion(), /--enforce/);
|
|
73
|
+
assert.match(powershellCompletion(), /doctor/);
|
|
74
|
+
assert.match(powershellCompletion(), /--repair/);
|
|
75
|
+
assert.match(powershellCompletion(), /proposals/);
|
|
76
|
+
assert.match(powershellCompletion(), /'create','list','show','apply','doctor'/);
|
|
77
|
+
assert.match(powershellCompletion(), /--category/);
|
|
78
|
+
|
|
79
|
+
const packBase = join(sandbox, "Context Packs");
|
|
80
|
+
const invalidDerivedPack = join(packBase, "cache", "context", "f".repeat(64));
|
|
81
|
+
mkdirSync(invalidDerivedPack, { recursive: true });
|
|
82
|
+
writeFileSync(join(invalidDerivedPack, `${"e".repeat(64)}.json`), "{\"corrupt\":true}\n");
|
|
83
|
+
process.env.CAIRN_PACK_BASE_DIR = packBase;
|
|
84
|
+
const inheritedLlmUrl = process.env.CAIRN_LLM_API_URL;
|
|
85
|
+
const exitCodeBeforeDoctor = process.exitCode;
|
|
86
|
+
const doctorWrite = process.stdout.write.bind(process.stdout);
|
|
87
|
+
let doctorOutput = "";
|
|
88
|
+
delete process.env.CAIRN_LLM_API_URL;
|
|
89
|
+
process.stdout.write = ((chunk) => { doctorOutput += String(chunk); return true; });
|
|
90
|
+
try {
|
|
91
|
+
await runWindowsCommand({ command: "doctor", args: ["--repair"], root });
|
|
92
|
+
} finally {
|
|
93
|
+
process.stdout.write = doctorWrite;
|
|
94
|
+
process.exitCode = exitCodeBeforeDoctor;
|
|
95
|
+
if (inheritedLlmUrl === undefined) delete process.env.CAIRN_LLM_API_URL;
|
|
96
|
+
else process.env.CAIRN_LLM_API_URL = inheritedLlmUrl;
|
|
97
|
+
}
|
|
98
|
+
assert.equal(existsSync(invalidDerivedPack), false, "native Windows doctor forwards safe context-pack cache repair");
|
|
99
|
+
assert.match(doctorOutput, /\[PASS\] context pack diagnostics passed/);
|
|
72
100
|
|
|
73
101
|
let containerArgs = [];
|
|
74
102
|
runNativeContainer(["stdio", "--image", "example/windows:1", "--volume", "windows-data"], root, (_engine, args) => { containerArgs = args; });
|
|
@@ -167,6 +195,8 @@ try {
|
|
|
167
195
|
} finally {
|
|
168
196
|
if (originalBase === undefined) delete process.env.CAIRN_AGENTFS_BASE_DIR;
|
|
169
197
|
else process.env.CAIRN_AGENTFS_BASE_DIR = originalBase;
|
|
198
|
+
if (originalPackBase === undefined) delete process.env.CAIRN_PACK_BASE_DIR;
|
|
199
|
+
else process.env.CAIRN_PACK_BASE_DIR = originalPackBase;
|
|
170
200
|
if (originalHome === undefined) delete process.env.HOME; else process.env.HOME = originalHome;
|
|
171
201
|
if (originalProfile === undefined) delete process.env.USERPROFILE; else process.env.USERPROFILE = originalProfile;
|
|
172
202
|
rmSync(sandbox, { recursive: true, force: true });
|
|
@@ -9,8 +9,8 @@ set -euo pipefail
|
|
|
9
9
|
#
|
|
10
10
|
# 1. Env-key check: every CAIRN_[A-Z_]+/MCP_HTTP_[A-Z_]+ token read in
|
|
11
11
|
# mcp-memory-server/src/*.ts (the runtime server config surface --
|
|
12
|
-
# NOT scripts/ or bin/) must be named somewhere in
|
|
13
|
-
#
|
|
12
|
+
# NOT scripts/ or bin/) must be named somewhere in the versioned
|
|
13
|
+
# top-level docs. `comm -23` on two sorted lists -- code-keys-not-in-
|
|
14
14
|
# docs -- not a symmetric diff.
|
|
15
15
|
# 2. Command check: every claude/commands/*.md basename must appear in
|
|
16
16
|
# docs/operating.md.
|
|
@@ -27,8 +27,8 @@ Usage: verify-docs-parity.sh
|
|
|
27
27
|
verify-docs-parity.sh -h|--help
|
|
28
28
|
|
|
29
29
|
Checks that every CAIRN_*/MCP_HTTP_* env key read by the cairn-memory MCP
|
|
30
|
-
server (mcp-memory-server/src/*.ts) is named in docs
|
|
31
|
-
|
|
30
|
+
server (mcp-memory-server/src/*.ts) is named in the versioned docs, and that
|
|
31
|
+
every claude/commands/*.md command is named in
|
|
32
32
|
docs/operating.md. One-directional: doc-only names are not a failure.
|
|
33
33
|
Prints every missing key/command by name, then exits non-zero on any
|
|
34
34
|
drift, or 0 if the docs are fully in sync with the shipped code.
|
|
@@ -264,7 +264,7 @@ check_env_keys() {
|
|
|
264
264
|
local code_keys doc_keys missing
|
|
265
265
|
|
|
266
266
|
code_keys=$(grep -ohE "\\b${ENV_KEY_PATTERN}\\b" mcp-memory-server/src/*.ts | grep -v '^CAIRN_TEST_' | sort -u)
|
|
267
|
-
doc_keys=$(grep -ohE "\\b${ENV_KEY_PATTERN}\\b" docs
|
|
267
|
+
doc_keys=$(grep -ohE "\\b${ENV_KEY_PATTERN}\\b" docs/*.md | sort -u)
|
|
268
268
|
|
|
269
269
|
missing=$(comm -23 <(printf '%s\n' "$code_keys") <(printf '%s\n' "$doc_keys"))
|
|
270
270
|
|
|
@@ -276,7 +276,7 @@ check_env_keys() {
|
|
|
276
276
|
return 1
|
|
277
277
|
fi
|
|
278
278
|
|
|
279
|
-
echo "[env-keys] OK: every mcp-memory-server/src env key is named in
|
|
279
|
+
echo "[env-keys] OK: every mcp-memory-server/src env key is named in the versioned docs"
|
|
280
280
|
return 0
|
|
281
281
|
}
|
|
282
282
|
|
|
@@ -316,12 +316,12 @@ check_typed_contract() {
|
|
|
316
316
|
failed=1
|
|
317
317
|
fi
|
|
318
318
|
done <<'EOF'
|
|
319
|
-
CAIRN_TYPED_MEMORY_NODES|
|
|
320
|
-
memory_import|
|
|
321
|
-
address_space|
|
|
322
|
-
node_types|
|
|
323
|
-
tags_all|
|
|
324
|
-
tags_any|
|
|
319
|
+
CAIRN_TYPED_MEMORY_NODES|docs/operating.md
|
|
320
|
+
memory_import|docs/operating.md docs/privacy-and-data-flow.md
|
|
321
|
+
address_space|docs/operating.md docs/privacy-and-data-flow.md
|
|
322
|
+
node_types|docs/operating.md
|
|
323
|
+
tags_all|docs/operating.md
|
|
324
|
+
tags_any|docs/operating.md
|
|
325
325
|
schemas/memory-node.schema.json|docs/storage.md
|
|
326
326
|
cairn_node_metadata_v1|docs/storage.md
|
|
327
327
|
cairn_node_import_replays_v1|docs/storage.md
|
|
@@ -350,11 +350,11 @@ check_artifact_contract() {
|
|
|
350
350
|
echo "FATAL: artifact default '$key=$value' is missing from env.example" >&2
|
|
351
351
|
failed=1
|
|
352
352
|
}
|
|
353
|
-
grep -qF "$key"
|
|
353
|
+
grep -qF "$key" docs/operating.md || {
|
|
354
354
|
echo "FATAL: artifact env key '$key' is missing from public configuration docs" >&2
|
|
355
355
|
failed=1
|
|
356
356
|
}
|
|
357
|
-
grep -qF "$value"
|
|
357
|
+
grep -qF "$value" docs/operating.md || {
|
|
358
358
|
echo "FATAL: artifact default '$key=$value' is missing from public configuration docs" >&2
|
|
359
359
|
failed=1
|
|
360
360
|
}
|
|
@@ -368,7 +368,7 @@ CAIRN_ARTIFACT_GENERATED_FILE_SNAPSHOT_MAX_BYTES|262144|GENERATED_FILE_MAX_SNAPS
|
|
|
368
368
|
EOF
|
|
369
369
|
|
|
370
370
|
for key in CAIRN_COMPACTION_CAPTURE CAIRN_ARTIFACT_STORE CAIRN_ARTIFACT_HTTP; do
|
|
371
|
-
grep -qF "$key" mcp-memory-server/src/artifact-schema.ts
|
|
371
|
+
grep -qF "$key" mcp-memory-server/src/artifact-schema.ts docs/operating.md docs/privacy-and-data-flow.md || {
|
|
372
372
|
echo "FATAL: artifact feature flag '$key' is not source/docs complete" >&2
|
|
373
373
|
failed=1
|
|
374
374
|
}
|
|
@@ -376,13 +376,13 @@ EOF
|
|
|
376
376
|
|
|
377
377
|
for source_term in artifact_write artifact_read artifact_list artifact_delete; do
|
|
378
378
|
grep -qF "\"$source_term\"" mcp-memory-server/src/index.ts || failed=1
|
|
379
|
-
grep -qF "$source_term"
|
|
379
|
+
grep -qF "$source_term" docs/operating.md docs/privacy-and-data-flow.md || failed=1
|
|
380
380
|
done
|
|
381
381
|
for source_term in 'list [--kind K] [--session REF] [--json]' 'show <artifact-id-or-prefix> [--json]' 'delete <artifact-id-or-prefix> [--dry-run] [--json]' 'prune [--dry-run] [--include-protected] [--json]'; do
|
|
382
382
|
grep -qF "$source_term" mcp-memory-server/src/artifact-cli.ts || failed=1
|
|
383
383
|
done
|
|
384
384
|
grep -qF '.agentfs/artifacts.db' docs/storage.md docs/privacy-and-data-flow.md || failed=1
|
|
385
|
-
grep -qF 'CAIRN_ARTIFACT_HTTP' mcp-memory-server/src/index.ts
|
|
385
|
+
grep -qF 'CAIRN_ARTIFACT_HTTP' mcp-memory-server/src/index.ts docs/operating.md docs/privacy-and-data-flow.md || failed=1
|
|
386
386
|
grep -qE 'redact[^.]{0,180}(before|then)[^.]{0,180}(digest|index|write)' docs/privacy-and-data-flow.md || failed=1
|
|
387
387
|
grep -qE 'default uninstall retains|Default uninstall retains' docs/storage.md docs/privacy-and-data-flow.md || failed=1
|
|
388
388
|
grep -qF -- '--purge-memory PROJECT' docs/storage.md || failed=1
|
|
@@ -390,10 +390,10 @@ EOF
|
|
|
390
390
|
|
|
391
391
|
for version in 2.1.219 2.1.220; do
|
|
392
392
|
grep -qF "version: \"$version\"" mcp-memory-server/src/compaction-normalize.ts || failed=1
|
|
393
|
-
grep -qF "$version"
|
|
393
|
+
grep -qF "$version" docs/operating.md docs/privacy-and-data-flow.md || failed=1
|
|
394
394
|
done
|
|
395
395
|
grep -qF 'version: "1.17.20"' mcp-memory-server/src/compaction-normalize.ts || failed=1
|
|
396
|
-
grep -qF '1.17.20'
|
|
396
|
+
grep -qF '1.17.20' docs/operating.md docs/privacy-and-data-flow.md || failed=1
|
|
397
397
|
|
|
398
398
|
[[ "$failed" -eq 0 ]] && echo "[artifact-contract] OK: artifact flags/defaults/tools/paths/privacy/uninstall/version pins match source"
|
|
399
399
|
return "$failed"
|
|
@@ -440,8 +440,8 @@ check_work_evidence_contract() {
|
|
|
440
440
|
grep -qF "$key" mcp-memory-server/src/work-evidence-schema.ts || failed=1
|
|
441
441
|
grep -qF "$source_term" mcp-memory-server/src/work-evidence-schema.ts || failed=1
|
|
442
442
|
grep -qxF "# $key=$value" templates/env.example.template || failed=1
|
|
443
|
-
grep -qF "$key"
|
|
444
|
-
grep -qF "$value"
|
|
443
|
+
grep -qF "$key" docs/operating.md docs/work-evidence.md || failed=1
|
|
444
|
+
grep -qF "$value" docs/operating.md docs/work-evidence.md || failed=1
|
|
445
445
|
done <<'EOF'
|
|
446
446
|
CAIRN_WORK_EVIDENCE_RETENTION_DAYS|30|WORK_EVIDENCE_DEFAULT_RETENTION_DAYS = 30
|
|
447
447
|
CAIRN_WORK_EVIDENCE_STORE_MAX_BYTES|67108864|WORK_EVIDENCE_DEFAULT_STORE_MAX_BYTES = 64 * 1024 * 1024
|
|
@@ -450,11 +450,11 @@ CAIRN_WORK_EVIDENCE_PATCH_MAX_BYTES|1048576|WORK_EVIDENCE_DEFAULT_PATCH_MAX_BYTE
|
|
|
450
450
|
EOF
|
|
451
451
|
|
|
452
452
|
for key in CAIRN_WORK_EVIDENCE CAIRN_WORK_EVIDENCE_PATCH; do
|
|
453
|
-
grep -qF "$key" mcp-memory-server/src/work-evidence-schema.ts templates/env.example.template
|
|
453
|
+
grep -qF "$key" mcp-memory-server/src/work-evidence-schema.ts templates/env.example.template docs/operating.md docs/privacy-and-data-flow.md || failed=1
|
|
454
454
|
done
|
|
455
455
|
for source_term in work_evidence_list work_evidence_read; do
|
|
456
456
|
grep -qF "\"$source_term\"" mcp-memory-server/src/index.ts || failed=1
|
|
457
|
-
grep -qF "$source_term"
|
|
457
|
+
grep -qF "$source_term" docs/operating.md docs/privacy-and-data-flow.md docs/work-evidence.md || failed=1
|
|
458
458
|
done
|
|
459
459
|
for source_term in 'list [--status pending|complete] [--json]' 'show <evidence-id-or-prefix> [--json]' 'delete <evidence-id-or-prefix> [--dry-run] [--json]' 'prune [--dry-run] [--json]' 'doctor [--repair] [--json]'; do
|
|
460
460
|
grep -qF "$source_term" mcp-memory-server/src/work-evidence-cli.ts || failed=1
|
|
@@ -478,7 +478,7 @@ check_capability_contract() {
|
|
|
478
478
|
mcp-memory-server/src/capability-store.ts \
|
|
479
479
|
mcp-memory-server/src/capability-adapter.ts \
|
|
480
480
|
schemas/capability-callback.schema.json \
|
|
481
|
-
docs/operating.md docs/storage.md docs/privacy-and-data-flow.md
|
|
481
|
+
docs/operating.md docs/storage.md docs/privacy-and-data-flow.md; do
|
|
482
482
|
if [[ ! -f "$file" ]]; then
|
|
483
483
|
echo "FATAL: capability parity input is missing: $file" >&2
|
|
484
484
|
failed=1
|
|
@@ -491,11 +491,11 @@ check_capability_contract() {
|
|
|
491
491
|
echo "FATAL: canonical capability ID is missing from source: $id" >&2
|
|
492
492
|
failed=1
|
|
493
493
|
}
|
|
494
|
-
grep -qF "$id"
|
|
494
|
+
grep -qF "$id" docs/operating.md || {
|
|
495
495
|
echo "FATAL: canonical capability ID is undocumented: $id" >&2
|
|
496
496
|
failed=1
|
|
497
497
|
}
|
|
498
|
-
grep -qF "$env"
|
|
498
|
+
grep -qF "$env" docs/operating.md templates/env.example.template || {
|
|
499
499
|
echo "FATAL: capability environment key is not source/docs complete: $env" >&2
|
|
500
500
|
failed=1
|
|
501
501
|
}
|
|
@@ -511,7 +511,7 @@ context.explore|CAIRN_CAPABILITY_CONTEXT_EXPLORE
|
|
|
511
511
|
EOF
|
|
512
512
|
|
|
513
513
|
for term in CAIRN_CAPABILITY_CONTRACT CAIRN_CAPABILITY_LOGGING; do
|
|
514
|
-
grep -qF "$term" mcp-memory-server/src/capability-config.ts
|
|
514
|
+
grep -qF "$term" mcp-memory-server/src/capability-config.ts docs/operating.md templates/env.example.template || {
|
|
515
515
|
echo "FATAL: capability master/logging flag is not source/docs complete: $term" >&2
|
|
516
516
|
failed=1
|
|
517
517
|
}
|
|
@@ -520,7 +520,7 @@ EOF
|
|
|
520
520
|
while IFS='|' read -r id tool; do
|
|
521
521
|
grep -qF "\"$id\"" mcp-memory-server/src/index.ts || failed=1
|
|
522
522
|
grep -qF "\"$tool\"" mcp-memory-server/src/index.ts || failed=1
|
|
523
|
-
grep -qF "$tool"
|
|
523
|
+
grep -qF "$tool" docs/operating.md || {
|
|
524
524
|
echo "FATAL: omitted MCP tool is undocumented: $tool" >&2
|
|
525
525
|
failed=1
|
|
526
526
|
}
|
|
@@ -452,8 +452,9 @@ async function doctorWindows(root, args) {
|
|
|
452
452
|
accessSync(store, fsConstants.W_OK);
|
|
453
453
|
line("PASS", `memory store is writable: ${store}`);
|
|
454
454
|
} catch { line("FAIL", `memory store is not writable: ${store}`); }
|
|
455
|
-
|
|
456
|
-
|
|
455
|
+
let sqliteStatus = null;
|
|
456
|
+
try { sqliteStatus = run("where.exe", ["sqlite3.exe"]).status; } catch {}
|
|
457
|
+
line(sqliteStatus === 0 ? "PASS" : "WARN", sqliteStatus === 0
|
|
457
458
|
? "sqlite3 is available for WAL-safe memory export"
|
|
458
459
|
: "sqlite3 is not installed; runtime works, but memory export is unavailable");
|
|
459
460
|
for (const [label, file, doctorArgs] of [
|
|
@@ -462,7 +463,7 @@ async function doctorWindows(root, args) {
|
|
|
462
463
|
["work evidence", "work-evidence-cli.js", ["doctor", "--json", ...(repair ? ["--repair"] : [])]],
|
|
463
464
|
["typed memory", "node-cli.js", ["doctor", "--project-root", process.cwd(), ...(repair ? ["--repair"] : [])]],
|
|
464
465
|
["capability", "capability-cli.js", ["doctor", "--json"]],
|
|
465
|
-
["context pack", "context-pack-cli.js", ["doctor", "--json"]],
|
|
466
|
+
["context pack", "context-pack-cli.js", ["doctor", "--json", ...(repair ? ["--repair"] : [])]],
|
|
466
467
|
["playbook", "playbook-cli.js", ["doctor", "--json"]],
|
|
467
468
|
]) {
|
|
468
469
|
const entry = join(root, "mcp-memory-server", "dist", file);
|
|
@@ -728,10 +729,12 @@ export function powershellCompletion() {
|
|
|
728
729
|
const harnesses = HARNESS_IDS.map((id) => `'${id}'`).join(",");
|
|
729
730
|
return `Register-ArgumentCompleter -Native -CommandName cairn -ScriptBlock {
|
|
730
731
|
param($wordToComplete, $commandAst, $cursorPosition)
|
|
731
|
-
$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'
|
|
732
|
+
$commands = 'bootstrap','setup','memory-server','sync','sync-pi','sync-kimi','doctor','trajectory','artifact','evidence','playbook','capabilities','mcp-tools','pack','proposals','notes','eval','skill','graph','memory','audit-timer','uninstall','completion','version','help'
|
|
732
733
|
$setup = '--git','--harness','--memory','--policy','--yes','--json','init','existing','none',${harnesses},'local'
|
|
733
734
|
$playbook = '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'
|
|
734
|
-
$
|
|
735
|
+
$pack = 'init','lock','validate','install','import-okf','validate-okf','export-okf','list','show','remove','enable','disable','update','skills','approve-skill','revoke-skill','doctor','--repair','--project','--project-id','--json'
|
|
736
|
+
$proposals = 'create','list','show','apply','doctor','--session','--scope','--model','--category','--project','--json'
|
|
737
|
+
$candidates = if ($commandAst.ToString() -match '^\\s*cairn\\s+setup(?:\\s|$)') { $setup } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+playbook(?:\\s|$)') { $playbook } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+pack(?:\\s|$)') { $pack } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+proposals(?:\\s|$)') { $proposals } else { $commands }
|
|
735
738
|
$candidates | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
|
736
739
|
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
737
740
|
}
|