@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.
- package/CHANGELOG.md +229 -0
- package/README.md +56 -17
- package/dist/cli/apply/apply.d.ts +13 -0
- package/dist/cli/apply/apply.js +59 -3
- package/dist/cli/apply/apply.js.map +1 -1
- package/dist/cli/apply/generate-codex-config.d.ts +6 -0
- package/dist/cli/apply/generate-codex-config.js +149 -0
- package/dist/cli/apply/generate-codex-config.js.map +1 -0
- package/dist/cli/apply/generate-settings.d.ts +15 -1
- package/dist/cli/apply/generate-settings.js +16 -1
- package/dist/cli/apply/generate-settings.js.map +1 -1
- package/dist/cli/apply/index.d.ts +2 -1
- package/dist/cli/apply/index.js +2 -1
- package/dist/cli/apply/index.js.map +1 -1
- package/dist/cli/approve/understanding.d.ts +39 -0
- package/dist/cli/approve/understanding.js +122 -0
- package/dist/cli/approve/understanding.js.map +1 -0
- package/dist/cli/doctor/codex.d.ts +34 -0
- package/dist/cli/doctor/codex.js +331 -0
- package/dist/cli/doctor/codex.js.map +1 -0
- package/dist/cli/doctor/format.js +11 -0
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/doctor/index.d.ts +13 -1
- package/dist/cli/doctor/index.js +19 -0
- package/dist/cli/doctor/index.js.map +1 -1
- package/dist/cli/doctor/types.d.ts +21 -1
- package/dist/cli/doctor/types.js +12 -1
- package/dist/cli/doctor/types.js.map +1 -1
- package/dist/cli/index.js +261 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/pack/add.d.ts +13 -0
- package/dist/cli/pack/add.js +71 -0
- package/dist/cli/pack/add.js.map +1 -0
- package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +30 -0
- package/dist/cli/pack/hook-codex-pre-tool-use.js +149 -0
- package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -0
- package/dist/cli/pack/hook-codex-stop.d.ts +31 -0
- package/dist/cli/pack/hook-codex-stop.js +332 -0
- package/dist/cli/pack/hook-codex-stop.js.map +1 -0
- package/dist/cli/pack/hook-codex-user-prompt-submit.d.ts +18 -0
- package/dist/cli/pack/hook-codex-user-prompt-submit.js +92 -0
- package/dist/cli/pack/hook-codex-user-prompt-submit.js.map +1 -0
- package/dist/cli/pack/hook-pre-tool-use.d.ts +32 -0
- package/dist/cli/pack/hook-pre-tool-use.js +181 -0
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -0
- package/dist/cli/pack/index.d.ts +4 -0
- package/dist/cli/pack/index.js +5 -0
- package/dist/cli/pack/index.js.map +1 -0
- package/dist/cli/pack/list.d.ts +10 -0
- package/dist/cli/pack/list.js +43 -0
- package/dist/cli/pack/list.js.map +1 -0
- package/dist/cli/pack/mutate.d.ts +14 -0
- package/dist/cli/pack/mutate.js +76 -0
- package/dist/cli/pack/mutate.js.map +1 -0
- package/dist/cli/pack/remove.d.ts +15 -0
- package/dist/cli/pack/remove.js +153 -0
- package/dist/cli/pack/remove.js.map +1 -0
- package/dist/cli/policy/intercept.js +24 -0
- package/dist/cli/policy/intercept.js.map +1 -1
- package/dist/cli/validate/checks.js +32 -0
- package/dist/cli/validate/checks.js.map +1 -1
- package/dist/policy-packs/builtin/permission-profiles.d.ts +11 -0
- package/dist/policy-packs/builtin/permission-profiles.js +74 -0
- package/dist/policy-packs/builtin/permission-profiles.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +56 -0
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +186 -0
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +15 -0
- package/dist/policy-packs/builtin/understanding-before-execution.js +254 -0
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -0
- package/dist/policy-packs/expand.d.ts +4 -0
- package/dist/policy-packs/expand.js +90 -0
- package/dist/policy-packs/expand.js.map +1 -0
- package/dist/policy-packs/index.d.ts +5 -0
- package/dist/policy-packs/index.js +5 -0
- package/dist/policy-packs/index.js.map +1 -0
- package/dist/policy-packs/permission-translator.d.ts +9 -0
- package/dist/policy-packs/permission-translator.js +76 -0
- package/dist/policy-packs/permission-translator.js.map +1 -0
- package/dist/policy-packs/registry.d.ts +11 -0
- package/dist/policy-packs/registry.js +20 -0
- package/dist/policy-packs/registry.js.map +1 -0
- package/dist/policy-packs/runtime.d.ts +8 -0
- package/dist/policy-packs/runtime.js +30 -0
- package/dist/policy-packs/runtime.js.map +1 -0
- package/dist/policy-packs/source.d.ts +6 -0
- package/dist/policy-packs/source.js +10 -0
- package/dist/policy-packs/source.js.map +1 -0
- package/dist/policy-packs/types.d.ts +41 -0
- package/dist/policy-packs/types.js +11 -0
- package/dist/policy-packs/types.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/intercept.d.ts +20 -1
- package/dist/runtime/intercept.js +18 -6
- package/dist/runtime/intercept.js.map +1 -1
- package/dist/runtime/ledger-add.d.ts +16 -0
- package/dist/runtime/ledger-add.js +139 -0
- package/dist/runtime/ledger-add.js.map +1 -0
- package/dist/schema/index.d.ts +1485 -10
- package/dist/schema/index.js +6 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/permission-profiles.d.ts +2161 -0
- package/dist/schema/permission-profiles.js +60 -0
- package/dist/schema/permission-profiles.js.map +1 -0
- package/dist/schema/policy-packs.d.ts +52 -0
- package/dist/schema/policy-packs.js +35 -0
- package/dist/schema/policy-packs.js.map +1 -0
- package/dist/schema/tools.d.ts +8 -8
- 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 @@
|
|
|
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
|