@lannguyensi/harness 0.5.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 +552 -0
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/dist/cli/add/index.d.ts +14 -0
- package/dist/cli/add/index.js +71 -0
- package/dist/cli/add/index.js.map +1 -0
- package/dist/cli/add/mutate.d.ts +39 -0
- package/dist/cli/add/mutate.js +36 -0
- package/dist/cli/add/mutate.js.map +1 -0
- package/dist/cli/adopt/derive.d.ts +38 -0
- package/dist/cli/adopt/derive.js +94 -0
- package/dist/cli/adopt/derive.js.map +1 -0
- package/dist/cli/adopt/index.d.ts +20 -0
- package/dist/cli/adopt/index.js +156 -0
- package/dist/cli/adopt/index.js.map +1 -0
- package/dist/cli/apply/apply.d.ts +49 -0
- package/dist/cli/apply/apply.js +333 -0
- package/dist/cli/apply/apply.js.map +1 -0
- package/dist/cli/apply/generate-memory-index.d.ts +17 -0
- package/dist/cli/apply/generate-memory-index.js +167 -0
- package/dist/cli/apply/generate-memory-index.js.map +1 -0
- package/dist/cli/apply/generate-settings.d.ts +15 -0
- package/dist/cli/apply/generate-settings.js +87 -0
- package/dist/cli/apply/generate-settings.js.map +1 -0
- package/dist/cli/apply/index.d.ts +1 -0
- package/dist/cli/apply/index.js +2 -0
- package/dist/cli/apply/index.js.map +1 -0
- package/dist/cli/audit.d.ts +36 -0
- package/dist/cli/audit.js +121 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/describe.d.ts +13 -0
- package/dist/cli/describe.js +26 -0
- package/dist/cli/describe.js.map +1 -0
- package/dist/cli/diff/engine.d.ts +21 -0
- package/dist/cli/diff/engine.js +161 -0
- package/dist/cli/diff/engine.js.map +1 -0
- package/dist/cli/diff/git.d.ts +6 -0
- package/dist/cli/diff/git.js +32 -0
- package/dist/cli/diff/git.js.map +1 -0
- package/dist/cli/diff/index.d.ts +15 -0
- package/dist/cli/diff/index.js +39 -0
- package/dist/cli/diff/index.js.map +1 -0
- package/dist/cli/diff/since-apply.d.ts +57 -0
- package/dist/cli/diff/since-apply.js +255 -0
- package/dist/cli/diff/since-apply.js.map +1 -0
- package/dist/cli/doctor/format.d.ts +2 -0
- package/dist/cli/doctor/format.js +126 -0
- package/dist/cli/doctor/format.js.map +1 -0
- package/dist/cli/doctor/index.d.ts +14 -0
- package/dist/cli/doctor/index.js +281 -0
- package/dist/cli/doctor/index.js.map +1 -0
- package/dist/cli/doctor/types.d.ts +46 -0
- package/dist/cli/doctor/types.js +2 -0
- package/dist/cli/doctor/types.js.map +1 -0
- package/dist/cli/dry-run.d.ts +46 -0
- package/dist/cli/dry-run.js +168 -0
- package/dist/cli/dry-run.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +10 -0
- package/dist/cli/exit-codes.js +15 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/explain.d.ts +14 -0
- package/dist/cli/explain.js +97 -0
- package/dist/cli/explain.js.map +1 -0
- package/dist/cli/export.d.ts +31 -0
- package/dist/cli/export.js +84 -0
- package/dist/cli/export.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +549 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init/index.d.ts +17 -0
- package/dist/cli/init/index.js +57 -0
- package/dist/cli/init/index.js.map +1 -0
- package/dist/cli/init/templates.d.ts +4 -0
- package/dist/cli/init/templates.js +175 -0
- package/dist/cli/init/templates.js.map +1 -0
- package/dist/cli/list.d.ts +12 -0
- package/dist/cli/list.js +118 -0
- package/dist/cli/list.js.map +1 -0
- package/dist/cli/loader.d.ts +24 -0
- package/dist/cli/loader.js +74 -0
- package/dist/cli/loader.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +6 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/policy/intercept.d.ts +34 -0
- package/dist/cli/policy/intercept.js +172 -0
- package/dist/cli/policy/intercept.js.map +1 -0
- package/dist/cli/remove/index.d.ts +18 -0
- package/dist/cli/remove/index.js +95 -0
- package/dist/cli/remove/index.js.map +1 -0
- package/dist/cli/remove/mutate.d.ts +9 -0
- package/dist/cli/remove/mutate.js +68 -0
- package/dist/cli/remove/mutate.js.map +1 -0
- package/dist/cli/validate/checks.d.ts +23 -0
- package/dist/cli/validate/checks.js +253 -0
- package/dist/cli/validate/checks.js.map +1 -0
- package/dist/cli/validate/index.d.ts +18 -0
- package/dist/cli/validate/index.js +50 -0
- package/dist/cli/validate/index.js.map +1 -0
- package/dist/cli/validate/types.d.ts +7 -0
- package/dist/cli/validate/types.js +5 -0
- package/dist/cli/validate/types.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/io/atomic-write.d.ts +8 -0
- package/dist/io/atomic-write.js +30 -0
- package/dist/io/atomic-write.js.map +1 -0
- package/dist/io/harness-lock.d.ts +33 -0
- package/dist/io/harness-lock.js +260 -0
- package/dist/io/harness-lock.js.map +1 -0
- package/dist/io/last-apply.d.ts +20 -0
- package/dist/io/last-apply.js +123 -0
- package/dist/io/last-apply.js.map +1 -0
- package/dist/io/lock.d.ts +11 -0
- package/dist/io/lock.js +33 -0
- package/dist/io/lock.js.map +1 -0
- package/dist/io/patch.d.ts +10 -0
- package/dist/io/patch.js +8 -0
- package/dist/io/patch.js.map +1 -0
- package/dist/io/restart-hints.d.ts +5 -0
- package/dist/io/restart-hints.js +59 -0
- package/dist/io/restart-hints.js.map +1 -0
- package/dist/io/three-state.d.ts +7 -0
- package/dist/io/three-state.js +20 -0
- package/dist/io/three-state.js.map +1 -0
- package/dist/io/validate-before-write.d.ts +12 -0
- package/dist/io/validate-before-write.js +23 -0
- package/dist/io/validate-before-write.js.map +1 -0
- package/dist/overrides/index.d.ts +2 -0
- package/dist/overrides/index.js +3 -0
- package/dist/overrides/index.js.map +1 -0
- package/dist/overrides/machines.d.ts +12 -0
- package/dist/overrides/machines.js +46 -0
- package/dist/overrides/machines.js.map +1 -0
- package/dist/overrides/merge.d.ts +6 -0
- package/dist/overrides/merge.js +173 -0
- package/dist/overrides/merge.js.map +1 -0
- package/dist/policies/duration.d.ts +5 -0
- package/dist/policies/duration.js +50 -0
- package/dist/policies/duration.js.map +1 -0
- package/dist/policies/extract.d.ts +50 -0
- package/dist/policies/extract.js +190 -0
- package/dist/policies/extract.js.map +1 -0
- package/dist/policies/index.d.ts +5 -0
- package/dist/policies/index.js +6 -0
- package/dist/policies/index.js.map +1 -0
- package/dist/policies/ledger-client.d.ts +39 -0
- package/dist/policies/ledger-client.js +378 -0
- package/dist/policies/ledger-client.js.map +1 -0
- package/dist/policies/requires.d.ts +44 -0
- package/dist/policies/requires.js +146 -0
- package/dist/policies/requires.js.map +1 -0
- package/dist/policies/timestamp.d.ts +14 -0
- package/dist/policies/timestamp.js +36 -0
- package/dist/policies/timestamp.js.map +1 -0
- package/dist/probes/mcp.d.ts +29 -0
- package/dist/probes/mcp.js +226 -0
- package/dist/probes/mcp.js.map +1 -0
- package/dist/probes/memory.d.ts +24 -0
- package/dist/probes/memory.js +89 -0
- package/dist/probes/memory.js.map +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/intercept.d.ts +53 -0
- package/dist/runtime/intercept.js +181 -0
- package/dist/runtime/intercept.js.map +1 -0
- package/dist/runtime/ledger-record.d.ts +43 -0
- package/dist/runtime/ledger-record.js +239 -0
- package/dist/runtime/ledger-record.js.map +1 -0
- package/dist/runtime/session-id.d.ts +10 -0
- package/dist/runtime/session-id.js +37 -0
- package/dist/runtime/session-id.js.map +1 -0
- package/dist/schema/extract.d.ts +5 -0
- package/dist/schema/extract.js +23 -0
- package/dist/schema/extract.js.map +1 -0
- package/dist/schema/grounding.d.ts +65 -0
- package/dist/schema/grounding.js +21 -0
- package/dist/schema/grounding.js.map +1 -0
- package/dist/schema/hooks.d.ts +86 -0
- package/dist/schema/hooks.js +42 -0
- package/dist/schema/hooks.js.map +1 -0
- package/dist/schema/index.d.ts +961 -0
- package/dist/schema/index.js +55 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/memory.d.ts +131 -0
- package/dist/schema/memory.js +38 -0
- package/dist/schema/memory.js.map +1 -0
- package/dist/schema/policies.d.ts +412 -0
- package/dist/schema/policies.js +53 -0
- package/dist/schema/policies.js.map +1 -0
- package/dist/schema/requires.d.ts +115 -0
- package/dist/schema/requires.js +57 -0
- package/dist/schema/requires.js.map +1 -0
- package/dist/schema/tools.d.ts +283 -0
- package/dist/schema/tools.js +66 -0
- package/dist/schema/tools.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Phase 4 #5 — `harness policy intercept` CLI entrypoint.
|
|
2
|
+
//
|
|
3
|
+
// Wired by Claude Code's settings.json (regenerated by `harness apply`) as
|
|
4
|
+
// the PreToolUse hook. Reads the event JSON from stdin, runs the runtime
|
|
5
|
+
// interceptor, writes Claude Code's deny JSON to stdout on block.
|
|
6
|
+
import { queryLedgerByTag, } from "../../policies/index.js";
|
|
7
|
+
import { intercept, recordPolicyDecision, } from "../../runtime/index.js";
|
|
8
|
+
import { loadManifest } from "../loader.js";
|
|
9
|
+
async function readStdin(stream) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
let data = "";
|
|
12
|
+
stream.setEncoding("utf8");
|
|
13
|
+
stream.on("data", (chunk) => {
|
|
14
|
+
data += chunk;
|
|
15
|
+
});
|
|
16
|
+
stream.on("end", () => resolve(data));
|
|
17
|
+
stream.on("error", (err) => reject(err));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function findGroundingMcp(manifest) {
|
|
21
|
+
return manifest.tools.mcp.find((m) => m.name === "grounding-mcp") ?? null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Phase 5 #3 — render a deny / warn-degraded decision as a stderr
|
|
25
|
+
* diagnostic block. Multiline, indented; each block is bounded by the
|
|
26
|
+
* policy name so concurrent fires (rare but possible) stay readable.
|
|
27
|
+
*/
|
|
28
|
+
function formatDecisionDiagnostic(decision) {
|
|
29
|
+
const header = `harness policy intercept: ${decision.policyName}: ${decision.outcome}${decision.outcome === "warn-degraded" ? " (ledger unreachable)" : ""}`;
|
|
30
|
+
const lines = [header];
|
|
31
|
+
lines.push(` ledger_tag: ${decision.ledgerTag}`);
|
|
32
|
+
if (decision.requiresEval !== undefined) {
|
|
33
|
+
lines.push(` matched: ${decision.requiresEval.matchedCount}`);
|
|
34
|
+
}
|
|
35
|
+
lines.push(` reason: ${decision.reason}`);
|
|
36
|
+
const extractKeys = Object.keys(decision.extractValues);
|
|
37
|
+
if (extractKeys.length > 0) {
|
|
38
|
+
lines.push(" extract:");
|
|
39
|
+
for (const k of extractKeys.sort()) {
|
|
40
|
+
lines.push(` ${k}=${decision.extractValues[k]}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return `${lines.join("\n")}\n`;
|
|
44
|
+
}
|
|
45
|
+
function isVerboseEnabled(opts) {
|
|
46
|
+
if (opts.verbose === true)
|
|
47
|
+
return true;
|
|
48
|
+
if (opts.verbose === false)
|
|
49
|
+
return false;
|
|
50
|
+
const env = process.env.HARNESS_POLICY_VERBOSE;
|
|
51
|
+
if (typeof env !== "string")
|
|
52
|
+
return false;
|
|
53
|
+
if (env.length === 0)
|
|
54
|
+
return false;
|
|
55
|
+
// Accept anything truthy except literal disable-words (case-insensitive).
|
|
56
|
+
return !/^(0|false|no|off)$/i.test(env.trim());
|
|
57
|
+
}
|
|
58
|
+
function realLedgerClient(server, opts) {
|
|
59
|
+
const command = Array.isArray(server.command)
|
|
60
|
+
? server.command
|
|
61
|
+
: server.command.trim().split(/\s+/);
|
|
62
|
+
const env = server.env ?? undefined;
|
|
63
|
+
const timeoutMs = opts.ledgerTimeoutMs ?? server.health?.timeout_ms ?? 5_000;
|
|
64
|
+
return {
|
|
65
|
+
async query(_tag, sessionId) {
|
|
66
|
+
return queryLedgerByTag({
|
|
67
|
+
mcpCommand: command,
|
|
68
|
+
...(env && { mcpEnv: env }),
|
|
69
|
+
sessionId,
|
|
70
|
+
timeoutMs,
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
async record(decision, sessionId) {
|
|
74
|
+
await recordPolicyDecision(decision, sessionId, {
|
|
75
|
+
mcpCommand: command,
|
|
76
|
+
...(env && { mcpEnv: env }),
|
|
77
|
+
timeoutMs,
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function degradedLedgerClient(reason) {
|
|
83
|
+
return {
|
|
84
|
+
async query() {
|
|
85
|
+
return { kind: "degraded", reason };
|
|
86
|
+
},
|
|
87
|
+
async record() {
|
|
88
|
+
/* no-op when ledger is unavailable */
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export async function runInterceptCli(opts = {}) {
|
|
93
|
+
const stdin = opts.stdin ?? process.stdin;
|
|
94
|
+
const stdout = opts.stdout ?? process.stdout;
|
|
95
|
+
const stderr = opts.stderr ?? process.stderr;
|
|
96
|
+
const verbose = isVerboseEnabled(opts);
|
|
97
|
+
const raw = await readStdin(stdin);
|
|
98
|
+
let event;
|
|
99
|
+
try {
|
|
100
|
+
event = JSON.parse(raw.trim() || "{}");
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
process.stderr.write(`harness policy intercept: malformed event JSON: ${err.message}\n`);
|
|
104
|
+
return { exitCode: 0, decisions: [], blocked: false };
|
|
105
|
+
}
|
|
106
|
+
let manifest;
|
|
107
|
+
try {
|
|
108
|
+
manifest = opts.manifest ?? loadManifest(opts).manifest;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
process.stderr.write(`harness policy intercept: manifest load failed: ${err.message}\n`);
|
|
112
|
+
return { exitCode: 0, decisions: [], blocked: false };
|
|
113
|
+
}
|
|
114
|
+
let ledger;
|
|
115
|
+
if (opts.ledger) {
|
|
116
|
+
ledger = opts.ledger;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const server = findGroundingMcp(manifest);
|
|
120
|
+
ledger = server
|
|
121
|
+
? realLedgerClient(server, opts)
|
|
122
|
+
: degradedLedgerClient("grounding-mcp not declared in manifest");
|
|
123
|
+
}
|
|
124
|
+
// For the SESSION_ID builtin we keep the empty-string fallback rather
|
|
125
|
+
// than routing through resolveSessionId (which lands on "default").
|
|
126
|
+
// Routing through "default" would silently bake the literal string
|
|
127
|
+
// into ledger_tag templates like `dogfood:${SESSION_ID}` →
|
|
128
|
+
// `dogfood:default`, which would substring-match anything in the
|
|
129
|
+
// shared "default" session — a worse failure mode than the current
|
|
130
|
+
// empty substitution. The empty path produces tags like `dogfood:`
|
|
131
|
+
// (substituteTemplate treats the registered "" value as a successful
|
|
132
|
+
// substitution since the key is hasOwnProperty-present, so this is
|
|
133
|
+
// NOT marked warn-degraded by the requires evaluator); the ledger
|
|
134
|
+
// query for that fire also lands on the resolveSessionId-derived
|
|
135
|
+
// session, so the asymmetry is acceptable in practice. Do NOT
|
|
136
|
+
// "simplify" by routing this through resolveSessionId.
|
|
137
|
+
// The env fallback is still applied so a Claude-Code-driven hook with
|
|
138
|
+
// a stripped event can pick up the active session.
|
|
139
|
+
const eventSessionId = typeof event.session_id === "string" ? event.session_id : undefined;
|
|
140
|
+
const builtinSessionId = eventSessionId ?? process.env.CLAUDE_SESSION_ID ?? "";
|
|
141
|
+
const builtins = {
|
|
142
|
+
SESSION_ID: builtinSessionId,
|
|
143
|
+
REPO: process.env.HARNESS_REPO ?? "",
|
|
144
|
+
BRANCH: process.env.HARNESS_BRANCH ?? "",
|
|
145
|
+
TOOL_NAME: typeof event.tool_name === "string" ? event.tool_name : "",
|
|
146
|
+
CWD: typeof event.cwd === "string" ? event.cwd : process.cwd(),
|
|
147
|
+
};
|
|
148
|
+
const result = await intercept({
|
|
149
|
+
manifest,
|
|
150
|
+
event,
|
|
151
|
+
ledger,
|
|
152
|
+
builtins,
|
|
153
|
+
...(opts.ledgerTimeoutMs !== undefined && { ledgerTimeoutMs: opts.ledgerTimeoutMs }),
|
|
154
|
+
...(opts.now && { now: opts.now }),
|
|
155
|
+
});
|
|
156
|
+
if (result.blockJson) {
|
|
157
|
+
stdout.write(`${JSON.stringify(result.blockJson)}\n`);
|
|
158
|
+
}
|
|
159
|
+
if (verbose) {
|
|
160
|
+
for (const decision of result.decisions) {
|
|
161
|
+
if (decision.outcome === "allow")
|
|
162
|
+
continue;
|
|
163
|
+
stderr.write(formatDecisionDiagnostic(decision));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
exitCode: 0,
|
|
168
|
+
decisions: result.decisions,
|
|
169
|
+
blocked: result.blockJson !== null,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=intercept.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intercept.js","sourceRoot":"","sources":["../../../src/cli/policy/intercept.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type RemoveType } from "./mutate.js";
|
|
2
|
+
export interface RemoveOptions {
|
|
3
|
+
configPath?: string;
|
|
4
|
+
homeDir?: string;
|
|
5
|
+
dryRun?: boolean;
|
|
6
|
+
force?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface RemoveResult {
|
|
9
|
+
path: string;
|
|
10
|
+
type: RemoveType;
|
|
11
|
+
name: string;
|
|
12
|
+
diff: string;
|
|
13
|
+
applied: boolean;
|
|
14
|
+
forcedReferences: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function remove(type: RemoveType, name: string, opts?: RemoveOptions): Promise<RemoveResult>;
|
|
17
|
+
export declare const KNOWN_REMOVE_TYPES: RemoveType[];
|
|
18
|
+
export declare function isRemoveType(s: string): s is RemoveType;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { atomicWriteFile } from "../../io/atomic-write.js";
|
|
6
|
+
import { withFileLock } from "../../io/lock.js";
|
|
7
|
+
import { unifiedDiff } from "../../io/patch.js";
|
|
8
|
+
import { formatValidationErrors, validateBeforeWrite, } from "../../io/validate-before-write.js";
|
|
9
|
+
import { EX_FAIL, EX_NOINPUT, HarnessExitError } from "../exit-codes.js";
|
|
10
|
+
import { applyRemove, planRemove } from "./mutate.js";
|
|
11
|
+
const DEFAULT_BASENAME = "harness.yaml";
|
|
12
|
+
const LOCK_BASENAME = ".harness.lock";
|
|
13
|
+
function resolveTargetPath(opts) {
|
|
14
|
+
if (opts.configPath)
|
|
15
|
+
return path.resolve(opts.configPath);
|
|
16
|
+
return path.join(opts.homeDir ?? path.join(os.homedir(), ".claude"), DEFAULT_BASENAME);
|
|
17
|
+
}
|
|
18
|
+
function formatNameList(names) {
|
|
19
|
+
if (names.length === 0)
|
|
20
|
+
return "(none declared)";
|
|
21
|
+
return names.map((n) => ` - ${n}`).join("\n");
|
|
22
|
+
}
|
|
23
|
+
export async function remove(type, name, opts = {}) {
|
|
24
|
+
const target = resolveTargetPath(opts);
|
|
25
|
+
if (!fs.existsSync(target)) {
|
|
26
|
+
throw new HarnessExitError(`harness manifest not found at ${target}; run \`harness init\` first`, EX_NOINPUT);
|
|
27
|
+
}
|
|
28
|
+
const original = fs.readFileSync(target, "utf8");
|
|
29
|
+
const plan = planRemove(original, type, name);
|
|
30
|
+
if (!plan.found) {
|
|
31
|
+
throw new HarnessExitError(`${type} entry "${name}" not found. Available ${type} entries:\n${formatNameList(plan.availableNames)}`, EX_FAIL);
|
|
32
|
+
}
|
|
33
|
+
if (plan.referencingPolicies.length > 0 && !opts.force) {
|
|
34
|
+
const refList = plan.referencingPolicies.map((p) => `"${p}"`).join(", ");
|
|
35
|
+
const verb = plan.referencingPolicies.length === 1 ? "policy" : "policies";
|
|
36
|
+
throw new HarnessExitError(`${verb} ${refList} reference${plan.referencingPolicies.length === 1 ? "s" : ""} this hook; remove the ${verb} first or pass --force`, EX_FAIL);
|
|
37
|
+
}
|
|
38
|
+
const proposed = applyRemove(original, type, name);
|
|
39
|
+
const diff = unifiedDiff({
|
|
40
|
+
fileName: path.basename(target),
|
|
41
|
+
oldText: original,
|
|
42
|
+
newText: proposed,
|
|
43
|
+
oldHeader: "current",
|
|
44
|
+
newHeader: "proposed",
|
|
45
|
+
});
|
|
46
|
+
// Schema gate. With --force on a referenced hook, the resulting manifest
|
|
47
|
+
// contains a dangling policy.hook reference, which the schema rejects.
|
|
48
|
+
// That refusal is the contract: --force does not let you ship a broken
|
|
49
|
+
// manifest, it just tells remove to skip the human-readable hook-reference
|
|
50
|
+
// pre-check. The actual safety net is the schema, which still fires.
|
|
51
|
+
const schemaResult = validateBeforeWrite(parseYaml(proposed));
|
|
52
|
+
if (!schemaResult.ok) {
|
|
53
|
+
throw new HarnessExitError(`proposed manifest fails schema validation:\n${formatValidationErrors(schemaResult.errors)}`, EX_FAIL);
|
|
54
|
+
}
|
|
55
|
+
if (opts.dryRun) {
|
|
56
|
+
// forcedReferences is informational on dry-run: it shows the user which
|
|
57
|
+
// policies would have been overridden if the schema gate did not refuse.
|
|
58
|
+
// On the write path below it is always [] because --force on a referenced
|
|
59
|
+
// hook never reaches that point — the schema rejects the dangling reference.
|
|
60
|
+
return {
|
|
61
|
+
path: target,
|
|
62
|
+
type,
|
|
63
|
+
name,
|
|
64
|
+
diff,
|
|
65
|
+
applied: false,
|
|
66
|
+
forcedReferences: opts.force ? plan.referencingPolicies : [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const lockPath = path.join(path.dirname(target), LOCK_BASENAME);
|
|
70
|
+
await withFileLock(lockPath, () => {
|
|
71
|
+
const current = fs.readFileSync(target, "utf8");
|
|
72
|
+
const next = applyRemove(current, type, name);
|
|
73
|
+
const recheck = validateBeforeWrite(parseYaml(next));
|
|
74
|
+
if (!recheck.ok) {
|
|
75
|
+
throw new HarnessExitError(`proposed manifest fails schema validation after lock acquisition:\n${formatValidationErrors(recheck.errors)}`, EX_FAIL);
|
|
76
|
+
}
|
|
77
|
+
atomicWriteFile(target, next);
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
path: target,
|
|
81
|
+
type,
|
|
82
|
+
name,
|
|
83
|
+
diff,
|
|
84
|
+
applied: true,
|
|
85
|
+
// Always [] on the write path — schema gate rejects --force on a referenced
|
|
86
|
+
// hook, so the only way to reach here with --force is when there are no
|
|
87
|
+
// referencing policies to begin with.
|
|
88
|
+
forcedReferences: [],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export const KNOWN_REMOVE_TYPES = ["mcp", "cli", "skill", "hook"];
|
|
92
|
+
export function isRemoveType(s) {
|
|
93
|
+
return KNOWN_REMOVE_TYPES.includes(s);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/remove/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type RemoveType = "mcp" | "cli" | "skill" | "hook";
|
|
2
|
+
export interface RemovePlan {
|
|
3
|
+
found: boolean;
|
|
4
|
+
availableNames: string[];
|
|
5
|
+
/** For type=hook only: the policies that reference this hook. */
|
|
6
|
+
referencingPolicies: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function planRemove(yamlText: string, type: RemoveType, name: string): RemovePlan;
|
|
9
|
+
export declare function applyRemove(yamlText: string, type: RemoveType, name: string): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isMap, isScalar, isSeq, parseDocument } from "yaml";
|
|
2
|
+
const TYPE_TO_PATH = {
|
|
3
|
+
mcp: ["tools", "mcp"],
|
|
4
|
+
cli: ["tools", "cli"],
|
|
5
|
+
skill: ["tools", "skills", "enabled"],
|
|
6
|
+
hook: ["hooks"],
|
|
7
|
+
};
|
|
8
|
+
export function planRemove(yamlText, type, name) {
|
|
9
|
+
const doc = parseDocument(yamlText);
|
|
10
|
+
const list = doc.getIn(TYPE_TO_PATH[type]);
|
|
11
|
+
const availableNames = listEntryNames(list, type);
|
|
12
|
+
const found = availableNames.includes(name);
|
|
13
|
+
const referencingPolicies = type === "hook" ? findPoliciesReferencingHook(doc, name) : [];
|
|
14
|
+
return { found, availableNames, referencingPolicies };
|
|
15
|
+
}
|
|
16
|
+
export function applyRemove(yamlText, type, name) {
|
|
17
|
+
const doc = parseDocument(yamlText);
|
|
18
|
+
const list = doc.getIn(TYPE_TO_PATH[type]);
|
|
19
|
+
if (!isSeq(list)) {
|
|
20
|
+
throw new Error(`expected a YAML sequence at ${TYPE_TO_PATH[type].join(".")}, found none`);
|
|
21
|
+
}
|
|
22
|
+
const idx = list.items.findIndex((item) => entryNameOf(item, type) === name);
|
|
23
|
+
if (idx < 0) {
|
|
24
|
+
throw new Error(`${type} entry "${name}" not found`);
|
|
25
|
+
}
|
|
26
|
+
list.items.splice(idx, 1);
|
|
27
|
+
return doc.toString({ flowCollectionPadding: false, lineWidth: 0 });
|
|
28
|
+
}
|
|
29
|
+
function listEntryNames(list, type) {
|
|
30
|
+
if (!isSeq(list))
|
|
31
|
+
return [];
|
|
32
|
+
return list.items
|
|
33
|
+
.map((item) => entryNameOf(item, type))
|
|
34
|
+
.filter((s) => typeof s === "string");
|
|
35
|
+
}
|
|
36
|
+
function entryNameOf(item, type) {
|
|
37
|
+
if (type === "skill") {
|
|
38
|
+
if (isScalar(item) && typeof item.value === "string")
|
|
39
|
+
return item.value;
|
|
40
|
+
if (typeof item === "string")
|
|
41
|
+
return item;
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (isMap(item)) {
|
|
45
|
+
const n = item.get("name");
|
|
46
|
+
if (typeof n === "string")
|
|
47
|
+
return n;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
function findPoliciesReferencingHook(doc, hookName) {
|
|
52
|
+
const policies = doc.get("policies");
|
|
53
|
+
if (!isSeq(policies))
|
|
54
|
+
return [];
|
|
55
|
+
const refs = [];
|
|
56
|
+
for (const item of policies.items) {
|
|
57
|
+
if (!isMap(item))
|
|
58
|
+
continue;
|
|
59
|
+
const hook = item.get("hook");
|
|
60
|
+
if (typeof hook === "string" && hook === hookName) {
|
|
61
|
+
const name = item.get("name");
|
|
62
|
+
if (typeof name === "string")
|
|
63
|
+
refs.push(name);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return refs;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=mutate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutate.js","sourceRoot":"","sources":["../../../src/cli/remove/mutate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAiB,MAAM,MAAM,CAAC;AAI5E,MAAM,YAAY,GAA0C;IAC1D,GAAG,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC;IACrB,GAAG,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC;IACrB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC;IACrC,IAAI,EAAE,CAAC,OAAO,CAAC;CAChB,CAAC;AASF,MAAM,UAAU,UAAU,CACxB,QAAgB,EAChB,IAAgB,EAChB,IAAY;IAEZ,MAAM,GAAG,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3C,MAAM,cAAc,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,mBAAmB,GACvB,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,2BAA2B,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,QAAgB,EAChB,IAAgB,EAChB,IAAY;IAEZ,MAAM,GAAG,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,+BAA+B,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,CAC1E,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;IAC7E,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,WAAW,IAAI,aAAa,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC1B,OAAO,GAAG,CAAC,QAAQ,CAAC,EAAE,qBAAqB,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,cAAc,CAAC,IAAa,EAAE,IAAgB;IACrD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5B,OAAO,IAAI,CAAC,KAAK;SACd,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;SACtC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,WAAW,CAAC,IAAa,EAAE,IAAgB;IAClD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC;QACxE,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC1C,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAChB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3B,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,2BAA2B,CAClC,GAAoB,EACpB,QAAgB;IAEhB,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC9B,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Manifest } from "../../schema/index.js";
|
|
2
|
+
import type { Diagnostic } from "./types.js";
|
|
3
|
+
export interface CheckOptions {
|
|
4
|
+
homeDir?: string;
|
|
5
|
+
pathEnv?: string;
|
|
6
|
+
builtinRuntimeProbe?: () => string[];
|
|
7
|
+
versionProbe?: (cmd: string[]) => string | null;
|
|
8
|
+
}
|
|
9
|
+
declare function expandHome(p: string, home: string): string;
|
|
10
|
+
declare function isRootedPath(p: string): boolean;
|
|
11
|
+
declare function firstToken(command: string): string;
|
|
12
|
+
declare function resolveOnPath(binary: string, pathEnv: string): string | null;
|
|
13
|
+
declare function compareVersions(actual: string, required: string): number;
|
|
14
|
+
export declare function runAssetChecks(manifest: Manifest, opts?: CheckOptions): Diagnostic[];
|
|
15
|
+
export declare const __testables: {
|
|
16
|
+
expandHome: typeof expandHome;
|
|
17
|
+
isRootedPath: typeof isRootedPath;
|
|
18
|
+
firstToken: typeof firstToken;
|
|
19
|
+
compareVersions: typeof compareVersions;
|
|
20
|
+
resolveOnPath: typeof resolveOnPath;
|
|
21
|
+
DEFAULT_RUNTIME_BUILTINS: string[];
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
const DEFAULT_RUNTIME_BUILTINS = [
|
|
5
|
+
"Read",
|
|
6
|
+
"Edit",
|
|
7
|
+
"Write",
|
|
8
|
+
"Bash",
|
|
9
|
+
"Agent",
|
|
10
|
+
"Skill",
|
|
11
|
+
"TaskCreate",
|
|
12
|
+
"Glob",
|
|
13
|
+
"Grep",
|
|
14
|
+
];
|
|
15
|
+
function expandHome(p, home) {
|
|
16
|
+
if (p === "~")
|
|
17
|
+
return home;
|
|
18
|
+
if (p.startsWith("~/"))
|
|
19
|
+
return path.join(home, p.slice(2));
|
|
20
|
+
return p;
|
|
21
|
+
}
|
|
22
|
+
function isRootedPath(p) {
|
|
23
|
+
return path.isAbsolute(p) || p === "~" || p.startsWith("~/");
|
|
24
|
+
}
|
|
25
|
+
function firstToken(command) {
|
|
26
|
+
return command.trim().split(/\s+/)[0] ?? "";
|
|
27
|
+
}
|
|
28
|
+
function isExecutable(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
fs.accessSync(filePath, fs.constants.X_OK);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function statOrNull(filePath) {
|
|
38
|
+
try {
|
|
39
|
+
return fs.statSync(filePath);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function resolveOnPath(binary, pathEnv) {
|
|
46
|
+
if (binary.includes(path.sep) || path.isAbsolute(binary))
|
|
47
|
+
return null;
|
|
48
|
+
const segments = pathEnv.split(path.delimiter).filter(Boolean);
|
|
49
|
+
for (const seg of segments) {
|
|
50
|
+
const candidate = path.join(seg, binary);
|
|
51
|
+
if (fs.existsSync(candidate) && isExecutable(candidate))
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const SEMVER_RE = /(\d+(?:\.\d+){0,3})/;
|
|
57
|
+
function compareVersions(actual, required) {
|
|
58
|
+
const a = actual.split(".").map((n) => Number.parseInt(n, 10));
|
|
59
|
+
const r = required.split(".").map((n) => Number.parseInt(n, 10));
|
|
60
|
+
const len = Math.max(a.length, r.length);
|
|
61
|
+
for (let i = 0; i < len; i++) {
|
|
62
|
+
const ai = a[i] ?? 0;
|
|
63
|
+
const ri = r[i] ?? 0;
|
|
64
|
+
if (Number.isNaN(ai) || Number.isNaN(ri))
|
|
65
|
+
return 0;
|
|
66
|
+
if (ai > ri)
|
|
67
|
+
return 1;
|
|
68
|
+
if (ai < ri)
|
|
69
|
+
return -1;
|
|
70
|
+
}
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
function checkMcp(manifest, home) {
|
|
74
|
+
const diags = [];
|
|
75
|
+
manifest.tools.mcp.forEach((mcp) => {
|
|
76
|
+
const cmdArr = Array.isArray(mcp.command) ? mcp.command : mcp.command.trim().split(/\s+/);
|
|
77
|
+
const first = cmdArr[0] ?? "";
|
|
78
|
+
if (!isRootedPath(first))
|
|
79
|
+
return;
|
|
80
|
+
const resolved = expandHome(first, home);
|
|
81
|
+
const stat = statOrNull(resolved);
|
|
82
|
+
if (!stat) {
|
|
83
|
+
diags.push({
|
|
84
|
+
severity: "error",
|
|
85
|
+
path: `tools.mcp[${mcp.name}].command`,
|
|
86
|
+
message: `path does not exist: ${resolved}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return diags;
|
|
91
|
+
}
|
|
92
|
+
function checkCli(manifest, opts) {
|
|
93
|
+
const diags = [];
|
|
94
|
+
const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
|
|
95
|
+
const versionProbe = opts.versionProbe ?? (() => null);
|
|
96
|
+
manifest.tools.cli.forEach((cli) => {
|
|
97
|
+
let resolved;
|
|
98
|
+
if (path.isAbsolute(cli.binary)) {
|
|
99
|
+
resolved = fs.existsSync(cli.binary) && isExecutable(cli.binary) ? cli.binary : null;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
resolved = resolveOnPath(cli.binary, pathEnv);
|
|
103
|
+
}
|
|
104
|
+
if (!resolved) {
|
|
105
|
+
diags.push({
|
|
106
|
+
severity: cli.required ? "error" : "warning",
|
|
107
|
+
path: `tools.cli[${cli.name}].binary`,
|
|
108
|
+
message: cli.required
|
|
109
|
+
? `required binary not found: ${cli.binary}`
|
|
110
|
+
: `binary not found on PATH: ${cli.binary}`,
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!cli.min_version)
|
|
115
|
+
return;
|
|
116
|
+
const versionCommand = cli.version_command ?? [resolved, "--version"];
|
|
117
|
+
const stdout = versionProbe(versionCommand);
|
|
118
|
+
if (stdout === null) {
|
|
119
|
+
diags.push({
|
|
120
|
+
severity: "warning",
|
|
121
|
+
path: `tools.cli[${cli.name}].min_version`,
|
|
122
|
+
message: `version probe failed for ${versionCommand.join(" ")}`,
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const match = stdout.match(SEMVER_RE);
|
|
127
|
+
if (!match || !match[1]) {
|
|
128
|
+
diags.push({
|
|
129
|
+
severity: "warning",
|
|
130
|
+
path: `tools.cli[${cli.name}].min_version`,
|
|
131
|
+
message: `could not parse a version from "${stdout.trim()}"`,
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (compareVersions(match[1], cli.min_version) < 0) {
|
|
136
|
+
diags.push({
|
|
137
|
+
severity: "error",
|
|
138
|
+
path: `tools.cli[${cli.name}].min_version`,
|
|
139
|
+
message: `installed version ${match[1]} is less than required ${cli.min_version}`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return diags;
|
|
144
|
+
}
|
|
145
|
+
function checkSkills(manifest, home) {
|
|
146
|
+
const diags = [];
|
|
147
|
+
const required = manifest.tools.skills.required ?? [];
|
|
148
|
+
if (required.length === 0)
|
|
149
|
+
return diags;
|
|
150
|
+
for (const skillName of required) {
|
|
151
|
+
let found = false;
|
|
152
|
+
for (const dir of manifest.tools.skills.source_dirs) {
|
|
153
|
+
const expanded = expandHome(dir, home);
|
|
154
|
+
const candidate = path.join(expanded, skillName, "SKILL.md");
|
|
155
|
+
if (fs.existsSync(candidate)) {
|
|
156
|
+
found = true;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (!found) {
|
|
161
|
+
diags.push({
|
|
162
|
+
severity: "error",
|
|
163
|
+
path: `tools.skills.required[${skillName}]`,
|
|
164
|
+
message: `SKILL.md not found in any tools.skills.source_dirs entry`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return diags;
|
|
169
|
+
}
|
|
170
|
+
function checkHooks(manifest, home) {
|
|
171
|
+
const diags = [];
|
|
172
|
+
manifest.hooks.forEach((hook) => {
|
|
173
|
+
const first = firstToken(hook.command);
|
|
174
|
+
if (!isRootedPath(first))
|
|
175
|
+
return;
|
|
176
|
+
const resolved = expandHome(first, home);
|
|
177
|
+
const stat = statOrNull(resolved);
|
|
178
|
+
if (!stat) {
|
|
179
|
+
diags.push({
|
|
180
|
+
severity: "error",
|
|
181
|
+
path: `hooks[${hook.name}].command`,
|
|
182
|
+
message: `path does not exist: ${resolved}`,
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (!stat.isFile()) {
|
|
187
|
+
diags.push({
|
|
188
|
+
severity: "error",
|
|
189
|
+
path: `hooks[${hook.name}].command`,
|
|
190
|
+
message: `not a regular file: ${resolved}`,
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (!isExecutable(resolved)) {
|
|
195
|
+
diags.push({
|
|
196
|
+
severity: "error",
|
|
197
|
+
path: `hooks[${hook.name}].command`,
|
|
198
|
+
message: `not executable (chmod +x): ${resolved}`,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
return diags;
|
|
203
|
+
}
|
|
204
|
+
function checkBuiltinDrift(manifest, opts) {
|
|
205
|
+
const probe = opts.builtinRuntimeProbe ?? (() => DEFAULT_RUNTIME_BUILTINS);
|
|
206
|
+
const runtime = probe();
|
|
207
|
+
const known = new Set(manifest.tools.builtin.known);
|
|
208
|
+
const diags = [];
|
|
209
|
+
for (const r of runtime) {
|
|
210
|
+
if (!known.has(r)) {
|
|
211
|
+
diags.push({
|
|
212
|
+
severity: "warning",
|
|
213
|
+
path: `tools.builtin.known`,
|
|
214
|
+
message: `runtime advertises built-in "${r}" but the manifest does not list it`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return diags;
|
|
219
|
+
}
|
|
220
|
+
function checkPolicyGroundingMcp(manifest) {
|
|
221
|
+
if (manifest.policies.length === 0)
|
|
222
|
+
return [];
|
|
223
|
+
const wired = manifest.tools.mcp.some((m) => m.name === "grounding-mcp");
|
|
224
|
+
if (wired)
|
|
225
|
+
return [];
|
|
226
|
+
return [
|
|
227
|
+
{
|
|
228
|
+
severity: "warning",
|
|
229
|
+
path: "policies",
|
|
230
|
+
message: "policies declared but grounding-mcp not wired: every policy will fire in degraded warn-mode at runtime; see docs/ARCHITECTURE.md §6",
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
export function runAssetChecks(manifest, opts = {}) {
|
|
235
|
+
const home = opts.homeDir ?? os.homedir();
|
|
236
|
+
return [
|
|
237
|
+
...checkMcp(manifest, home),
|
|
238
|
+
...checkCli(manifest, opts),
|
|
239
|
+
...checkSkills(manifest, home),
|
|
240
|
+
...checkHooks(manifest, home),
|
|
241
|
+
...checkBuiltinDrift(manifest, opts),
|
|
242
|
+
...checkPolicyGroundingMcp(manifest),
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
export const __testables = {
|
|
246
|
+
expandHome,
|
|
247
|
+
isRootedPath,
|
|
248
|
+
firstToken,
|
|
249
|
+
compareVersions,
|
|
250
|
+
resolveOnPath,
|
|
251
|
+
DEFAULT_RUNTIME_BUILTINS,
|
|
252
|
+
};
|
|
253
|
+
//# sourceMappingURL=checks.js.map
|