@lannguyensi/harness 0.7.0 → 0.8.1

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 (111) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/README.md +56 -17
  3. package/dist/cli/apply/apply.d.ts +13 -0
  4. package/dist/cli/apply/apply.js +59 -3
  5. package/dist/cli/apply/apply.js.map +1 -1
  6. package/dist/cli/apply/generate-codex-config.d.ts +6 -0
  7. package/dist/cli/apply/generate-codex-config.js +149 -0
  8. package/dist/cli/apply/generate-codex-config.js.map +1 -0
  9. package/dist/cli/apply/generate-settings.d.ts +15 -1
  10. package/dist/cli/apply/generate-settings.js +16 -1
  11. package/dist/cli/apply/generate-settings.js.map +1 -1
  12. package/dist/cli/apply/index.d.ts +2 -1
  13. package/dist/cli/apply/index.js +2 -1
  14. package/dist/cli/apply/index.js.map +1 -1
  15. package/dist/cli/approve/understanding.d.ts +39 -0
  16. package/dist/cli/approve/understanding.js +122 -0
  17. package/dist/cli/approve/understanding.js.map +1 -0
  18. package/dist/cli/doctor/codex.d.ts +34 -0
  19. package/dist/cli/doctor/codex.js +331 -0
  20. package/dist/cli/doctor/codex.js.map +1 -0
  21. package/dist/cli/doctor/format.js +11 -0
  22. package/dist/cli/doctor/format.js.map +1 -1
  23. package/dist/cli/doctor/index.d.ts +13 -1
  24. package/dist/cli/doctor/index.js +19 -0
  25. package/dist/cli/doctor/index.js.map +1 -1
  26. package/dist/cli/doctor/types.d.ts +21 -1
  27. package/dist/cli/doctor/types.js +12 -1
  28. package/dist/cli/doctor/types.js.map +1 -1
  29. package/dist/cli/index.js +261 -2
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/cli/pack/add.d.ts +13 -0
  32. package/dist/cli/pack/add.js +71 -0
  33. package/dist/cli/pack/add.js.map +1 -0
  34. package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +30 -0
  35. package/dist/cli/pack/hook-codex-pre-tool-use.js +149 -0
  36. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -0
  37. package/dist/cli/pack/hook-codex-stop.d.ts +31 -0
  38. package/dist/cli/pack/hook-codex-stop.js +332 -0
  39. package/dist/cli/pack/hook-codex-stop.js.map +1 -0
  40. package/dist/cli/pack/hook-codex-user-prompt-submit.d.ts +18 -0
  41. package/dist/cli/pack/hook-codex-user-prompt-submit.js +92 -0
  42. package/dist/cli/pack/hook-codex-user-prompt-submit.js.map +1 -0
  43. package/dist/cli/pack/hook-pre-tool-use.d.ts +32 -0
  44. package/dist/cli/pack/hook-pre-tool-use.js +181 -0
  45. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -0
  46. package/dist/cli/pack/index.d.ts +4 -0
  47. package/dist/cli/pack/index.js +5 -0
  48. package/dist/cli/pack/index.js.map +1 -0
  49. package/dist/cli/pack/list.d.ts +10 -0
  50. package/dist/cli/pack/list.js +43 -0
  51. package/dist/cli/pack/list.js.map +1 -0
  52. package/dist/cli/pack/mutate.d.ts +14 -0
  53. package/dist/cli/pack/mutate.js +76 -0
  54. package/dist/cli/pack/mutate.js.map +1 -0
  55. package/dist/cli/pack/remove.d.ts +15 -0
  56. package/dist/cli/pack/remove.js +153 -0
  57. package/dist/cli/pack/remove.js.map +1 -0
  58. package/dist/cli/policy/intercept.js +24 -0
  59. package/dist/cli/policy/intercept.js.map +1 -1
  60. package/dist/cli/validate/checks.js +32 -0
  61. package/dist/cli/validate/checks.js.map +1 -1
  62. package/dist/policy-packs/builtin/permission-profiles.d.ts +11 -0
  63. package/dist/policy-packs/builtin/permission-profiles.js +74 -0
  64. package/dist/policy-packs/builtin/permission-profiles.js.map +1 -0
  65. package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +56 -0
  66. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +186 -0
  67. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -0
  68. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +15 -0
  69. package/dist/policy-packs/builtin/understanding-before-execution.js +254 -0
  70. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -0
  71. package/dist/policy-packs/expand.d.ts +4 -0
  72. package/dist/policy-packs/expand.js +90 -0
  73. package/dist/policy-packs/expand.js.map +1 -0
  74. package/dist/policy-packs/index.d.ts +5 -0
  75. package/dist/policy-packs/index.js +5 -0
  76. package/dist/policy-packs/index.js.map +1 -0
  77. package/dist/policy-packs/permission-translator.d.ts +9 -0
  78. package/dist/policy-packs/permission-translator.js +76 -0
  79. package/dist/policy-packs/permission-translator.js.map +1 -0
  80. package/dist/policy-packs/registry.d.ts +11 -0
  81. package/dist/policy-packs/registry.js +20 -0
  82. package/dist/policy-packs/registry.js.map +1 -0
  83. package/dist/policy-packs/runtime.d.ts +8 -0
  84. package/dist/policy-packs/runtime.js +30 -0
  85. package/dist/policy-packs/runtime.js.map +1 -0
  86. package/dist/policy-packs/source.d.ts +6 -0
  87. package/dist/policy-packs/source.js +10 -0
  88. package/dist/policy-packs/source.js.map +1 -0
  89. package/dist/policy-packs/types.d.ts +41 -0
  90. package/dist/policy-packs/types.js +11 -0
  91. package/dist/policy-packs/types.js.map +1 -0
  92. package/dist/runtime/index.d.ts +1 -0
  93. package/dist/runtime/index.js +1 -0
  94. package/dist/runtime/index.js.map +1 -1
  95. package/dist/runtime/intercept.d.ts +20 -1
  96. package/dist/runtime/intercept.js +18 -6
  97. package/dist/runtime/intercept.js.map +1 -1
  98. package/dist/runtime/ledger-add.d.ts +16 -0
  99. package/dist/runtime/ledger-add.js +139 -0
  100. package/dist/runtime/ledger-add.js.map +1 -0
  101. package/dist/schema/index.d.ts +1485 -10
  102. package/dist/schema/index.js +6 -0
  103. package/dist/schema/index.js.map +1 -1
  104. package/dist/schema/permission-profiles.d.ts +2161 -0
  105. package/dist/schema/permission-profiles.js +60 -0
  106. package/dist/schema/permission-profiles.js.map +1 -0
  107. package/dist/schema/policy-packs.d.ts +52 -0
  108. package/dist/schema/policy-packs.js +35 -0
  109. package/dist/schema/policy-packs.js.map +1 -0
  110. package/dist/schema/tools.d.ts +8 -8
  111. package/package.json +1 -1
@@ -0,0 +1,32 @@
1
+ import { type LedgerEntry } from "../../policies/index.js";
2
+ import { type ApprovalCheckResult } from "../../policy-packs/builtin/understanding-before-execution-runtime.js";
3
+ import type { Manifest } from "../../schema/index.js";
4
+ import { type LoaderOptions } from "../loader.js";
5
+ export interface PackHookPreToolUseOptions extends LoaderOptions {
6
+ /** Pack name to evaluate. Defaults to understanding-before-execution. */
7
+ pack?: string;
8
+ /** Override report directory (test injection). */
9
+ reportsDir?: string;
10
+ /** Override timeout per ledger call. */
11
+ ledgerTimeoutMs?: number;
12
+ /** Defaults to process.stdin. */
13
+ stdin?: NodeJS.ReadableStream;
14
+ /** Defaults to process.stdout. */
15
+ stdout?: NodeJS.WritableStream;
16
+ /** Defaults to process.stderr. */
17
+ stderr?: NodeJS.WritableStream;
18
+ /** Inject an alternate manifest (test). */
19
+ manifest?: Manifest;
20
+ /** Inject a fake ledger query (test). */
21
+ ledgerQuery?: (sessionId: string) => Promise<LedgerEntry[] | {
22
+ degraded: string;
23
+ }>;
24
+ }
25
+ export interface PackHookPreToolUseResult {
26
+ exitCode: number;
27
+ blocked: boolean;
28
+ approvalCheck: ApprovalCheckResult;
29
+ /** Diagnostic line emitted to stderr (always; even on allow). */
30
+ diagnostic: string;
31
+ }
32
+ export declare function runPackHookPreToolUseCli(opts?: PackHookPreToolUseOptions): Promise<PackHookPreToolUseResult>;
@@ -0,0 +1,181 @@
1
+ // Phase 6 #4 — `harness pack hook pre-tool-use [--pack <name>]` runtime verb.
2
+ //
3
+ // PreToolUse blocker for pack-driven gates. Wired by the
4
+ // understanding-before-execution pack's hook contribution; receives the
5
+ // Claude Code event JSON on stdin, consults the two approval sources
6
+ // (evidence ledger via grounding-mcp, persisted JSON report under
7
+ // `.understanding-gate/reports/`), emits a `{decision: "block"}` JSON to
8
+ // stdout when neither source has approved.
9
+ //
10
+ // Why a new CLI verb (vs reusing `harness policy intercept`): the
11
+ // existing intercept layer evaluates `policies[]` against `requires`,
12
+ // which is purely ledger-based. The Understanding Gate has a second
13
+ // source-of-truth (the persisted JSON report), and bolting a fallback
14
+ // into the requires evaluator would leak pack-specific semantics into
15
+ // the generic policy layer. This verb lives next to the pack instead.
16
+ //
17
+ // Failure mode: any error in load / parse / ledger / report scan
18
+ // resolves to ALLOW (exit 0, silent). The Understanding Gate is opt-in;
19
+ // turning a bug in this code into a session-wide tool block would be
20
+ // hostile. The npm package's own standalone blocker still runs as a
21
+ // secondary safety net for solo users, and `harness explain --trace`
22
+ // (Phase 4 #6) surfaces the runtime audit trail when configured.
23
+ import { queryLedgerByTag, } from "../../policies/index.js";
24
+ import { checkPersistedReport, defaultReportsDir, matchLedgerEntries, } from "../../policy-packs/builtin/understanding-before-execution-runtime.js";
25
+ import { loadManifest } from "../loader.js";
26
+ const PACK_NAME = "understanding-before-execution";
27
+ async function readStdin(stream) {
28
+ return new Promise((resolve, reject) => {
29
+ let data = "";
30
+ stream.setEncoding("utf8");
31
+ stream.on("data", (chunk) => {
32
+ data += chunk;
33
+ });
34
+ stream.on("end", () => resolve(data));
35
+ stream.on("error", (err) => reject(err));
36
+ });
37
+ }
38
+ function findGroundingMcp(manifest) {
39
+ return manifest.tools.mcp.find((m) => m.name === "grounding-mcp") ?? null;
40
+ }
41
+ function blockJson(toolName, reason) {
42
+ return JSON.stringify({
43
+ decision: "block",
44
+ reason: `Understanding Gate: ${reason}. Tool: ${toolName}. Run \`harness approve understanding\` once you have produced and confirmed an Understanding Report.`,
45
+ });
46
+ }
47
+ async function checkLedger(manifest, sessionId, opts) {
48
+ if (opts.ledgerQuery) {
49
+ const result = await opts.ledgerQuery(sessionId);
50
+ if ("degraded" in result) {
51
+ return { matched: false, detail: `ledger degraded (${result.degraded})` };
52
+ }
53
+ return matchLedgerEntries(result, sessionId);
54
+ }
55
+ const server = findGroundingMcp(manifest);
56
+ if (!server) {
57
+ return { matched: false, detail: "grounding-mcp not declared in manifest" };
58
+ }
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
+ const result = await queryLedgerByTag({
65
+ mcpCommand: command,
66
+ ...(env && { mcpEnv: env }),
67
+ sessionId,
68
+ timeoutMs,
69
+ });
70
+ if (result.kind === "degraded") {
71
+ return { matched: false, detail: `ledger degraded (${result.reason})` };
72
+ }
73
+ return matchLedgerEntries(result.entries, sessionId);
74
+ }
75
+ export async function runPackHookPreToolUseCli(opts = {}) {
76
+ const stdin = opts.stdin ?? process.stdin;
77
+ const stdout = opts.stdout ?? process.stdout;
78
+ const stderr = opts.stderr ?? process.stderr;
79
+ const packName = opts.pack ?? PACK_NAME;
80
+ // Read stdin defensively. Bad JSON falls through to allow (matches
81
+ // policy intercept's failure mode).
82
+ const raw = await readStdin(stdin);
83
+ let event = {};
84
+ try {
85
+ event = JSON.parse(raw.trim() || "{}");
86
+ }
87
+ catch {
88
+ /* allow on malformed input */
89
+ }
90
+ const sessionId = (typeof event.session_id === "string" ? event.session_id : undefined) ??
91
+ process.env.CLAUDE_SESSION_ID ??
92
+ "";
93
+ const toolName = typeof event.tool_name === "string" ? event.tool_name : "(unknown)";
94
+ // Load manifest (or use injection). Bail to allow on any failure so a
95
+ // missing harness install never bricks the session.
96
+ let manifest;
97
+ try {
98
+ manifest = opts.manifest ?? loadManifest(opts).manifest;
99
+ }
100
+ catch (err) {
101
+ const diagnostic = `harness pack hook: manifest load failed (${err.message}), allowing.`;
102
+ stderr.write(`${diagnostic}\n`);
103
+ return {
104
+ exitCode: 0,
105
+ blocked: false,
106
+ approvalCheck: { approved: true, source: "none", detail: diagnostic },
107
+ diagnostic,
108
+ };
109
+ }
110
+ // Confirm the pack is enabled. A pack that isn't even declared in the
111
+ // manifest means the operator wired this hook directly into
112
+ // settings.json without `harness apply` — odd but harmless; allow.
113
+ const declared = manifest.policy_packs.find((p) => p.name === packName);
114
+ if (!declared) {
115
+ const diagnostic = `harness pack hook: pack "${packName}" not declared in manifest, allowing.`;
116
+ stderr.write(`${diagnostic}\n`);
117
+ return {
118
+ exitCode: 0,
119
+ blocked: false,
120
+ approvalCheck: { approved: true, source: "none", detail: diagnostic },
121
+ diagnostic,
122
+ };
123
+ }
124
+ if (!declared.enabled) {
125
+ const diagnostic = `harness pack hook: pack "${packName}" is enabled:false, allowing.`;
126
+ stderr.write(`${diagnostic}\n`);
127
+ return {
128
+ exitCode: 0,
129
+ blocked: false,
130
+ approvalCheck: { approved: true, source: "none", detail: diagnostic },
131
+ diagnostic,
132
+ };
133
+ }
134
+ if (sessionId === "") {
135
+ const diagnostic = 'harness pack hook: no session_id resolvable from input or $CLAUDE_SESSION_ID, allowing.';
136
+ stderr.write(`${diagnostic}\n`);
137
+ return {
138
+ exitCode: 0,
139
+ blocked: false,
140
+ approvalCheck: { approved: true, source: "none", detail: diagnostic },
141
+ diagnostic,
142
+ };
143
+ }
144
+ // Source 1: ledger.
145
+ const ledger = await checkLedger(manifest, sessionId, opts);
146
+ if (ledger.matched) {
147
+ const diagnostic = `harness pack hook: ${ledger.detail}, allowing.`;
148
+ stderr.write(`${diagnostic}\n`);
149
+ return {
150
+ exitCode: 0,
151
+ blocked: false,
152
+ approvalCheck: { approved: true, source: "ledger", detail: ledger.detail },
153
+ diagnostic,
154
+ };
155
+ }
156
+ // Source 2: persisted report.
157
+ const reportsDir = opts.reportsDir ?? defaultReportsDir();
158
+ const report = checkPersistedReport(reportsDir, sessionId);
159
+ if (report.approved) {
160
+ const diagnostic = `harness pack hook: ${report.detail}, allowing.`;
161
+ stderr.write(`${diagnostic}\n`);
162
+ return {
163
+ exitCode: 0,
164
+ blocked: false,
165
+ approvalCheck: { approved: true, source: "persisted-report", detail: report.detail },
166
+ diagnostic,
167
+ };
168
+ }
169
+ // Neither source approved.
170
+ const reason = `${ledger.detail}; ${report.detail}`;
171
+ const diagnostic = `harness pack hook: BLOCK — ${reason}`;
172
+ stderr.write(`${diagnostic}\n`);
173
+ stdout.write(`${blockJson(toolName, "no approved Understanding Report for this session")}\n`);
174
+ return {
175
+ exitCode: 0,
176
+ blocked: true,
177
+ approvalCheck: { approved: false, source: "none", detail: reason },
178
+ diagnostic,
179
+ };
180
+ }
181
+ //# sourceMappingURL=hook-pre-tool-use.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-pre-tool-use.js","sourceRoot":"","sources":["../../../src/cli/pack/hook-pre-tool-use.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { packAdd, type PackAddOptions, type PackAddResult } from "./add.js";
2
+ export { packRemove, type PackRemoveOptions, type PackRemoveResult } from "./remove.js";
3
+ export { packList, type PackListOptions, type PackListResult } from "./list.js";
4
+ export { applyPackAdd, applyPackRemove, planPackRemove, type PackAddEntry, type PackRemovePlan, } from "./mutate.js";
@@ -0,0 +1,5 @@
1
+ export { packAdd } from "./add.js";
2
+ export { packRemove } from "./remove.js";
3
+ export { packList } from "./list.js";
4
+ export { applyPackAdd, applyPackRemove, planPackRemove, } from "./mutate.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/pack/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAA2C,MAAM,UAAU,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAiD,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,QAAQ,EAA6C,MAAM,WAAW,CAAC;AAChF,OAAO,EACL,YAAY,EACZ,eAAe,EACf,cAAc,GAGf,MAAM,aAAa,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { type LoaderOptions } from "../loader.js";
2
+ export interface PackListOptions extends LoaderOptions {
3
+ enabledOnly?: boolean;
4
+ json?: boolean;
5
+ }
6
+ export interface PackListResult {
7
+ output: string;
8
+ rows: Record<string, unknown>[];
9
+ }
10
+ export declare function packList(opts?: PackListOptions): PackListResult;
@@ -0,0 +1,43 @@
1
+ // `harness pack list` — flat read of policy_packs[] for shell consumption.
2
+ //
3
+ // Output rows match the existing `harness list <category>` shape so the
4
+ // rendering helper (renderText vs JSON) stays consistent across categories.
5
+ // Today the columns are: name, source, enabled, mode, description.
6
+ import { loadManifest } from "../loader.js";
7
+ function modeOf(pack) {
8
+ const raw = pack.config["mode"];
9
+ return typeof raw === "string" ? raw : "";
10
+ }
11
+ function buildRows(manifest, opts) {
12
+ let entries = manifest.policy_packs;
13
+ if (opts.enabledOnly)
14
+ entries = entries.filter((p) => p.enabled);
15
+ return entries.map((p) => ({
16
+ name: p.name,
17
+ source: p.source,
18
+ enabled: p.enabled,
19
+ mode: modeOf(p),
20
+ description: p.description ?? "",
21
+ }));
22
+ }
23
+ function renderText(rows) {
24
+ if (rows.length === 0)
25
+ return "(no entries)\n";
26
+ const headers = Object.keys(rows[0]);
27
+ const widths = headers.map((h) => Math.max(h.length, ...rows.map((r) => String(r[h] ?? "").length)));
28
+ const pad = (s, w) => s.padEnd(w, " ");
29
+ const lines = [];
30
+ lines.push(headers.map((h, i) => pad(h, widths[i])).join(" "));
31
+ lines.push(headers.map((_, i) => "-".repeat(widths[i])).join(" "));
32
+ for (const row of rows) {
33
+ lines.push(headers.map((h, i) => pad(String(row[h] ?? ""), widths[i])).join(" "));
34
+ }
35
+ return `${lines.join("\n")}\n`;
36
+ }
37
+ export function packList(opts = {}) {
38
+ const { manifest } = loadManifest(opts);
39
+ const rows = buildRows(manifest, opts);
40
+ const output = opts.json ? `${JSON.stringify(rows, null, 2)}\n` : renderText(rows);
41
+ return { output, rows };
42
+ }
43
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/cli/pack/list.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface PackAddEntry {
2
+ name: string;
3
+ source?: string;
4
+ enabled?: boolean;
5
+ description?: string;
6
+ config?: Record<string, unknown>;
7
+ }
8
+ export interface PackRemovePlan {
9
+ found: boolean;
10
+ availableNames: string[];
11
+ }
12
+ export declare function applyPackAdd(yamlText: string, entry: PackAddEntry): string;
13
+ export declare function planPackRemove(yamlText: string, name: string): PackRemovePlan;
14
+ export declare function applyPackRemove(yamlText: string, name: string): string;
@@ -0,0 +1,76 @@
1
+ // YAML-document mutators for `harness pack` add/remove. Pure functions over
2
+ // the on-disk `harness.yaml` text, mirroring src/cli/add/mutate.ts and
3
+ // src/cli/remove/mutate.ts.
4
+ import { isMap, isSeq, parseDocument } from "yaml";
5
+ export function applyPackAdd(yamlText, entry) {
6
+ const doc = parseDocument(yamlText);
7
+ // Build a YAML-friendly plain object. Only emit fields the caller set so the
8
+ // resulting manifest stays minimal (the schema fills in defaults at parse
9
+ // time).
10
+ const plain = { name: entry.name };
11
+ if (entry.source !== undefined)
12
+ plain["source"] = entry.source;
13
+ if (entry.enabled !== undefined)
14
+ plain["enabled"] = entry.enabled;
15
+ if (entry.description !== undefined)
16
+ plain["description"] = entry.description;
17
+ if (entry.config !== undefined && Object.keys(entry.config).length > 0) {
18
+ plain["config"] = entry.config;
19
+ }
20
+ const node = doc.getIn(["policy_packs"]);
21
+ if (node === undefined || node === null) {
22
+ // setIn with `[plain]` materialises a YAML Seq containing the entry.
23
+ // Doing setIn(path, []) followed by getIn(path) returns the JS array
24
+ // unchanged — same footgun src/cli/add/mutate.ts already navigates
25
+ // around. Match that pattern: create-with-entry in one step.
26
+ doc.setIn(["policy_packs"], [plain]);
27
+ }
28
+ else if (isSeq(node)) {
29
+ node.add(plain);
30
+ }
31
+ else {
32
+ throw new Error(`expected a YAML sequence at policy_packs, got ${typeof node}`);
33
+ }
34
+ return doc.toString({ flowCollectionPadding: false, lineWidth: 0 });
35
+ }
36
+ export function planPackRemove(yamlText, name) {
37
+ const doc = parseDocument(yamlText);
38
+ const node = doc.getIn(["policy_packs"]);
39
+ if (!isSeq(node)) {
40
+ return { found: false, availableNames: [] };
41
+ }
42
+ const names = [];
43
+ let found = false;
44
+ for (const item of node.items) {
45
+ if (!isMap(item))
46
+ continue;
47
+ const itemName = item.get("name");
48
+ if (typeof itemName !== "string")
49
+ continue;
50
+ names.push(itemName);
51
+ if (itemName === name)
52
+ found = true;
53
+ }
54
+ return { found, availableNames: names };
55
+ }
56
+ export function applyPackRemove(yamlText, name) {
57
+ const doc = parseDocument(yamlText);
58
+ const node = doc.getIn(["policy_packs"]);
59
+ if (!isSeq(node))
60
+ return yamlText;
61
+ for (let i = 0; i < node.items.length; i++) {
62
+ const item = node.items[i];
63
+ if (!isMap(item))
64
+ continue;
65
+ const itemName = item.get("name");
66
+ if (typeof itemName === "string" && itemName === name) {
67
+ node.delete(i);
68
+ // If the array became empty, leave it as `policy_packs: []` rather
69
+ // than removing the key entirely. Keeping the key surface visible
70
+ // helps the next `harness pack add` round-trip cleanly.
71
+ return doc.toString({ flowCollectionPadding: false, lineWidth: 0 });
72
+ }
73
+ }
74
+ return yamlText;
75
+ }
76
+ //# sourceMappingURL=mutate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutate.js","sourceRoot":"","sources":["../../../src/cli/pack/mutate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface PackRemoveOptions {
2
+ configPath?: string;
3
+ homeDir?: string;
4
+ dryRun?: boolean;
5
+ force?: boolean;
6
+ }
7
+ export interface PackRemoveResult {
8
+ path: string;
9
+ name: string;
10
+ diff: string;
11
+ applied: boolean;
12
+ /** Pack files that were (or would be) deleted because of --force. */
13
+ cleanedFiles: string[];
14
+ }
15
+ export declare function packRemove(name: string, opts?: PackRemoveOptions): Promise<PackRemoveResult>;
@@ -0,0 +1,153 @@
1
+ // `harness pack remove <name>` — managed remove from policy_packs[].
2
+ //
3
+ // Reference-checked: refuses (without --force) when `.last-apply` records
4
+ // files under `policy-packs/<name>/`, on the theory that `harness apply`
5
+ // has run with this pack present and removing it without telling apply
6
+ // would leave orphan files in `harness.generated/`. With --force, the
7
+ // manifest entry is removed AND the orphan files are deleted AND the
8
+ // `.last-apply` file entries are pruned, so the next `harness apply` is
9
+ // a clean no-op.
10
+ import * as fs from "node:fs";
11
+ import * as os from "node:os";
12
+ import * as path from "node:path";
13
+ import { parse as parseYaml } from "yaml";
14
+ import { atomicWriteFile } from "../../io/atomic-write.js";
15
+ import { writeLastApply } from "../../io/last-apply.js";
16
+ import { LAST_APPLY_BASENAME, readLastApply, } from "../../io/last-apply.js";
17
+ import { withFileLock } from "../../io/lock.js";
18
+ import { unifiedDiff } from "../../io/patch.js";
19
+ import { formatValidationErrors, validateBeforeWrite, } from "../../io/validate-before-write.js";
20
+ import { EX_FAIL, EX_NOINPUT, HarnessExitError } from "../exit-codes.js";
21
+ import { applyPackRemove, planPackRemove } from "./mutate.js";
22
+ const DEFAULT_BASENAME = "harness.yaml";
23
+ const LOCK_BASENAME = ".harness.lock";
24
+ const GENERATED_DIRNAME = "harness.generated";
25
+ function resolveTargetPath(opts) {
26
+ if (opts.configPath)
27
+ return path.resolve(opts.configPath);
28
+ return path.join(opts.homeDir ?? path.join(os.homedir(), ".claude"), DEFAULT_BASENAME);
29
+ }
30
+ function resolveGeneratedDir(opts, manifestPath) {
31
+ if (opts.homeDir !== undefined)
32
+ return path.join(opts.homeDir, GENERATED_DIRNAME);
33
+ return path.join(path.dirname(manifestPath), GENERATED_DIRNAME);
34
+ }
35
+ function packFileKeys(record, packName) {
36
+ if (!record)
37
+ return [];
38
+ const prefix = `policy-packs/${packName}/`;
39
+ return Object.keys(record.files)
40
+ .filter((k) => k.startsWith(prefix))
41
+ .sort();
42
+ }
43
+ function pruneRecord(record, packName) {
44
+ const out = {
45
+ files: {},
46
+ ...(record.manifest !== undefined ? { manifest: record.manifest } : {}),
47
+ ...(record.memoryDirs !== undefined ? { memoryDirs: record.memoryDirs } : {}),
48
+ };
49
+ const prefix = `policy-packs/${packName}/`;
50
+ for (const [key, entry] of Object.entries(record.files)) {
51
+ if (!key.startsWith(prefix))
52
+ out.files[key] = entry;
53
+ }
54
+ return out;
55
+ }
56
+ function formatNameList(names) {
57
+ if (names.length === 0)
58
+ return "(none declared)";
59
+ return names.map((n) => ` - ${n}`).join("\n");
60
+ }
61
+ // Belt-and-braces defense against a path-traversal `name` reaching the
62
+ // filesystem cleanup. The schema regex on PolicyPackSchema.name catches
63
+ // this at parseManifest time, but planPackRemove reads the YAML
64
+ // directly (not via the schema), so a malformed manifest could still
65
+ // surface a bad name here. Refuse rather than rmSync into the void.
66
+ const SAFE_PACK_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
67
+ export async function packRemove(name, opts = {}) {
68
+ const target = resolveTargetPath(opts);
69
+ if (!fs.existsSync(target)) {
70
+ throw new HarnessExitError(`harness manifest not found at ${target}; run \`harness init\` first`, EX_NOINPUT);
71
+ }
72
+ if (!SAFE_PACK_NAME_RE.test(name)) {
73
+ throw new HarnessExitError(`policy_pack name ${JSON.stringify(name)} contains path separators or other unsafe characters; refusing to operate on it. Allowed: [A-Za-z0-9._-], leading char alphanumeric.`, EX_FAIL);
74
+ }
75
+ const original = fs.readFileSync(target, "utf8");
76
+ const plan = planPackRemove(original, name);
77
+ if (!plan.found) {
78
+ throw new HarnessExitError(`policy_packs entry "${name}" not found. Available entries:\n${formatNameList(plan.availableNames)}`, EX_FAIL);
79
+ }
80
+ const generatedDir = resolveGeneratedDir(opts, target);
81
+ const lastApply = readLastApply(generatedDir);
82
+ const trackedFiles = packFileKeys(lastApply, name);
83
+ if (trackedFiles.length > 0 && !opts.force) {
84
+ const list = trackedFiles.map((f) => ` - ${f}`).join("\n");
85
+ throw new HarnessExitError(`pack "${name}" has applied state present in ${LAST_APPLY_BASENAME}:\n${list}\n` +
86
+ `Pass --force to remove the manifest entry and clean up these generated files. ` +
87
+ `(Without cleanup, a subsequent \`harness apply\` would not delete them.)`, EX_FAIL);
88
+ }
89
+ const proposed = applyPackRemove(original, name);
90
+ const diff = unifiedDiff({
91
+ fileName: path.basename(target),
92
+ oldText: original,
93
+ newText: proposed,
94
+ oldHeader: "current",
95
+ newHeader: "proposed",
96
+ });
97
+ const schemaResult = validateBeforeWrite(parseYaml(proposed));
98
+ if (!schemaResult.ok) {
99
+ throw new HarnessExitError(`proposed manifest fails schema validation:\n${formatValidationErrors(schemaResult.errors)}`, EX_FAIL);
100
+ }
101
+ if (opts.dryRun) {
102
+ // On dry-run, surface what --force WOULD clean up so the user can
103
+ // sanity-check the blast radius before committing.
104
+ return {
105
+ path: target,
106
+ name,
107
+ diff,
108
+ applied: false,
109
+ cleanedFiles: opts.force ? trackedFiles : [],
110
+ };
111
+ }
112
+ const lockPath = path.join(path.dirname(target), LOCK_BASENAME);
113
+ const cleanedFiles = [];
114
+ await withFileLock(lockPath, () => {
115
+ const current = fs.readFileSync(target, "utf8");
116
+ const next = applyPackRemove(current, name);
117
+ const recheck = validateBeforeWrite(parseYaml(next));
118
+ if (!recheck.ok) {
119
+ throw new HarnessExitError(`proposed manifest fails schema validation after lock acquisition:\n${formatValidationErrors(recheck.errors)}`, EX_FAIL);
120
+ }
121
+ atomicWriteFile(target, next);
122
+ // Best-effort cleanup under --force. We do this AFTER the manifest
123
+ // write has succeeded so a partial cleanup never leaves the manifest
124
+ // in a confusing half-state. Each fs operation is wrapped: a missing
125
+ // file is a no-op, not a failure (the user may have deleted it
126
+ // manually since the last apply).
127
+ if (opts.force && trackedFiles.length > 0) {
128
+ const packDir = path.join(generatedDir, "policy-packs", name);
129
+ try {
130
+ fs.rmSync(packDir, { recursive: true, force: true });
131
+ }
132
+ catch {
133
+ // ignore — best-effort
134
+ }
135
+ // Update .last-apply so the next `harness apply` no-ops cleanly
136
+ // instead of detecting the now-missing files as drift.
137
+ const fresh = readLastApply(generatedDir);
138
+ if (fresh) {
139
+ const pruned = pruneRecord(fresh, name);
140
+ writeLastApply(generatedDir, pruned);
141
+ }
142
+ cleanedFiles.push(...trackedFiles);
143
+ }
144
+ });
145
+ return {
146
+ path: target,
147
+ name,
148
+ diff,
149
+ applied: true,
150
+ cleanedFiles,
151
+ };
152
+ }
153
+ //# sourceMappingURL=remove.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove.js","sourceRoot":"","sources":["../../../src/cli/pack/remove.ts"],"names":[],"mappings":"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"}
@@ -156,6 +156,17 @@ export async function runInterceptCli(opts = {}) {
156
156
  if (result.blockJson) {
157
157
  stdout.write(`${JSON.stringify(result.blockJson)}\n`);
158
158
  }
159
+ // No-match diagnostic. Always (not gated on --verbose) when the
160
+ // manifest has at least one policy but none of them matched this
161
+ // event. Catches the common debug footgun where an operator probes
162
+ // `harness policy intercept` by hand and forgets `hook_event_name`,
163
+ // making the engine return exit 0 + empty stdout. The probe then
164
+ // looks like "policy did not load", when in fact the trigger filter
165
+ // simply rejected the input. Emits to stderr so the Claude Code
166
+ // hook contract on stdout is preserved.
167
+ if (result.decisions.length === 0 && manifest.policies.length > 0) {
168
+ stderr.write(formatNoMatchHint(event, manifest));
169
+ }
159
170
  if (verbose) {
160
171
  for (const decision of result.decisions) {
161
172
  if (decision.outcome === "allow")
@@ -169,4 +180,17 @@ export async function runInterceptCli(opts = {}) {
169
180
  blocked: result.blockJson !== null,
170
181
  };
171
182
  }
183
+ function formatNoMatchHint(event, manifest) {
184
+ const observedEvent = typeof event.hook_event_name === "string" && event.hook_event_name.length > 0
185
+ ? `"${event.hook_event_name}"`
186
+ : "(missing)";
187
+ const observedTool = typeof event.tool_name === "string" && event.tool_name.length > 0
188
+ ? `"${event.tool_name}"`
189
+ : "(missing)";
190
+ const registeredEvents = Array.from(new Set(manifest.policies.map((p) => p.trigger.event))).sort();
191
+ return (`harness policy intercept: no policy matched event ` +
192
+ `hook_event_name=${observedEvent} tool_name=${observedTool} ` +
193
+ `(registered policy events: ${registeredEvents.join(", ")}). ` +
194
+ `If probing by hand, ensure stdin includes hook_event_name (e.g. "PreToolUse" for tool gates).\n`);
195
+ }
172
196
  //# sourceMappingURL=intercept.js.map