@deftai/directive 0.88.0 → 0.89.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.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(argv?: string[]): number;
3
+ export default main;
4
+ //# sourceMappingURL=authz.d.ts.map
package/dist/authz.js ADDED
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Authz CLI (#2944 Wave 1 + #1095 Wave 4): human-origin grants + UAT lease +
4
+ * AFK closed-verb templates (mint via mintHumanOriginGrant only).
5
+ *
6
+ * deft authz:show
7
+ * deft authz:uat-start -- --campaign <id> [--actor <name>] [--note <text>]
8
+ * deft authz:uat-suspend
9
+ * deft authz:grant -- --operations edit,push --surfaces 'src/**' --cohort <id> ...
10
+ * deft authz:grant -- --template release-publish --target 0.30.0
11
+ * deft authz:grant -- --template finish-loop
12
+ * deft authz:revoke -- <grant-id>
13
+ */
14
+ import { AFK_TEMPLATE_NAMES, AUTHZ_OPERATIONS, CLOSED_VERB_TEMPLATE_NAMES, FINISH_LOOP_TEMPLATE_NAME, isAfkTemplateName, isClosedVerbTemplateName, isFinishLoopTemplateName, mintAfkTemplateGrant, mintHumanOriginGrant, revokeGrant, showAuthzSnapshot, startUatLease, suspendUatLease, } from "@deftai/directive-core/authz";
15
+ function parseOps(raw) {
16
+ const allowed = new Set(AUTHZ_OPERATIONS);
17
+ const out = [];
18
+ for (const part of raw.split(/[,\s]+/)) {
19
+ const op = part.trim().toLowerCase();
20
+ if (op.length === 0)
21
+ continue;
22
+ if (!allowed.has(op)) {
23
+ throw new Error(`unknown operation '${op}'; expected one of ${AUTHZ_OPERATIONS.join(", ")}`);
24
+ }
25
+ out.push(op);
26
+ }
27
+ return out;
28
+ }
29
+ function parseArgv(argv) {
30
+ const base = {
31
+ cmd: "show",
32
+ projectRoot: process.cwd(),
33
+ campaign: null,
34
+ actor: "operator",
35
+ note: null,
36
+ operations: [],
37
+ surfaces: [],
38
+ cohort: null,
39
+ planRef: null,
40
+ repo: null,
41
+ branch: null,
42
+ storyIds: [],
43
+ issueIds: [],
44
+ expiresAt: null,
45
+ singleUse: false,
46
+ grantId: null,
47
+ template: null,
48
+ target: null,
49
+ format: "text",
50
+ };
51
+ const args = [...argv];
52
+ // Drop leading `--` separators from task-style invocation.
53
+ while (args[0] === "--")
54
+ args.shift();
55
+ if (args.length > 0 && !args[0]?.startsWith("-")) {
56
+ const cmd = args.shift();
57
+ if (cmd === "show" ||
58
+ cmd === "uat-start" ||
59
+ cmd === "uat-suspend" ||
60
+ cmd === "grant" ||
61
+ cmd === "revoke") {
62
+ base.cmd = cmd;
63
+ }
64
+ else if (cmd.startsWith("grant-")) {
65
+ base.cmd = "revoke";
66
+ base.grantId = cmd;
67
+ }
68
+ else {
69
+ return { ...base, error: `unknown authz subcommand: ${cmd}` };
70
+ }
71
+ }
72
+ while (args[0] === "--")
73
+ args.shift();
74
+ for (let i = 0; i < args.length; i++) {
75
+ const a = args[i];
76
+ if (a === undefined)
77
+ break;
78
+ if (a === "--project-root" || a === "--projectRoot") {
79
+ base.projectRoot = args[++i] ?? base.projectRoot;
80
+ continue;
81
+ }
82
+ if (a === "--campaign") {
83
+ base.campaign = args[++i] ?? null;
84
+ continue;
85
+ }
86
+ if (a === "--actor") {
87
+ base.actor = args[++i] ?? base.actor;
88
+ continue;
89
+ }
90
+ if (a === "--note") {
91
+ base.note = args[++i] ?? null;
92
+ continue;
93
+ }
94
+ if (a === "--operations" || a === "--ops") {
95
+ try {
96
+ base.operations = parseOps(args[++i] ?? "");
97
+ }
98
+ catch (err) {
99
+ return { ...base, error: String(err) };
100
+ }
101
+ continue;
102
+ }
103
+ if (a === "--surfaces") {
104
+ const raw = args[++i] ?? "";
105
+ base.surfaces = raw
106
+ .split(/[,\s]+/)
107
+ .map((s) => s.trim())
108
+ .filter((s) => s.length > 0);
109
+ continue;
110
+ }
111
+ if (a === "--cohort") {
112
+ base.cohort = args[++i] ?? null;
113
+ continue;
114
+ }
115
+ if (a === "--plan-ref" || a === "--planRef") {
116
+ base.planRef = args[++i] ?? null;
117
+ continue;
118
+ }
119
+ if (a === "--repo") {
120
+ base.repo = args[++i] ?? null;
121
+ continue;
122
+ }
123
+ if (a === "--branch") {
124
+ base.branch = args[++i] ?? null;
125
+ continue;
126
+ }
127
+ if (a === "--stories" || a === "--story-ids") {
128
+ const raw = args[++i] ?? "";
129
+ base.storyIds = raw
130
+ .split(/[,\s]+/)
131
+ .map((s) => s.trim())
132
+ .filter((s) => s.length > 0);
133
+ continue;
134
+ }
135
+ if (a === "--issues" || a === "--issue-ids") {
136
+ const raw = args[++i] ?? "";
137
+ base.issueIds = raw
138
+ .split(/[,\s]+/)
139
+ .map((s) => Number(s.trim()))
140
+ .filter((n) => Number.isFinite(n));
141
+ continue;
142
+ }
143
+ if (a === "--expires" || a === "--expires-at") {
144
+ base.expiresAt = args[++i] ?? null;
145
+ continue;
146
+ }
147
+ if (a === "--single-use") {
148
+ base.singleUse = true;
149
+ continue;
150
+ }
151
+ if (a === "--format") {
152
+ const fmt = (args[++i] ?? "text").toLowerCase();
153
+ base.format = fmt === "json" ? "json" : "text";
154
+ continue;
155
+ }
156
+ if (a === "--grant-id") {
157
+ base.grantId = args[++i] ?? null;
158
+ continue;
159
+ }
160
+ if (a === "--template") {
161
+ base.template = args[++i] ?? null;
162
+ continue;
163
+ }
164
+ if (a === "--target") {
165
+ base.target = args[++i] ?? null;
166
+ continue;
167
+ }
168
+ if (!a.startsWith("-") && base.cmd === "revoke" && base.grantId === null) {
169
+ base.grantId = a;
170
+ continue;
171
+ }
172
+ if (a === "--help" || a === "-h") {
173
+ return { ...base, error: "help" };
174
+ }
175
+ }
176
+ return base;
177
+ }
178
+ function helpText() {
179
+ return [
180
+ "Usage:",
181
+ " deft authz:show [--format json]",
182
+ " deft authz:uat-start -- --campaign <id> [--actor <name>] [--note <text>]",
183
+ " deft authz:uat-suspend",
184
+ " deft authz:grant -- --operations edit,push --surfaces 'src/**' --cohort <id> \\",
185
+ " [--stories 2944] [--plan-ref <id>] [--repo owner/name] [--branch <b>] [--expires ISO]",
186
+ " deft authz:grant -- --template release-publish --target 0.30.0 [--actor <name>] [--expires ISO]",
187
+ " deft authz:grant -- --template finish-loop [--actor <name>] [--expires ISO]",
188
+ " deft authz:revoke -- <grant-id>",
189
+ "",
190
+ "Human-origin grants are minted only via this CLI (origin.kind=operator-cli).",
191
+ "Self-authored xBRIEF/lifecycle/dispatch tokens never satisfy implement gates (#2944).",
192
+ "",
193
+ `AFK templates (#1095 / #871): ${AFK_TEMPLATE_NAMES.join(", ")}`,
194
+ ` Closed-verb (#1095): ${CLOSED_VERB_TEMPLATE_NAMES.join(", ")} — require --target`,
195
+ ` Finish-loop (#871): ${FINISH_LOOP_TEMPLATE_NAME} — edit/push/pr/merge (no release ops)`,
196
+ " Templates call mintHumanOriginGrant only — no second session-auth mint engine.",
197
+ " Env bypass for a single shell: DEFT_ALLOW_RELEASE_PUBLISH=1 / DEFT_ALLOW_FINISH_LOOP=1.",
198
+ ].join("\n");
199
+ }
200
+ export function main(argv = process.argv.slice(2)) {
201
+ const args = parseArgv(argv);
202
+ if (args.error === "help") {
203
+ process.stdout.write(`${helpText()}\n`);
204
+ return 0;
205
+ }
206
+ if (args.error !== undefined) {
207
+ process.stderr.write(`authz: ${args.error}\n`);
208
+ process.stderr.write(`${helpText()}\n`);
209
+ return 2;
210
+ }
211
+ try {
212
+ switch (args.cmd) {
213
+ case "show": {
214
+ const snap = showAuthzSnapshot(args.projectRoot);
215
+ if (args.format === "json") {
216
+ process.stdout.write(`${JSON.stringify(snap, null, 2)}\n`);
217
+ return 0;
218
+ }
219
+ const uat = snap.state.uat;
220
+ if (uat === null) {
221
+ process.stdout.write("UAT lease: inactive\n");
222
+ }
223
+ else {
224
+ process.stdout.write(`UAT lease: ${uat.active ? "ACTIVE" : "suspended"} campaign=${uat.campaignId}\n`);
225
+ process.stdout.write(` started=${uat.startedAt} by=${uat.startedBy.actor} (${uat.startedBy.kind})\n`);
226
+ if (uat.suspendedAt)
227
+ process.stdout.write(` suspended=${uat.suspendedAt}\n`);
228
+ }
229
+ process.stdout.write(`Active human-origin grants: ${snap.activeGrants.length}\n`);
230
+ for (const g of snap.activeGrants) {
231
+ process.stdout.write(` - ${g.id} ops=[${g.scope.operations.join(",")}] ` +
232
+ `cohort=${g.scope.cohortId ?? "-"} surfaces=${g.scope.surfaces.join("|") || "*"}\n`);
233
+ }
234
+ const rejected = snap.allGrants.length - snap.activeGrants.length;
235
+ if (rejected > 0) {
236
+ process.stdout.write(`(${rejected} grant file(s) present but not active/human-origin)\n`);
237
+ }
238
+ return 0;
239
+ }
240
+ case "uat-start": {
241
+ if (args.campaign === null || args.campaign.trim().length === 0) {
242
+ process.stderr.write("authz:uat-start requires --campaign <id>\n");
243
+ return 2;
244
+ }
245
+ const { lease } = startUatLease({
246
+ projectRoot: args.projectRoot,
247
+ campaignId: args.campaign,
248
+ actor: args.actor,
249
+ note: args.note,
250
+ });
251
+ process.stdout.write(`✓ UAT lease ACTIVE campaign=${lease.campaignId} (human-origin operator-cli)\n`);
252
+ process.stdout.write(" Product edit/push/PR/merge denied until a named fix cohort grant is minted.\n");
253
+ process.stdout.write(" Tests, evidence capture, and issue filing remain allowed.\n");
254
+ return 0;
255
+ }
256
+ case "uat-suspend": {
257
+ const state = suspendUatLease({
258
+ projectRoot: args.projectRoot,
259
+ actor: args.actor,
260
+ });
261
+ if (state.uat === null) {
262
+ process.stdout.write("UAT lease was already inactive.\n");
263
+ }
264
+ else {
265
+ process.stdout.write(`✓ UAT lease suspended campaign=${state.uat.campaignId} at ${state.uat.suspendedAt}\n`);
266
+ }
267
+ return 0;
268
+ }
269
+ case "grant": {
270
+ // AFK template path (#1095 / #871): presets only — still mintHumanOriginGrant.
271
+ if (args.template !== null && args.template.trim().length > 0) {
272
+ if (!isAfkTemplateName(args.template)) {
273
+ process.stderr.write(`authz:grant unknown --template '${args.template}'; expected one of: ${AFK_TEMPLATE_NAMES.join(", ")}\n`);
274
+ return 2;
275
+ }
276
+ if (isClosedVerbTemplateName(args.template) &&
277
+ (args.target === null || args.target.trim().length === 0)) {
278
+ process.stderr.write(`authz:grant --template ${args.template} requires --target <version>\n`);
279
+ return 2;
280
+ }
281
+ const grant = mintAfkTemplateGrant({
282
+ projectRoot: args.projectRoot,
283
+ template: args.template,
284
+ target: args.target,
285
+ actor: args.actor,
286
+ expiresAt: args.expiresAt,
287
+ singleUse: args.singleUse,
288
+ planRef: args.planRef,
289
+ repo: args.repo,
290
+ branch: args.branch,
291
+ surfaces: args.surfaces,
292
+ storyIds: args.storyIds,
293
+ issueIds: args.issueIds,
294
+ cohortId: args.cohort,
295
+ });
296
+ process.stdout.write(`✓ human-origin grant minted id=${grant.id} origin=${grant.origin.kind} ` +
297
+ `template=${args.template}\n`);
298
+ if (isFinishLoopTemplateName(args.template)) {
299
+ process.stdout.write(` ops=[${grant.scope.operations.join(",")}] ` +
300
+ `(finish-loop walk-away; release-* NOT authorized)\n`);
301
+ }
302
+ else {
303
+ process.stdout.write(` ops=[${grant.scope.operations.join(",")}] target surfaces=${grant.scope.surfaces.join(", ")}\n`);
304
+ }
305
+ process.stdout.write(" Authorization SoT: Wave 1 grant store (.deft/authz/grants) — not session-auth.\n");
306
+ return 0;
307
+ }
308
+ if (args.operations.length === 0) {
309
+ process.stderr.write("authz:grant requires --operations <edit,push,...> or --template <finish-loop|release-*> \n");
310
+ return 2;
311
+ }
312
+ const grant = mintHumanOriginGrant({
313
+ projectRoot: args.projectRoot,
314
+ actor: args.actor,
315
+ operations: args.operations,
316
+ surfaces: args.surfaces,
317
+ cohortId: args.cohort,
318
+ planRef: args.planRef,
319
+ repo: args.repo,
320
+ branch: args.branch,
321
+ storyIds: args.storyIds,
322
+ issueIds: args.issueIds,
323
+ expiresAt: args.expiresAt,
324
+ singleUse: args.singleUse,
325
+ });
326
+ process.stdout.write(`✓ human-origin grant minted id=${grant.id} origin=${grant.origin.kind}\n`);
327
+ process.stdout.write(` ops=[${grant.scope.operations.join(",")}] cohort=${grant.scope.cohortId ?? "-"}\n`);
328
+ if (grant.scope.surfaces.length > 0) {
329
+ process.stdout.write(` surfaces=${grant.scope.surfaces.join(", ")}\n`);
330
+ }
331
+ return 0;
332
+ }
333
+ case "revoke": {
334
+ if (args.grantId === null) {
335
+ process.stderr.write("authz:revoke requires <grant-id>\n");
336
+ return 2;
337
+ }
338
+ const revoked = revokeGrant({
339
+ projectRoot: args.projectRoot,
340
+ grantId: args.grantId,
341
+ });
342
+ if (revoked === null) {
343
+ process.stderr.write(`authz: grant not found: ${args.grantId}\n`);
344
+ return 1;
345
+ }
346
+ process.stdout.write(`✓ grant revoked id=${revoked.id} at ${revoked.semantics.revokedAt}\n`);
347
+ return 0;
348
+ }
349
+ default:
350
+ process.stderr.write(`${helpText()}\n`);
351
+ return 2;
352
+ }
353
+ }
354
+ catch (err) {
355
+ process.stderr.write(`authz: ${String(err)}\n`);
356
+ return 1;
357
+ }
358
+ }
359
+ export default main;
360
+ if (import.meta.url === `file://${process.argv[1]?.replace(/\\/g, "/")}`) {
361
+ process.exitCode = main();
362
+ }
363
+ //# sourceMappingURL=authz.js.map
@@ -36,6 +36,7 @@ export const PR_VERB_MAP = {
36
36
  "check-closing-keywords": "pr-closing-keywords",
37
37
  "wait-mergeable-and-merge": "pr-wait-mergeable",
38
38
  watch: "pr-watch",
39
+ "finish-loop": "pr-finish-loop",
39
40
  };
40
41
  /** verify:* aliases that map to non-verify-* handler stems. */
41
42
  export const VERIFY_VERB_MAP = {
@@ -74,8 +75,18 @@ export const SUBCOMMAND_ROUTES = {
74
75
  "policy:enable-directive": ["policy", "enable-directive"],
75
76
  "policy:enforce-branches": ["policy", "enforce-branches"],
76
77
  "policy:allow-direct-commits": ["policy", "allow-direct-commits"],
78
+ "policy:allow-bot-merge": ["policy", "allow-bot-merge"],
77
79
  "policy:enable-value-feedback": ["policy", "enable-value-feedback"],
78
80
  "policy:clear-value-feedback": ["policy", "clear-value-feedback"],
81
+ "authz:show": ["authz", "show"],
82
+ "authz:uat-start": ["authz", "uat-start"],
83
+ "authz:uat-suspend": ["authz", "uat-suspend"],
84
+ "authz:grant": ["authz", "grant"],
85
+ "authz:revoke": ["authz", "revoke"],
86
+ "escalation:file": ["escalation-cli", "file"],
87
+ "escalation:list": ["escalation-cli", "list"],
88
+ "escalation:resolve": ["escalation-cli", "resolve"],
89
+ "escalation:batch-approve": ["escalation-cli", "batch-approve"],
79
90
  "product-signal:status": ["product-signal", "status"],
80
91
  "product-signal:enable": ["product-signal", "enable"],
81
92
  "product-signal:consent": ["product-signal", "consent"],
@@ -89,9 +100,11 @@ export const SUBCOMMAND_ROUTES = {
89
100
  "github-body:issue-create": ["github-body", "issue-create"],
90
101
  "github-body:issue-edit": ["github-body", "issue-edit"],
91
102
  "github-body:issue-fetch": ["github-body", "issue-fetch"],
103
+ "github-body:issue-lint": ["github-body", "issue-lint"],
92
104
  "github-body:comment-create": ["github-body", "comment-create"],
93
105
  "github-body:comment-edit": ["github-body", "comment-edit"],
94
106
  "github-body:pr-edit": ["github-body", "pr-edit"],
107
+ "github-body:pr-lint": ["github-body", "pr-lint"],
95
108
  "plan-sequence:set": ["plan-sequence", "set"],
96
109
  "plan-sequence:current": ["plan-sequence", "current"],
97
110
  "plan-sequence:clear": ["plan-sequence", "clear"],
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function run(argv: string[]): number;
3
+ //# sourceMappingURL=directive-finish-loop.d.ts.map
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { cmdDirectiveFinishLoop } from "@deftai/directive-core/dist/finish-loop/main.js";
4
+ export function run(argv) {
5
+ return cmdDirectiveFinishLoop(argv);
6
+ }
7
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
8
+ process.exit(run(process.argv.slice(2)));
9
+ }
10
+ //# sourceMappingURL=directive-finish-loop.js.map
@@ -10,13 +10,17 @@ export interface DispatchIo {
10
10
  writeErr: (text: string) => void;
11
11
  }
12
12
  /** CLI modules in packages/cli/src (excluding parity harnesses and bin/index). */
13
- export declare const CLI_MODULE_VERBS: readonly ["agents-refresh", "cache", "check", "capacity-backfill", "capacity-show", "codebase-default-extractor", "codebase-map", "codebase-map-fresh", "codebase-projection-registry", "codebase-provider", "doctor", "install-upgrade", "install-uninstall", "migrate-preflight", "migrate-xbrief", "migrate-category-b", "framework-check-updates", "hook-dispatch", "umbrella-current-shape", "changelog-check", "change-init", "commit-lint", "coverage-hotspots", "policy", "pr-closing-keywords", "pr-merge-readiness", "pr-monitor", "pr-protected-issues", "pr-wait-mergeable", "pr-watch", "preflight-cache", "preflight-gh", "probe-session", "release", "release-e2e", "release-publish", "release-rollback", "scope-lifecycle", "lifecycle-event", "session-start", "plan-sequence", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "triage-help", "triage-queue", "triage-reconcile", "triage-refresh", "triage-scope", "triage-scope-drift", "triage-smoketest", "triage-subscribe", "triage-summary", "triage-welcome", "ts-check-lane", "vbrief-activate", "vbrief-build", "vbrief-preflight", "vbrief-reconcile", "vbrief-validate", "vbrief-validation", "verify-branch", "verify-encoding", "verify-forward-coverage", "verify-hooks-installed", "verify-investigation", "verify-judgment-gates", "verify-no-task-runtime", "validate-links", "validate-strategy-output", "verify-biome-config", "verify-bridge-drift", "verify-capacity", "verify-content-manifest", "verify-skill-external-fetch-gate", "verify-contract-drift", "verify-cursor-tier1", "verify-openclaw-tier1", "verify-go-freeze", "verify-scm-boundary", "verify-session-ritual", "verify-plan-sequence", "verify-stubs", "verify-xbrief-drift", "rule-ownership-lint", "verify-story-ready", "verify-review-monitor", "verify-subagent-alive", "review-monitor-register", "review-monitor-release", "verify-tools", "verify-wip-cap", "verify-orphan-active", "verify-agents-md-budget", "verify-agents-md-advisory", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "eval-health", "eval-run", "eval-report", "eval-triggers"];
13
+ export declare const CLI_MODULE_VERBS: readonly ["agents-refresh", "cache", "check", "capacity-backfill", "capacity-show", "codebase-default-extractor", "codebase-map", "codebase-map-fresh", "codebase-projection-registry", "codebase-provider", "doctor", "install-upgrade", "install-uninstall", "migrate-preflight", "migrate-xbrief", "migrate-category-b", "framework-check-updates", "hook-dispatch", "umbrella-current-shape", "changelog-check", "change-init", "commit-lint", "coverage-hotspots", "policy", "authz", "escalation-cli", "pr-closing-keywords", "pr-merge-readiness", "pr-monitor", "pr-protected-issues", "pr-wait-mergeable", "pr-watch", "pr-finish-loop", "directive-finish-loop", "preflight-cache", "preflight-gh", "probe-session", "release", "release-e2e", "release-publish", "release-rollback", "scope-lifecycle", "lifecycle-event", "session-start", "plan-sequence", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "triage-help", "triage-queue", "triage-reconcile", "triage-refresh", "triage-scope", "triage-scope-drift", "triage-smoketest", "triage-subscribe", "triage-summary", "triage-welcome", "ts-check-lane", "vbrief-activate", "vbrief-build", "vbrief-preflight", "vbrief-reconcile", "vbrief-validate", "vbrief-validation", "verify-branch", "verify-encoding", "verify-forward-coverage", "verify-hooks-installed", "verify-investigation", "verify-judgment-gates", "verify-no-task-runtime", "validate-links", "validate-strategy-output", "verify-biome-config", "verify-bridge-drift", "verify-capacity", "verify-contained-writes", "verify-content-manifest", "verify-skill-external-fetch-gate", "verify-contract-drift", "verify-cursor-tier1", "verify-openclaw-tier1", "verify-go-freeze", "verify-scm-boundary", "verify-session-ritual", "verify-plan-sequence", "verify-stubs", "verify-xbrief-drift", "rule-ownership-lint", "verify-story-ready", "verify-review-monitor", "verify-subagent-alive", "review-monitor-register", "review-monitor-release", "verify-tools", "verify-wip-cap", "verify-orphan-active", "verify-agents-md-budget", "verify-agents-md-advisory", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "eval-health", "eval-run", "eval-report", "eval-triggers"];
14
14
  /** Core-only CLI entrypoints without a packages/cli wrapper. */
15
15
  export declare const CORE_MODULE_VERBS: readonly ["scm", "github-auth-modes", "github-body", "issue-emit", "issue-ingest", "issue-sync-from-xbrief", "reconcile-issues", "swarm-launch", "swarm-complete-cohort", "swarm-finalize-cohort", "swarm-readiness", "swarm-routing-verify", "swarm-routing-set", "swarm-verify-review-clean", "swarm-worktrees", "framework-commands", "pack-render", "packs-slice", "prd-render", "export-spec", "project-render", "roadmap-render", "rule-map", "spec-render", "spec-validate", "code-structure-validate", "pack-migrate-skills", "pack-migrate-rules", "pack-migrate-strategies", "pack-migrate-patterns", "pack-migrate-swarm-spec", "policy-set", "setup-ghx", "scope-undo", "scope-demote", "scope-decompose", "changelog-resolve-unreleased", "architecture-preflight-sor", "feedback-file", "value-readback", "product-signal"];
16
16
  /** Colon aliases for triage-actions (mirrors cli-router SUBCOMMAND_ROUTES). */
17
17
  export declare const TRIAGE_ACTION_ALIAS_SUBCOMMANDS: Readonly<Record<string, string>>;
18
18
  /** Colon aliases for policy subcommands (mirrors cli-router SUBCOMMAND_ROUTES). */
19
19
  export declare const POLICY_ACTION_ALIAS_SUBCOMMANDS: Readonly<Record<string, string>>;
20
+ /** Colon aliases for authz subcommands (#2944). */
21
+ export declare const AUTHZ_ACTION_ALIAS_SUBCOMMANDS: Readonly<Record<string, string>>;
22
+ /** Colon aliases for escalation subcommands (#518). */
23
+ export declare const ESCALATION_ACTION_ALIAS_SUBCOMMANDS: Readonly<Record<string, string>>;
20
24
  /** Colon aliases for plan-sequence subcommands (#2402). */
21
25
  export declare const PLAN_SEQUENCE_ALIAS_SUBCOMMANDS: Readonly<Record<string, string>>;
22
26
  /** Colon aliases for product-signal subcommands (#2693). */
package/dist/dispatch.js CHANGED
@@ -50,12 +50,16 @@ export const CLI_MODULE_VERBS = [
50
50
  "commit-lint",
51
51
  "coverage-hotspots",
52
52
  "policy",
53
+ "authz",
54
+ "escalation-cli",
53
55
  "pr-closing-keywords",
54
56
  "pr-merge-readiness",
55
57
  "pr-monitor",
56
58
  "pr-protected-issues",
57
59
  "pr-wait-mergeable",
58
60
  "pr-watch",
61
+ "pr-finish-loop",
62
+ "directive-finish-loop",
59
63
  "preflight-cache",
60
64
  "preflight-gh",
61
65
  "probe-session",
@@ -103,6 +107,7 @@ export const CLI_MODULE_VERBS = [
103
107
  "verify-biome-config",
104
108
  "verify-bridge-drift",
105
109
  "verify-capacity",
110
+ "verify-contained-writes",
106
111
  "verify-content-manifest",
107
112
  "verify-skill-external-fetch-gate",
108
113
  "verify-contract-drift",
@@ -193,12 +198,30 @@ export const POLICY_ACTION_ALIAS_SUBCOMMANDS = {
193
198
  "policy:show": "show",
194
199
  "policy:enforce-branches": "enforce-branches",
195
200
  "policy:allow-direct-commits": "allow-direct-commits",
201
+ "policy:allow-bot-merge": "allow-bot-merge",
196
202
  "policy:enable-value-feedback": "enable-value-feedback",
197
203
  "policy:clear-value-feedback": "clear-value-feedback",
198
204
  "policy:disable-directive": "disable-directive",
199
205
  "policy:enable-directive": "enable-directive",
200
206
  };
201
207
  const POLICY_ACTION_COLON_ALIASES = Object.fromEntries(Object.keys(POLICY_ACTION_ALIAS_SUBCOMMANDS).map((alias) => [alias, "policy"]));
208
+ /** Colon aliases for authz subcommands (#2944). */
209
+ export const AUTHZ_ACTION_ALIAS_SUBCOMMANDS = {
210
+ "authz:show": "show",
211
+ "authz:uat-start": "uat-start",
212
+ "authz:uat-suspend": "uat-suspend",
213
+ "authz:grant": "grant",
214
+ "authz:revoke": "revoke",
215
+ };
216
+ const AUTHZ_ACTION_COLON_ALIASES = Object.fromEntries(Object.keys(AUTHZ_ACTION_ALIAS_SUBCOMMANDS).map((alias) => [alias, "authz"]));
217
+ /** Colon aliases for escalation subcommands (#518). */
218
+ export const ESCALATION_ACTION_ALIAS_SUBCOMMANDS = {
219
+ "escalation:file": "file",
220
+ "escalation:list": "list",
221
+ "escalation:resolve": "resolve",
222
+ "escalation:batch-approve": "batch-approve",
223
+ };
224
+ const ESCALATION_ACTION_COLON_ALIASES = Object.fromEntries(Object.keys(ESCALATION_ACTION_ALIAS_SUBCOMMANDS).map((alias) => [alias, "escalation-cli"]));
202
225
  /** Colon aliases for plan-sequence subcommands (#2402). */
203
226
  export const PLAN_SEQUENCE_ALIAS_SUBCOMMANDS = {
204
227
  "plan-sequence:set": "set",
@@ -251,6 +274,7 @@ export const VERB_ALIASES = {
251
274
  "verify:rule-ownership": "rule-ownership-lint",
252
275
  "rule:ownership-lint": "rule-ownership-lint",
253
276
  "verify:biome-config": "verify-biome-config",
277
+ "verify:contained-writes": "verify-contained-writes",
254
278
  "verify:content-manifest": "verify-content-manifest",
255
279
  "verify:skill-external-fetch-gate": "verify-skill-external-fetch-gate",
256
280
  "verify:contract-drift": "verify-contract-drift",
@@ -275,6 +299,8 @@ export const VERB_ALIASES = {
275
299
  "triage:scope": "triage-scope",
276
300
  ...TRIAGE_ACTION_COLON_ALIASES,
277
301
  ...POLICY_ACTION_COLON_ALIASES,
302
+ ...AUTHZ_ACTION_COLON_ALIASES,
303
+ ...ESCALATION_ACTION_COLON_ALIASES,
278
304
  ...PRODUCT_SIGNAL_COLON_ALIASES,
279
305
  "agents:refresh": "agents-refresh",
280
306
  "migrate:preflight": "migrate-preflight",
@@ -295,6 +321,8 @@ export const VERB_ALIASES = {
295
321
  "docs:rule-map": "rule-map",
296
322
  "project:export-spec": "export-spec",
297
323
  "pr:watch": "pr-watch",
324
+ "pr:finish-loop": "pr-finish-loop",
325
+ "directive:finish-loop": "directive-finish-loop",
298
326
  doctor: "doctor",
299
327
  "eval:health": "eval-health",
300
328
  "feedback:file": "feedback-file",
@@ -311,6 +339,7 @@ const SUBDIR_CLI_STEMS = {
311
339
  "verify-stubs": "verify-source-cli/verify-stubs",
312
340
  "rule-ownership-lint": "verify-source-cli/rule-ownership-lint",
313
341
  "verify-biome-config": "verify-source-cli/verify-biome-config",
342
+ "verify-contained-writes": "verify-source-cli/verify-contained-writes",
314
343
  "verify-content-manifest": "verify-source-cli/verify-content-manifest",
315
344
  "verify-skill-external-fetch-gate": "verify-source-cli/verify-skill-external-fetch-gate",
316
345
  "verify-contract-drift": "verify-source-cli/verify-contract-drift",
@@ -2598,6 +2627,8 @@ export async function dispatch(argv, io = defaultIo()) {
2598
2627
  const handler = await loadHandler(canonical, io);
2599
2628
  const triageSubcommand = verb !== undefined ? TRIAGE_ACTION_ALIAS_SUBCOMMANDS[verb] : undefined;
2600
2629
  const policySubcommand = verb !== undefined ? POLICY_ACTION_ALIAS_SUBCOMMANDS[verb] : undefined;
2630
+ const authzSubcommand = verb !== undefined ? AUTHZ_ACTION_ALIAS_SUBCOMMANDS[verb] : undefined;
2631
+ const escalationSubcommand = verb !== undefined ? ESCALATION_ACTION_ALIAS_SUBCOMMANDS[verb] : undefined;
2601
2632
  const planSequenceSubcommand = verb !== undefined ? PLAN_SEQUENCE_ALIAS_SUBCOMMANDS[verb] : undefined;
2602
2633
  const productSignalSubcommand = verb !== undefined ? PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS[verb] : undefined;
2603
2634
  const handlerArgv = canonical === "framework-commands" && verb !== undefined && verb !== canonical
@@ -2606,11 +2637,15 @@ export async function dispatch(argv, io = defaultIo()) {
2606
2637
  ? [triageSubcommand, ...rest]
2607
2638
  : policySubcommand !== undefined && canonical === "policy"
2608
2639
  ? [policySubcommand, ...rest]
2609
- : planSequenceSubcommand !== undefined && canonical === "plan-sequence"
2610
- ? [planSequenceSubcommand, ...rest]
2611
- : productSignalSubcommand !== undefined && canonical === "product-signal"
2612
- ? [productSignalSubcommand, ...rest]
2613
- : rest;
2640
+ : authzSubcommand !== undefined && canonical === "authz"
2641
+ ? [authzSubcommand, ...rest]
2642
+ : escalationSubcommand !== undefined && canonical === "escalation-cli"
2643
+ ? [escalationSubcommand, ...rest]
2644
+ : planSequenceSubcommand !== undefined && canonical === "plan-sequence"
2645
+ ? [planSequenceSubcommand, ...rest]
2646
+ : productSignalSubcommand !== undefined && canonical === "product-signal"
2647
+ ? [productSignalSubcommand, ...rest]
2648
+ : rest;
2614
2649
  return await invokeHandler(handler, handlerArgv);
2615
2650
  }
2616
2651
  catch (err) {
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(argv?: string[]): number;
3
+ export default main;
4
+ //# sourceMappingURL=escalation-cli.d.ts.map
@@ -0,0 +1,301 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Escalation CLI (#518 slim / #2948 Wave 5): typed queue under .deft/escalations/.
4
+ *
5
+ * deft escalation:file -- --type cmd_approval --title "…"
6
+ * deft escalation:list [--open] [--type <type>] [--format json]
7
+ * deft escalation:resolve -- <id> --decision approved|denied|answered|dismissed
8
+ * deft escalation:batch-approve [--ids a,b] [--include-dangerous]
9
+ */
10
+ import { batchApproveEscalations, ESCALATION_TYPES, fileEscalation, isEscalationType, listEscalationsFiltered, resolveEscalation, } from "@deftai/directive-core/escalation";
11
+ function parseArgv(argv) {
12
+ const base = {
13
+ cmd: "list",
14
+ projectRoot: process.cwd(),
15
+ type: null,
16
+ title: null,
17
+ body: null,
18
+ agentId: "agent",
19
+ contextRefs: [],
20
+ slaHours: null,
21
+ dangerous: false,
22
+ id: null,
23
+ ids: [],
24
+ decision: null,
25
+ actor: "operator",
26
+ note: null,
27
+ answer: null,
28
+ openOnly: false,
29
+ includeDangerous: false,
30
+ format: "text",
31
+ };
32
+ const args = [...argv];
33
+ while (args[0] === "--")
34
+ args.shift();
35
+ if (args.length > 0 && !args[0]?.startsWith("-")) {
36
+ const cmd = args.shift();
37
+ if (cmd === "file" || cmd === "list" || cmd === "resolve" || cmd === "batch-approve") {
38
+ base.cmd = cmd;
39
+ }
40
+ else if (cmd.startsWith("esc-")) {
41
+ base.cmd = "resolve";
42
+ base.id = cmd;
43
+ }
44
+ else {
45
+ return { ...base, error: `unknown escalation subcommand: ${cmd}` };
46
+ }
47
+ }
48
+ while (args[0] === "--")
49
+ args.shift();
50
+ for (let i = 0; i < args.length; i++) {
51
+ const a = args[i];
52
+ if (a === undefined)
53
+ break;
54
+ if (a === "--project-root" || a === "--projectRoot") {
55
+ base.projectRoot = args[++i] ?? base.projectRoot;
56
+ continue;
57
+ }
58
+ if (a === "--type") {
59
+ base.type = args[++i] ?? null;
60
+ continue;
61
+ }
62
+ if (a === "--title") {
63
+ base.title = args[++i] ?? null;
64
+ continue;
65
+ }
66
+ if (a === "--body") {
67
+ base.body = args[++i] ?? null;
68
+ continue;
69
+ }
70
+ if (a === "--agent" || a === "--agent-id" || a === "--agentId") {
71
+ base.agentId = args[++i] ?? base.agentId;
72
+ continue;
73
+ }
74
+ if (a === "--context" || a === "--context-refs" || a === "--contextRefs") {
75
+ const raw = args[++i] ?? "";
76
+ base.contextRefs = raw
77
+ .split(/[,\s]+/)
78
+ .map((s) => s.trim())
79
+ .filter((s) => s.length > 0);
80
+ continue;
81
+ }
82
+ if (a === "--sla-hours" || a === "--slaHours") {
83
+ const n = Number(args[++i] ?? "");
84
+ base.slaHours = Number.isFinite(n) ? n : null;
85
+ continue;
86
+ }
87
+ if (a === "--dangerous") {
88
+ base.dangerous = true;
89
+ continue;
90
+ }
91
+ if (a === "--id") {
92
+ base.id = args[++i] ?? null;
93
+ continue;
94
+ }
95
+ if (a === "--ids") {
96
+ const raw = args[++i] ?? "";
97
+ base.ids = raw
98
+ .split(/[,\s]+/)
99
+ .map((s) => s.trim())
100
+ .filter((s) => s.length > 0);
101
+ continue;
102
+ }
103
+ if (a === "--decision") {
104
+ base.decision = args[++i] ?? null;
105
+ continue;
106
+ }
107
+ if (a === "--actor") {
108
+ base.actor = args[++i] ?? base.actor;
109
+ continue;
110
+ }
111
+ if (a === "--note") {
112
+ base.note = args[++i] ?? null;
113
+ continue;
114
+ }
115
+ if (a === "--answer") {
116
+ base.answer = args[++i] ?? null;
117
+ continue;
118
+ }
119
+ if (a === "--open") {
120
+ base.openOnly = true;
121
+ continue;
122
+ }
123
+ if (a === "--include-dangerous") {
124
+ base.includeDangerous = true;
125
+ continue;
126
+ }
127
+ if (a === "--format") {
128
+ const fmt = (args[++i] ?? "text").toLowerCase();
129
+ base.format = fmt === "json" ? "json" : "text";
130
+ continue;
131
+ }
132
+ if (!a.startsWith("-") && base.cmd === "resolve" && base.id === null) {
133
+ base.id = a;
134
+ continue;
135
+ }
136
+ if (a === "--help" || a === "-h") {
137
+ return { ...base, error: "help" };
138
+ }
139
+ }
140
+ return base;
141
+ }
142
+ function helpText() {
143
+ return [
144
+ "Usage:",
145
+ " deft escalation:file -- --type <type> --title <text> [--body <text>] [--agent <id>]",
146
+ " [--context refs…] [--sla-hours N] [--dangerous] [--format json]",
147
+ " deft escalation:list [--open] [--type <type>] [--format json]",
148
+ " deft escalation:resolve -- <id> --decision approved|denied|answered|dismissed",
149
+ " [--note <text>] [--answer <text>] [--actor <name>]",
150
+ " deft escalation:batch-approve [--ids a,b] [--include-dangerous] [--note <text>]",
151
+ "",
152
+ `Types: ${ESCALATION_TYPES.join(", ")}`,
153
+ "Bulk batch-approve is limited to cmd_approval + question (non-dangerous by default).",
154
+ "design_decision / approval / resource / external require individual resolve.",
155
+ "Store: .deft/escalations/<id>.json Contract: content/contracts/escalation.md",
156
+ "Compose gated actions with deft authz:grant after approval (Wave 1 grants).",
157
+ ].join("\n");
158
+ }
159
+ export function main(argv = process.argv.slice(2)) {
160
+ const args = parseArgv(argv);
161
+ if (args.error === "help") {
162
+ process.stdout.write(`${helpText()}\n`);
163
+ return 0;
164
+ }
165
+ if (args.error !== undefined) {
166
+ process.stderr.write(`escalation: ${args.error}\n`);
167
+ process.stderr.write(`${helpText()}\n`);
168
+ return 2;
169
+ }
170
+ try {
171
+ switch (args.cmd) {
172
+ case "file": {
173
+ if (args.type === null || args.type.trim().length === 0) {
174
+ process.stderr.write("escalation:file requires --type <type>\n");
175
+ return 2;
176
+ }
177
+ if (args.title === null || args.title.trim().length === 0) {
178
+ process.stderr.write("escalation:file requires --title <text>\n");
179
+ return 2;
180
+ }
181
+ const event = fileEscalation({
182
+ projectRoot: args.projectRoot,
183
+ type: args.type,
184
+ title: args.title,
185
+ body: args.body ?? undefined,
186
+ agentId: args.agentId,
187
+ contextRefs: args.contextRefs,
188
+ slaHours: args.slaHours ?? undefined,
189
+ dangerous: args.dangerous,
190
+ id: args.id ?? undefined,
191
+ });
192
+ if (args.format === "json") {
193
+ process.stdout.write(`${JSON.stringify(event, null, 2)}\n`);
194
+ }
195
+ else {
196
+ process.stdout.write(`✓ escalation filed id=${event.id} type=${event.type} status=${event.status}` +
197
+ `${event.dangerous ? " dangerous=true" : ""}\n`);
198
+ process.stdout.write(` title=${event.title}\n`);
199
+ process.stdout.write(` store=.deft/escalations/${event.id}.json\n`);
200
+ }
201
+ return 0;
202
+ }
203
+ case "list": {
204
+ let typeFilter;
205
+ if (args.type !== null && args.type.trim().length > 0) {
206
+ if (!isEscalationType(args.type.trim().toLowerCase())) {
207
+ process.stderr.write(`escalation:list unknown --type '${args.type}'; expected: ${ESCALATION_TYPES.join(", ")}\n`);
208
+ return 2;
209
+ }
210
+ typeFilter = args.type.trim().toLowerCase();
211
+ }
212
+ const items = listEscalationsFiltered(args.projectRoot, {
213
+ openOnly: args.openOnly,
214
+ type: typeFilter,
215
+ });
216
+ if (args.format === "json") {
217
+ process.stdout.write(`${JSON.stringify(items, null, 2)}\n`);
218
+ return 0;
219
+ }
220
+ if (items.length === 0) {
221
+ process.stdout.write(args.openOnly ? "No open escalations.\n" : "No escalations on disk.\n");
222
+ return 0;
223
+ }
224
+ process.stdout.write(`Escalations (${items.length}):\n`);
225
+ for (const e of items) {
226
+ const dang = e.dangerous ? " !dangerous" : "";
227
+ process.stdout.write(` - ${e.id} [${e.status}] type=${e.type}${dang} agent=${e.agentId} sla=${e.slaHours}h\n`);
228
+ process.stdout.write(` ${e.title}\n`);
229
+ if (e.resolution) {
230
+ process.stdout.write(` → ${e.resolution.decision} by ${e.resolution.resolvedBy} @ ${e.resolution.resolvedAt}\n`);
231
+ }
232
+ }
233
+ return 0;
234
+ }
235
+ case "resolve": {
236
+ if (args.id === null || args.id.trim().length === 0) {
237
+ process.stderr.write("escalation:resolve requires <id>\n");
238
+ return 2;
239
+ }
240
+ if (args.decision === null || args.decision.trim().length === 0) {
241
+ process.stderr.write("escalation:resolve requires --decision approved|denied|answered|dismissed\n");
242
+ return 2;
243
+ }
244
+ const result = resolveEscalation({
245
+ projectRoot: args.projectRoot,
246
+ id: args.id,
247
+ decision: args.decision,
248
+ actor: args.actor,
249
+ note: args.note,
250
+ answer: args.answer,
251
+ });
252
+ if (!result.ok) {
253
+ process.stderr.write(`escalation: ${result.message}\n`);
254
+ return result.code === "not-found" ? 1 : 2;
255
+ }
256
+ if (args.format === "json") {
257
+ process.stdout.write(`${JSON.stringify(result.event, null, 2)}\n`);
258
+ }
259
+ else {
260
+ process.stdout.write(`✓ resolved id=${result.event.id} decision=${result.event.resolution?.decision}\n`);
261
+ process.stdout.write(" For gated product actions, mint a Wave 1 grant: deft authz:grant …\n");
262
+ }
263
+ return 0;
264
+ }
265
+ case "batch-approve": {
266
+ const batch = batchApproveEscalations({
267
+ projectRoot: args.projectRoot,
268
+ ids: args.ids.length > 0 ? args.ids : undefined,
269
+ actor: args.actor,
270
+ note: args.note,
271
+ includeDangerous: args.includeDangerous,
272
+ });
273
+ if (args.format === "json") {
274
+ process.stdout.write(`${JSON.stringify(batch, null, 2)}\n`);
275
+ }
276
+ else {
277
+ process.stdout.write(`✓ batch-approve approved=${batch.approved.length} skipped=${batch.skipped.length}\n`);
278
+ for (const e of batch.approved) {
279
+ process.stdout.write(` + ${e.id} type=${e.type}\n`);
280
+ }
281
+ for (const s of batch.skipped) {
282
+ process.stdout.write(` - skip ${s.id}: ${s.reason}\n`);
283
+ }
284
+ }
285
+ return 0;
286
+ }
287
+ default:
288
+ process.stderr.write(`${helpText()}\n`);
289
+ return 2;
290
+ }
291
+ }
292
+ catch (err) {
293
+ process.stderr.write(`escalation: ${String(err)}\n`);
294
+ return 1;
295
+ }
296
+ }
297
+ export default main;
298
+ if (import.meta.url === `file://${process.argv[1]?.replace(/\\/g, "/")}`) {
299
+ process.exitCode = main();
300
+ }
301
+ //# sourceMappingURL=escalation-cli.js.map
@@ -48,10 +48,16 @@ export declare function readStdinHardened(readOnce: () => string, options?: {
48
48
  nowMs?: () => number;
49
49
  }): string;
50
50
  export declare function parseArgs(argv: readonly string[]): ParsedArgs;
51
+ /** @deprecated Prefer ParsedHookPayload from @deftai/directive-core/hooks (#2950). */
51
52
  export interface ParsedPayload {
52
53
  readonly payload: unknown;
53
54
  readonly context: HookPayloadContext;
54
55
  }
56
+ /**
57
+ * Parse host hook stdin into payload + context.
58
+ * Pure implementation lives in core classify (`parseHookStdin`); CLI re-exports
59
+ * for backward-compatible imports in tests (#2734 / #2738 / #2950).
60
+ */
55
61
  export declare function parsePayload(raw: string): ParsedPayload;
56
62
  /**
57
63
  * Provider-neutral hook dispatch CLI entry (`deft hook:dispatch` / `deft-hook`).
@@ -2,7 +2,7 @@
2
2
  import { readFileSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { decideHook, hookPayloadTopLevelKeys, isHookEvent, isHookHost, normalizeHookProjectRoot, projectRootFromHookPayload, renderHostDecision, } from "@deftai/directive-core/hooks";
5
+ import { decideHook, hookPayloadTopLevelKeys, isHookEvent, isHookHost, normalizeHookProjectRoot, parseHookStdin, projectRootFromHookPayload, renderHostDecision, } from "@deftai/directive-core/hooks";
6
6
  /**
7
7
  * How long to re-poll stdin after an empty first read before concluding the host
8
8
  * sent nothing (#2864). Keeps well under Cursor's deposited `timeout: 5` while
@@ -103,59 +103,13 @@ export function parseArgs(argv) {
103
103
  return { error: "--event is required" };
104
104
  return parsed;
105
105
  }
106
- const UTF8_BOM = "\uFEFF";
107
- const APPLY_PATCH_BEGIN_MARKER = "*** Begin Patch";
108
- /** Single-file Add/Update only other *** File: ops must fail closed (#2738 Greptile). */
109
- const APPLY_PATCH_MUTATION_LINE_RE = /^\*\*\* (Add File|Update File|Delete File|Move File|Rename File): (.+)$/gm;
110
- function stripUtf8Bom(raw) {
111
- return raw.startsWith(UTF8_BOM) ? raw.slice(UTF8_BOM.length) : raw;
112
- }
113
- function trySynthesizeFreeFormApplyPatch(normalized) {
114
- if (!normalized.includes(APPLY_PATCH_BEGIN_MARKER))
115
- return null;
116
- const mutations = [];
117
- for (const match of normalized.matchAll(APPLY_PATCH_MUTATION_LINE_RE)) {
118
- const op = match[1];
119
- const path = match[2]?.trim();
120
- if (op === undefined || !path)
121
- continue;
122
- mutations.push({ op, path });
123
- }
124
- if (mutations.length !== 1)
125
- return null;
126
- const sole = mutations[0];
127
- if (sole === undefined || (sole.op !== "Add File" && sole.op !== "Update File"))
128
- return null;
129
- return {
130
- payload: {
131
- tool_name: "ApplyPatch",
132
- tool_input: {
133
- path: sole.path,
134
- patch: normalized,
135
- },
136
- },
137
- context: {},
138
- };
139
- }
106
+ /**
107
+ * Parse host hook stdin into payload + context.
108
+ * Pure implementation lives in core classify (`parseHookStdin`); CLI re-exports
109
+ * for backward-compatible imports in tests (#2734 / #2738 / #2950).
110
+ */
140
111
  export function parsePayload(raw) {
141
- if (raw.trim().length === 0) {
142
- return { payload: {}, context: { stdinEmpty: true } };
143
- }
144
- const normalized = stripUtf8Bom(raw);
145
- if (normalized.trim().length === 0) {
146
- return { payload: {}, context: { stdinEmpty: true } };
147
- }
148
- try {
149
- return { payload: JSON.parse(normalized), context: {} };
150
- }
151
- catch {
152
- const synthesized = trySynthesizeFreeFormApplyPatch(normalized);
153
- if (synthesized !== null)
154
- return synthesized;
155
- // tool.before is installed only on direct-write matchers, so an unreadable
156
- // payload becomes a missing-tool denial rather than a fail-open crash.
157
- return { payload: {}, context: { parseFailed: true } };
158
- }
112
+ return parseHookStdin(raw);
159
113
  }
160
114
  /**
161
115
  * Provider-neutral hook dispatch CLI entry (`deft hook:dispatch` / `deft-hook`).
package/dist/policy.d.ts CHANGED
@@ -7,7 +7,7 @@ interface ShowArgs {
7
7
  error?: string;
8
8
  }
9
9
  interface SetArgs {
10
- cmd: "show" | "enforce-branches" | "allow-direct-commits" | "enable-value-feedback" | "clear-value-feedback" | "disable-directive" | "enable-directive" | "resolve";
10
+ cmd: "show" | "enforce-branches" | "allow-direct-commits" | "allow-bot-merge" | "enable-value-feedback" | "clear-value-feedback" | "disable-directive" | "enable-directive" | "resolve";
11
11
  confirm: boolean;
12
12
  actor: string;
13
13
  note: string;
package/dist/policy.js CHANGED
@@ -6,7 +6,7 @@
6
6
  import { existsSync } from "node:fs";
7
7
  import { resolve as pathResolve, relative } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import { clearValueFeedback, createNoDeftDirectiveFlag, describeShadowedPlanExtension, detectNoDeftDirective, detectShadowedPlanExtensions, disclosureLine, enableValueFeedback, FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, formatValueFeedbackStatusLine, inspectAllPolicies, inspectOnePolicy, loadProjectDefinition, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, policyColonInvocation, projectDefinitionPath, pythonListRepr, pythonStringRepr, registeredPolicyNames, removeNoDeftDirectiveFlag, renderJson, renderText, resolvePolicy, resolveValueFeedback, setPolicy, } from "@deftai/directive-core/policy";
9
+ import { ALLOW_BOT_MERGE_CAPABILITY_COST, clearValueFeedback, createNoDeftDirectiveFlag, describeShadowedPlanExtension, detectNoDeftDirective, detectShadowedPlanExtensions, disclosureLine, enableValueFeedback, FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, formatValueFeedbackStatusLine, humanMergeDisclosureLine, inspectAllPolicies, inspectOnePolicy, loadProjectDefinition, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, policyColonInvocation, projectDefinitionPath, pythonListRepr, pythonStringRepr, registeredPolicyNames, removeNoDeftDirectiveFlag, renderJson, renderText, resolveHumanMergePolicy, resolvePolicy, resolveValueFeedback, setPolicy, setRequireHumanMerge, } from "@deftai/directive-core/policy";
10
10
  const CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- enabling direct commits to the default " +
11
11
  "branch turns OFF the deft branch-protection policy.\n" +
12
12
  " \u2022 Pre-commit + pre-push hooks will no longer block default-branch " +
@@ -114,7 +114,7 @@ export function parseShowArgs(argv) {
114
114
  /** Parse argv for the policy CLI (show + set subcommands). */
115
115
  export function parseArgs(argv) {
116
116
  if (argv.length === 0) {
117
- const usage = "usage: policy [show|enforce-branches|allow-direct-commits|enable-value-feedback|clear-value-feedback|disable-directive|enable-directive|resolve] ...";
117
+ const usage = "usage: policy [show|enforce-branches|allow-direct-commits|allow-bot-merge|enable-value-feedback|clear-value-feedback|disable-directive|enable-directive|resolve] ...";
118
118
  return makeSetError(usage);
119
119
  }
120
120
  const cmd = argv[0];
@@ -148,6 +148,7 @@ export function parseArgs(argv) {
148
148
  }
149
149
  if (cmd === "enforce-branches" ||
150
150
  cmd === "allow-direct-commits" ||
151
+ cmd === "allow-bot-merge" ||
151
152
  cmd === "enable-value-feedback" ||
152
153
  cmd === "clear-value-feedback" ||
153
154
  cmd === "disable-directive" ||
@@ -157,13 +158,15 @@ export function parseArgs(argv) {
157
158
  ? policyColonInvocation("enforce-branches")
158
159
  : cmd === "allow-direct-commits"
159
160
  ? policyColonInvocation("allow-direct-commits")
160
- : cmd === "enable-value-feedback"
161
- ? policyColonInvocation("enable-value-feedback")
162
- : cmd === "clear-value-feedback"
163
- ? policyColonInvocation("clear-value-feedback")
164
- : cmd === "disable-directive"
165
- ? policyColonInvocation("disable-directive")
166
- : policyColonInvocation("enable-directive");
161
+ : cmd === "allow-bot-merge"
162
+ ? policyColonInvocation("allow-bot-merge")
163
+ : cmd === "enable-value-feedback"
164
+ ? policyColonInvocation("enable-value-feedback")
165
+ : cmd === "clear-value-feedback"
166
+ ? policyColonInvocation("clear-value-feedback")
167
+ : cmd === "disable-directive"
168
+ ? policyColonInvocation("disable-directive")
169
+ : policyColonInvocation("enable-directive");
167
170
  let note = "";
168
171
  let projectRoot = ".";
169
172
  for (let i = 1; i < argv.length; i += 1) {
@@ -381,6 +384,48 @@ function runEnableDirective(args) {
381
384
  process.stdout.write("Directive opt-out cleared. Run `directive init` or `directive update` to ensure install.\n");
382
385
  return 0;
383
386
  }
387
+ /** Allow agent/bot merge by writing requireHumanMerge=false (#1193). */
388
+ function runAllowBotMerge(args) {
389
+ const projectRoot = pathResolve(args.projectRoot);
390
+ if (!args.confirm) {
391
+ process.stdout.write(`${ALLOW_BOT_MERGE_CAPABILITY_COST}\n\n`);
392
+ process.stdout.write(`Re-run with --confirm to apply: ${policyColonInvocation("allow-bot-merge", " -- --confirm")}\n`);
393
+ return 1;
394
+ }
395
+ try {
396
+ const { changed, auditEntry } = setRequireHumanMerge(projectRoot, {
397
+ requireHumanMerge: false,
398
+ actor: args.actor,
399
+ note: args.note,
400
+ });
401
+ process.stdout.write(`\u2713 plan.policy.requireHumanMerge=false (human merge gate OFF; agent may merge).\n`);
402
+ if (changed) {
403
+ process.stdout.write(` audit: meta/policy-changes.log :: ${auditEntry}\n`);
404
+ }
405
+ else {
406
+ process.stdout.write(" no-op: value already matched (audit entry still appended for trail).\n");
407
+ }
408
+ const line = humanMergeDisclosureLine(resolveHumanMergePolicy(projectRoot));
409
+ if (line !== null) {
410
+ process.stdout.write(`${line}\n`);
411
+ }
412
+ else {
413
+ process.stdout.write("[deft policy] Human merge gate is OFF; agent may merge when other gates allow.\n");
414
+ }
415
+ return 0;
416
+ }
417
+ catch (err) {
418
+ const message = err instanceof Error ? err.message : String(err);
419
+ if (message.includes("PROJECT-DEFINITION not found")) {
420
+ process.stderr.write(`\u274c ${message}\n`);
421
+ const pdRel = relative(projectRoot, projectDefinitionPath(projectRoot));
422
+ process.stderr.write(` Recovery: run \`task setup\` to generate ${pdRel}.\n`);
423
+ return 2;
424
+ }
425
+ process.stderr.write(`\u274c Config error: ${message}\n`);
426
+ return 2;
427
+ }
428
+ }
384
429
  /** Run the policy CLI; returns process exit code. */
385
430
  export function run(argv) {
386
431
  const args = parseArgs(argv);
@@ -402,6 +447,9 @@ export function run(argv) {
402
447
  if (args.cmd === "enforce-branches" || args.cmd === "allow-direct-commits") {
403
448
  return runSet(args);
404
449
  }
450
+ if (args.cmd === "allow-bot-merge") {
451
+ return runAllowBotMerge(args);
452
+ }
405
453
  if (args.cmd === "enable-value-feedback") {
406
454
  return runEnableValueFeedback(args);
407
455
  }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function run(argv: string[]): number;
3
+ //# sourceMappingURL=pr-finish-loop.d.ts.map
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { cmdPrFinishLoop } from "@deftai/directive-core/dist/finish-loop/main.js";
4
+ export function run(argv) {
5
+ return cmdPrFinishLoop(argv);
6
+ }
7
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
8
+ process.exit(run(process.argv.slice(2)));
9
+ }
10
+ //# sourceMappingURL=pr-finish-loop.js.map
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string;
4
+ enforce: boolean;
5
+ help?: boolean;
6
+ error?: string;
7
+ }
8
+ /** Parse verify-contained-writes CLI args (#2951). */
9
+ export declare function parseArgs(argv: string[]): ParsedArgs;
10
+ /** Run the gate and return the process exit code. */
11
+ export declare function run(argv: string[]): number;
12
+ export {};
13
+ //# sourceMappingURL=verify-contained-writes.d.ts.map
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for task verify:contained-writes (#2951 Phase 1).
4
+ * Fail-open by default; pass --enforce for fail-closed (later phases).
5
+ */
6
+ import { resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { evaluateContainedWrites } from "@deftai/directive-core/verify-source";
9
+ /** Parse verify-contained-writes CLI args (#2951). */
10
+ export function parseArgs(argv) {
11
+ const parsed = { projectRoot: ".", enforce: false };
12
+ for (let i = 0; i < argv.length; i += 1) {
13
+ const arg = argv[i];
14
+ if (arg === "--project-root") {
15
+ const value = argv[i + 1];
16
+ if (value === undefined) {
17
+ return { ...parsed, error: "argument --project-root: expected one argument" };
18
+ }
19
+ parsed.projectRoot = value;
20
+ i += 1;
21
+ }
22
+ else if (arg?.startsWith("--project-root=")) {
23
+ parsed.projectRoot = arg.slice("--project-root=".length);
24
+ }
25
+ else if (arg === "--enforce") {
26
+ parsed.enforce = true;
27
+ }
28
+ else if (arg === "--help" || arg === "-h") {
29
+ return { ...parsed, help: true };
30
+ }
31
+ else {
32
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
33
+ }
34
+ }
35
+ return parsed;
36
+ }
37
+ const HELP_TEXT = "Usage: verify-contained-writes [--project-root <path>] [--enforce]\n" +
38
+ " Inventory raw write sinks outside the allowlist (#2951).\n" +
39
+ " Default: fail-open (exit 0 with advisory report).\n" +
40
+ " --enforce: fail closed (exit 1) when non-allowlisted sinks remain.\n";
41
+ /** Run the gate and return the process exit code. */
42
+ export function run(argv) {
43
+ const args = parseArgs(argv);
44
+ if (args.help === true) {
45
+ process.stdout.write(HELP_TEXT);
46
+ return 0;
47
+ }
48
+ if (args.error !== undefined) {
49
+ process.stderr.write(`verify_contained_writes: ${args.error}\n`);
50
+ return 2;
51
+ }
52
+ const result = evaluateContainedWrites({
53
+ projectRoot: resolve(args.projectRoot),
54
+ enforce: args.enforce,
55
+ });
56
+ if (result.stream === "stdout") {
57
+ process.stdout.write(`${result.message}\n`);
58
+ }
59
+ else {
60
+ process.stderr.write(`${result.message}\n`);
61
+ }
62
+ return result.code;
63
+ }
64
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
65
+ process.exit(run(process.argv.slice(2)));
66
+ }
67
+ //# sourceMappingURL=verify-contained-writes.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive",
3
- "version": "0.88.0",
3
+ "version": "0.89.0",
4
4
  "description": "Directive CLI — npm install path for the Deft Directive framework.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://deftai.github.io/directive/",
@@ -34,8 +34,8 @@
34
34
  "provenance": true
35
35
  },
36
36
  "dependencies": {
37
- "@deftai/directive-core": "^0.88.0",
38
- "@deftai/directive-content": "^0.88.0"
37
+ "@deftai/directive-core": "^0.89.0",
38
+ "@deftai/directive-content": "^0.89.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b"