@deftai/directive 0.87.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 = {
@@ -70,10 +71,22 @@ export const SUBCOMMAND_ROUTES = {
70
71
  "cache:prune": ["cache", "prune"],
71
72
  "cache:clear": ["cache", "clear"],
72
73
  "policy:show": ["policy", "show"],
74
+ "policy:disable-directive": ["policy", "disable-directive"],
75
+ "policy:enable-directive": ["policy", "enable-directive"],
73
76
  "policy:enforce-branches": ["policy", "enforce-branches"],
74
77
  "policy:allow-direct-commits": ["policy", "allow-direct-commits"],
78
+ "policy:allow-bot-merge": ["policy", "allow-bot-merge"],
75
79
  "policy:enable-value-feedback": ["policy", "enable-value-feedback"],
76
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"],
77
90
  "product-signal:status": ["product-signal", "status"],
78
91
  "product-signal:enable": ["product-signal", "enable"],
79
92
  "product-signal:consent": ["product-signal", "consent"],
@@ -87,9 +100,11 @@ export const SUBCOMMAND_ROUTES = {
87
100
  "github-body:issue-create": ["github-body", "issue-create"],
88
101
  "github-body:issue-edit": ["github-body", "issue-edit"],
89
102
  "github-body:issue-fetch": ["github-body", "issue-fetch"],
103
+ "github-body:issue-lint": ["github-body", "issue-lint"],
90
104
  "github-body:comment-create": ["github-body", "comment-create"],
91
105
  "github-body:comment-edit": ["github-body", "comment-edit"],
92
106
  "github-body:pr-edit": ["github-body", "pr-edit"],
107
+ "github-body:pr-lint": ["github-body", "pr-lint"],
93
108
  "plan-sequence:set": ["plan-sequence", "set"],
94
109
  "plan-sequence:current": ["plan-sequence", "current"],
95
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,10 +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",
204
+ "policy:disable-directive": "disable-directive",
205
+ "policy:enable-directive": "enable-directive",
198
206
  };
199
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"]));
200
225
  /** Colon aliases for plan-sequence subcommands (#2402). */
201
226
  export const PLAN_SEQUENCE_ALIAS_SUBCOMMANDS = {
202
227
  "plan-sequence:set": "set",
@@ -249,6 +274,7 @@ export const VERB_ALIASES = {
249
274
  "verify:rule-ownership": "rule-ownership-lint",
250
275
  "rule:ownership-lint": "rule-ownership-lint",
251
276
  "verify:biome-config": "verify-biome-config",
277
+ "verify:contained-writes": "verify-contained-writes",
252
278
  "verify:content-manifest": "verify-content-manifest",
253
279
  "verify:skill-external-fetch-gate": "verify-skill-external-fetch-gate",
254
280
  "verify:contract-drift": "verify-contract-drift",
@@ -273,6 +299,8 @@ export const VERB_ALIASES = {
273
299
  "triage:scope": "triage-scope",
274
300
  ...TRIAGE_ACTION_COLON_ALIASES,
275
301
  ...POLICY_ACTION_COLON_ALIASES,
302
+ ...AUTHZ_ACTION_COLON_ALIASES,
303
+ ...ESCALATION_ACTION_COLON_ALIASES,
276
304
  ...PRODUCT_SIGNAL_COLON_ALIASES,
277
305
  "agents:refresh": "agents-refresh",
278
306
  "migrate:preflight": "migrate-preflight",
@@ -293,6 +321,8 @@ export const VERB_ALIASES = {
293
321
  "docs:rule-map": "rule-map",
294
322
  "project:export-spec": "export-spec",
295
323
  "pr:watch": "pr-watch",
324
+ "pr:finish-loop": "pr-finish-loop",
325
+ "directive:finish-loop": "directive-finish-loop",
296
326
  doctor: "doctor",
297
327
  "eval:health": "eval-health",
298
328
  "feedback:file": "feedback-file",
@@ -309,6 +339,7 @@ const SUBDIR_CLI_STEMS = {
309
339
  "verify-stubs": "verify-source-cli/verify-stubs",
310
340
  "rule-ownership-lint": "verify-source-cli/rule-ownership-lint",
311
341
  "verify-biome-config": "verify-source-cli/verify-biome-config",
342
+ "verify-contained-writes": "verify-source-cli/verify-contained-writes",
312
343
  "verify-content-manifest": "verify-source-cli/verify-content-manifest",
313
344
  "verify-skill-external-fetch-gate": "verify-source-cli/verify-skill-external-fetch-gate",
314
345
  "verify-contract-drift": "verify-source-cli/verify-contract-drift",
@@ -2596,6 +2627,8 @@ export async function dispatch(argv, io = defaultIo()) {
2596
2627
  const handler = await loadHandler(canonical, io);
2597
2628
  const triageSubcommand = verb !== undefined ? TRIAGE_ACTION_ALIAS_SUBCOMMANDS[verb] : undefined;
2598
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;
2599
2632
  const planSequenceSubcommand = verb !== undefined ? PLAN_SEQUENCE_ALIAS_SUBCOMMANDS[verb] : undefined;
2600
2633
  const productSignalSubcommand = verb !== undefined ? PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS[verb] : undefined;
2601
2634
  const handlerArgv = canonical === "framework-commands" && verb !== undefined && verb !== canonical
@@ -2604,11 +2637,15 @@ export async function dispatch(argv, io = defaultIo()) {
2604
2637
  ? [triageSubcommand, ...rest]
2605
2638
  : policySubcommand !== undefined && canonical === "policy"
2606
2639
  ? [policySubcommand, ...rest]
2607
- : planSequenceSubcommand !== undefined && canonical === "plan-sequence"
2608
- ? [planSequenceSubcommand, ...rest]
2609
- : productSignalSubcommand !== undefined && canonical === "product-signal"
2610
- ? [productSignalSubcommand, ...rest]
2611
- : 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;
2612
2649
  return await invokeHandler(handler, handlerArgv);
2613
2650
  }
2614
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" | "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, describeShadowedPlanExtension, detectShadowedPlanExtensions, disclosureLine, enableValueFeedback, FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, formatValueFeedbackStatusLine, inspectAllPolicies, inspectOnePolicy, loadProjectDefinition, policyColonInvocation, projectDefinitionPath, pythonListRepr, pythonStringRepr, registeredPolicyNames, 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|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,16 +148,25 @@ 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
- cmd === "clear-value-feedback") {
153
+ cmd === "clear-value-feedback" ||
154
+ cmd === "disable-directive" ||
155
+ cmd === "enable-directive") {
153
156
  let confirm = false;
154
157
  let actor = cmd === "enforce-branches"
155
158
  ? policyColonInvocation("enforce-branches")
156
159
  : cmd === "allow-direct-commits"
157
160
  ? policyColonInvocation("allow-direct-commits")
158
- : cmd === "enable-value-feedback"
159
- ? policyColonInvocation("enable-value-feedback")
160
- : policyColonInvocation("clear-value-feedback");
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");
161
170
  let note = "";
162
171
  let projectRoot = ".";
163
172
  for (let i = 1; i < argv.length; i += 1) {
@@ -338,6 +347,85 @@ function runClearValueFeedback(args) {
338
347
  process.stdout.write(result.stdout);
339
348
  return result.exitCode;
340
349
  }
350
+ /** Create root `.no-deft-directive` opt-out flag (#2926). Does not delete deposits. */
351
+ function runDisableDirective(args) {
352
+ const projectRoot = pathResolve(args.projectRoot);
353
+ const before = detectNoDeftDirective(projectRoot);
354
+ if (before.present) {
355
+ process.stdout.write(`${NO_DEFT_DIRECTIVE_DISABLED_MESSAGE} (already present)\n`);
356
+ if (before.inconsistent) {
357
+ process.stderr.write(`${NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE}\n`);
358
+ return 1;
359
+ }
360
+ return 0;
361
+ }
362
+ const path = createNoDeftDirectiveFlag(projectRoot, {
363
+ rationale: args.note.trim().length > 0 ? args.note.trim() : undefined,
364
+ });
365
+ process.stdout.write(`Created ${NO_DEFT_DIRECTIVE_FLAG_NAME} at ${path}\n`);
366
+ process.stdout.write(`${NO_DEFT_DIRECTIVE_DISABLED_MESSAGE}\n`);
367
+ const after = detectNoDeftDirective(projectRoot);
368
+ if (after.inconsistent) {
369
+ process.stderr.write(`${NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE}\n`);
370
+ return 1;
371
+ }
372
+ return 0;
373
+ }
374
+ /** Remove root `.no-deft-directive` so Directive may be installed/used again (#2926). */
375
+ function runEnableDirective(args) {
376
+ const projectRoot = pathResolve(args.projectRoot);
377
+ const removed = removeNoDeftDirectiveFlag(projectRoot);
378
+ if (removed) {
379
+ process.stdout.write(`Removed ${NO_DEFT_DIRECTIVE_FLAG_NAME}\n`);
380
+ }
381
+ else {
382
+ process.stdout.write(`${NO_DEFT_DIRECTIVE_FLAG_NAME} was not present\n`);
383
+ }
384
+ process.stdout.write("Directive opt-out cleared. Run `directive init` or `directive update` to ensure install.\n");
385
+ return 0;
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
+ }
341
429
  /** Run the policy CLI; returns process exit code. */
342
430
  export function run(argv) {
343
431
  const args = parseArgs(argv);
@@ -359,12 +447,21 @@ export function run(argv) {
359
447
  if (args.cmd === "enforce-branches" || args.cmd === "allow-direct-commits") {
360
448
  return runSet(args);
361
449
  }
450
+ if (args.cmd === "allow-bot-merge") {
451
+ return runAllowBotMerge(args);
452
+ }
362
453
  if (args.cmd === "enable-value-feedback") {
363
454
  return runEnableValueFeedback(args);
364
455
  }
365
456
  if (args.cmd === "clear-value-feedback") {
366
457
  return runClearValueFeedback(args);
367
458
  }
459
+ if (args.cmd === "disable-directive") {
460
+ return runDisableDirective(args);
461
+ }
462
+ if (args.cmd === "enable-directive") {
463
+ return runEnableDirective(args);
464
+ }
368
465
  return 2;
369
466
  }
370
467
  if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
@@ -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,7 +1,9 @@
1
1
  {
2
2
  "name": "@deftai/directive",
3
- "version": "0.87.0",
3
+ "version": "0.89.0",
4
4
  "description": "Directive CLI — npm install path for the Deft Directive framework.",
5
+ "license": "MIT",
6
+ "homepage": "https://deftai.github.io/directive/",
5
7
  "type": "module",
6
8
  "main": "./dist/index.js",
7
9
  "types": "./dist/index.d.ts",
@@ -32,8 +34,8 @@
32
34
  "provenance": true
33
35
  },
34
36
  "dependencies": {
35
- "@deftai/directive-core": "^0.87.0",
36
- "@deftai/directive-content": "^0.87.0"
37
+ "@deftai/directive-core": "^0.89.0",
38
+ "@deftai/directive-content": "^0.89.0"
37
39
  },
38
40
  "scripts": {
39
41
  "build": "tsc -b"