@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.
Files changed (199) hide show
  1. package/CHANGELOG.md +552 -0
  2. package/LICENSE +21 -0
  3. package/README.md +154 -0
  4. package/dist/cli/add/index.d.ts +14 -0
  5. package/dist/cli/add/index.js +71 -0
  6. package/dist/cli/add/index.js.map +1 -0
  7. package/dist/cli/add/mutate.d.ts +39 -0
  8. package/dist/cli/add/mutate.js +36 -0
  9. package/dist/cli/add/mutate.js.map +1 -0
  10. package/dist/cli/adopt/derive.d.ts +38 -0
  11. package/dist/cli/adopt/derive.js +94 -0
  12. package/dist/cli/adopt/derive.js.map +1 -0
  13. package/dist/cli/adopt/index.d.ts +20 -0
  14. package/dist/cli/adopt/index.js +156 -0
  15. package/dist/cli/adopt/index.js.map +1 -0
  16. package/dist/cli/apply/apply.d.ts +49 -0
  17. package/dist/cli/apply/apply.js +333 -0
  18. package/dist/cli/apply/apply.js.map +1 -0
  19. package/dist/cli/apply/generate-memory-index.d.ts +17 -0
  20. package/dist/cli/apply/generate-memory-index.js +167 -0
  21. package/dist/cli/apply/generate-memory-index.js.map +1 -0
  22. package/dist/cli/apply/generate-settings.d.ts +15 -0
  23. package/dist/cli/apply/generate-settings.js +87 -0
  24. package/dist/cli/apply/generate-settings.js.map +1 -0
  25. package/dist/cli/apply/index.d.ts +1 -0
  26. package/dist/cli/apply/index.js +2 -0
  27. package/dist/cli/apply/index.js.map +1 -0
  28. package/dist/cli/audit.d.ts +36 -0
  29. package/dist/cli/audit.js +121 -0
  30. package/dist/cli/audit.js.map +1 -0
  31. package/dist/cli/describe.d.ts +13 -0
  32. package/dist/cli/describe.js +26 -0
  33. package/dist/cli/describe.js.map +1 -0
  34. package/dist/cli/diff/engine.d.ts +21 -0
  35. package/dist/cli/diff/engine.js +161 -0
  36. package/dist/cli/diff/engine.js.map +1 -0
  37. package/dist/cli/diff/git.d.ts +6 -0
  38. package/dist/cli/diff/git.js +32 -0
  39. package/dist/cli/diff/git.js.map +1 -0
  40. package/dist/cli/diff/index.d.ts +15 -0
  41. package/dist/cli/diff/index.js +39 -0
  42. package/dist/cli/diff/index.js.map +1 -0
  43. package/dist/cli/diff/since-apply.d.ts +57 -0
  44. package/dist/cli/diff/since-apply.js +255 -0
  45. package/dist/cli/diff/since-apply.js.map +1 -0
  46. package/dist/cli/doctor/format.d.ts +2 -0
  47. package/dist/cli/doctor/format.js +126 -0
  48. package/dist/cli/doctor/format.js.map +1 -0
  49. package/dist/cli/doctor/index.d.ts +14 -0
  50. package/dist/cli/doctor/index.js +281 -0
  51. package/dist/cli/doctor/index.js.map +1 -0
  52. package/dist/cli/doctor/types.d.ts +46 -0
  53. package/dist/cli/doctor/types.js +2 -0
  54. package/dist/cli/doctor/types.js.map +1 -0
  55. package/dist/cli/dry-run.d.ts +46 -0
  56. package/dist/cli/dry-run.js +168 -0
  57. package/dist/cli/dry-run.js.map +1 -0
  58. package/dist/cli/exit-codes.d.ts +10 -0
  59. package/dist/cli/exit-codes.js +15 -0
  60. package/dist/cli/exit-codes.js.map +1 -0
  61. package/dist/cli/explain.d.ts +14 -0
  62. package/dist/cli/explain.js +97 -0
  63. package/dist/cli/explain.js.map +1 -0
  64. package/dist/cli/export.d.ts +31 -0
  65. package/dist/cli/export.js +84 -0
  66. package/dist/cli/export.js.map +1 -0
  67. package/dist/cli/index.d.ts +8 -0
  68. package/dist/cli/index.js +549 -0
  69. package/dist/cli/index.js.map +1 -0
  70. package/dist/cli/init/index.d.ts +17 -0
  71. package/dist/cli/init/index.js +57 -0
  72. package/dist/cli/init/index.js.map +1 -0
  73. package/dist/cli/init/templates.d.ts +4 -0
  74. package/dist/cli/init/templates.js +175 -0
  75. package/dist/cli/init/templates.js.map +1 -0
  76. package/dist/cli/list.d.ts +12 -0
  77. package/dist/cli/list.js +118 -0
  78. package/dist/cli/list.js.map +1 -0
  79. package/dist/cli/loader.d.ts +24 -0
  80. package/dist/cli/loader.js +74 -0
  81. package/dist/cli/loader.js.map +1 -0
  82. package/dist/cli/main.d.ts +2 -0
  83. package/dist/cli/main.js +6 -0
  84. package/dist/cli/main.js.map +1 -0
  85. package/dist/cli/policy/intercept.d.ts +34 -0
  86. package/dist/cli/policy/intercept.js +172 -0
  87. package/dist/cli/policy/intercept.js.map +1 -0
  88. package/dist/cli/remove/index.d.ts +18 -0
  89. package/dist/cli/remove/index.js +95 -0
  90. package/dist/cli/remove/index.js.map +1 -0
  91. package/dist/cli/remove/mutate.d.ts +9 -0
  92. package/dist/cli/remove/mutate.js +68 -0
  93. package/dist/cli/remove/mutate.js.map +1 -0
  94. package/dist/cli/validate/checks.d.ts +23 -0
  95. package/dist/cli/validate/checks.js +253 -0
  96. package/dist/cli/validate/checks.js.map +1 -0
  97. package/dist/cli/validate/index.d.ts +18 -0
  98. package/dist/cli/validate/index.js +50 -0
  99. package/dist/cli/validate/index.js.map +1 -0
  100. package/dist/cli/validate/types.d.ts +7 -0
  101. package/dist/cli/validate/types.js +5 -0
  102. package/dist/cli/validate/types.js.map +1 -0
  103. package/dist/index.d.ts +15 -0
  104. package/dist/index.js +16 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/io/atomic-write.d.ts +8 -0
  107. package/dist/io/atomic-write.js +30 -0
  108. package/dist/io/atomic-write.js.map +1 -0
  109. package/dist/io/harness-lock.d.ts +33 -0
  110. package/dist/io/harness-lock.js +260 -0
  111. package/dist/io/harness-lock.js.map +1 -0
  112. package/dist/io/last-apply.d.ts +20 -0
  113. package/dist/io/last-apply.js +123 -0
  114. package/dist/io/last-apply.js.map +1 -0
  115. package/dist/io/lock.d.ts +11 -0
  116. package/dist/io/lock.js +33 -0
  117. package/dist/io/lock.js.map +1 -0
  118. package/dist/io/patch.d.ts +10 -0
  119. package/dist/io/patch.js +8 -0
  120. package/dist/io/patch.js.map +1 -0
  121. package/dist/io/restart-hints.d.ts +5 -0
  122. package/dist/io/restart-hints.js +59 -0
  123. package/dist/io/restart-hints.js.map +1 -0
  124. package/dist/io/three-state.d.ts +7 -0
  125. package/dist/io/three-state.js +20 -0
  126. package/dist/io/three-state.js.map +1 -0
  127. package/dist/io/validate-before-write.d.ts +12 -0
  128. package/dist/io/validate-before-write.js +23 -0
  129. package/dist/io/validate-before-write.js.map +1 -0
  130. package/dist/overrides/index.d.ts +2 -0
  131. package/dist/overrides/index.js +3 -0
  132. package/dist/overrides/index.js.map +1 -0
  133. package/dist/overrides/machines.d.ts +12 -0
  134. package/dist/overrides/machines.js +46 -0
  135. package/dist/overrides/machines.js.map +1 -0
  136. package/dist/overrides/merge.d.ts +6 -0
  137. package/dist/overrides/merge.js +173 -0
  138. package/dist/overrides/merge.js.map +1 -0
  139. package/dist/policies/duration.d.ts +5 -0
  140. package/dist/policies/duration.js +50 -0
  141. package/dist/policies/duration.js.map +1 -0
  142. package/dist/policies/extract.d.ts +50 -0
  143. package/dist/policies/extract.js +190 -0
  144. package/dist/policies/extract.js.map +1 -0
  145. package/dist/policies/index.d.ts +5 -0
  146. package/dist/policies/index.js +6 -0
  147. package/dist/policies/index.js.map +1 -0
  148. package/dist/policies/ledger-client.d.ts +39 -0
  149. package/dist/policies/ledger-client.js +378 -0
  150. package/dist/policies/ledger-client.js.map +1 -0
  151. package/dist/policies/requires.d.ts +44 -0
  152. package/dist/policies/requires.js +146 -0
  153. package/dist/policies/requires.js.map +1 -0
  154. package/dist/policies/timestamp.d.ts +14 -0
  155. package/dist/policies/timestamp.js +36 -0
  156. package/dist/policies/timestamp.js.map +1 -0
  157. package/dist/probes/mcp.d.ts +29 -0
  158. package/dist/probes/mcp.js +226 -0
  159. package/dist/probes/mcp.js.map +1 -0
  160. package/dist/probes/memory.d.ts +24 -0
  161. package/dist/probes/memory.js +89 -0
  162. package/dist/probes/memory.js.map +1 -0
  163. package/dist/runtime/index.d.ts +3 -0
  164. package/dist/runtime/index.js +4 -0
  165. package/dist/runtime/index.js.map +1 -0
  166. package/dist/runtime/intercept.d.ts +53 -0
  167. package/dist/runtime/intercept.js +181 -0
  168. package/dist/runtime/intercept.js.map +1 -0
  169. package/dist/runtime/ledger-record.d.ts +43 -0
  170. package/dist/runtime/ledger-record.js +239 -0
  171. package/dist/runtime/ledger-record.js.map +1 -0
  172. package/dist/runtime/session-id.d.ts +10 -0
  173. package/dist/runtime/session-id.js +37 -0
  174. package/dist/runtime/session-id.js.map +1 -0
  175. package/dist/schema/extract.d.ts +5 -0
  176. package/dist/schema/extract.js +23 -0
  177. package/dist/schema/extract.js.map +1 -0
  178. package/dist/schema/grounding.d.ts +65 -0
  179. package/dist/schema/grounding.js +21 -0
  180. package/dist/schema/grounding.js.map +1 -0
  181. package/dist/schema/hooks.d.ts +86 -0
  182. package/dist/schema/hooks.js +42 -0
  183. package/dist/schema/hooks.js.map +1 -0
  184. package/dist/schema/index.d.ts +961 -0
  185. package/dist/schema/index.js +55 -0
  186. package/dist/schema/index.js.map +1 -0
  187. package/dist/schema/memory.d.ts +131 -0
  188. package/dist/schema/memory.js +38 -0
  189. package/dist/schema/memory.js.map +1 -0
  190. package/dist/schema/policies.d.ts +412 -0
  191. package/dist/schema/policies.js +53 -0
  192. package/dist/schema/policies.js.map +1 -0
  193. package/dist/schema/requires.d.ts +115 -0
  194. package/dist/schema/requires.js +57 -0
  195. package/dist/schema/requires.js.map +1 -0
  196. package/dist/schema/tools.d.ts +283 -0
  197. package/dist/schema/tools.js +66 -0
  198. package/dist/schema/tools.js.map +1 -0
  199. 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":"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"}
@@ -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