@deftai/directive 0.93.0 → 0.95.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.
package/dist/authz.d.ts CHANGED
@@ -1,4 +1,32 @@
1
1
  #!/usr/bin/env node
2
- export declare function main(argv?: string[]): number;
2
+ /**
3
+ * Env markers that indicate an agent/host/CI shell even when stdin reports a TTY
4
+ * (pseudo-terminal residual; #3110 Greptile). Presence refuses mutating authz.
5
+ * Expanded for dogfood conf 5/5 — markers are fail-closed (any non-empty value).
6
+ */
7
+ export declare const AUTHZ_AGENT_SHELL_ENV_MARKERS: readonly ["CLAUDECODE", "CLAUDE_CODE", "CLAUDE_CODE_ENTRYPOINT", "CURSOR_AGENT", "CURSOR_TRACE_ID", "CURSOR_SESSION_ID", "AIDER", "CONTINUE_CLI", "CODEX_SANDBOX", "CODEX_CI", "OPENAI_CODEX", "OPENCLAW", "OPENCLAW_STATE_DIR", "DEFT_PROBE_OPENCLAW", "DEFT_HOOK_HOST", "DEFT_AGENT_SHELL", "DEFT_AGENT_RUNTIME", "WARP_SESSION_ID", "WARP_HARNESS", "WARP_RUN_ID", "GEMINI_CLI", "AMP_CLI", "SWARM_AGENT", "AI_AGENT", "CI", "CONTINUOUS_INTEGRATION", "GITHUB_ACTIONS", "GITLAB_CI", "CIRCLECI", "BUILDKITE", "TRAVIS", "JENKINS_URL", "TEAMCITY_VERSION", "TF_BUILD", "APPVEYOR", "BITBUCKET_BUILD_NUMBER", "CODEBUILD_BUILD_ID"];
8
+ /** Phrase an operator must type on the controlling TTY after --confirm (#3110). */
9
+ export declare const AUTHZ_INTERACTIVE_CONFIRM_PHRASE = "mint";
10
+ /** Testable seams for TTY / agent-shell detection (#3110). */
11
+ export interface AuthzMainSeams {
12
+ /**
13
+ * When true, interactive human TTY is present.
14
+ * Default: both stdin and stdout report isTTY (pseudo-TTY residual; #3110).
15
+ */
16
+ readonly isTty?: () => boolean;
17
+ /** Environ for agent-shell marker detection (default: process.env). */
18
+ readonly environ?: NodeJS.ProcessEnv;
19
+ /**
20
+ * True when a controlling terminal device is available (`/dev/tty` or `CONIN$`).
21
+ * Default: open/close the platform controlling terminal (fail-closed on error).
22
+ */
23
+ readonly hasControllingTerminal?: () => boolean;
24
+ /**
25
+ * Read one interactive confirmation line from the operator (trimmed).
26
+ * Default: one line from stdin. Tests inject a fixed phrase.
27
+ */
28
+ readonly readInteractiveConfirm?: () => string | null;
29
+ }
30
+ export declare function main(argv?: string[], seams?: AuthzMainSeams): number;
3
31
  export default main;
4
32
  //# sourceMappingURL=authz.d.ts.map
package/dist/authz.js CHANGED
@@ -1,17 +1,75 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Authz CLI (#2944 Wave 1 + #1095 Wave 4): human-origin grants + UAT lease +
3
+ * Authz CLI (#2944 Wave 1 + #1095 Wave 4 + #3110): human-origin grants + UAT lease +
4
4
  * AFK closed-verb templates (mint via mintHumanOriginGrant only).
5
5
  *
6
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>
7
+ * deft authz:uat-start -- --campaign <id> [--actor <name>] [--note <text>] [--confirm]
8
+ * deft authz:uat-suspend [--confirm]
9
+ * deft authz:grant -- --operations edit,push --surfaces 'src/**' --cohort <id> ... [--confirm]
10
+ * deft authz:grant -- --template release-publish --target 0.30.0 [--confirm]
11
+ * deft authz:grant -- --template finish-loop [--confirm]
12
+ * deft authz:revoke -- <grant-id> [--confirm]
13
+ *
14
+ * **UAT-active hard refuse (#3110):** while any UAT lease is active, ALL mutating
15
+ * verbs (`uat-start`, `uat-suspend`, `grant`, `revoke`) refuse unconditionally —
16
+ * no TTY, no `--confirm`, no typed phrase path. Self-approval under UAT is
17
+ * impossible by construction (agent PTY cannot mint operator-cli authority).
18
+ *
19
+ * **Outside UAT:** mutating verbs require multi-factor human presence: interactive
20
+ * TTY + controlling terminal + `--confirm` + typed phrase `mint`; agent/CI env
21
+ * markers refuse fail-closed. Argv `--confirm` alone is never enough.
22
+ */
23
+ import { closeSync, openSync, readSync } from "node:fs";
24
+ import { AFK_TEMPLATE_NAMES, AUTHZ_OPERATIONS, CLOSED_VERB_TEMPLATE_NAMES, FINISH_LOOP_TEMPLATE_NAME, isAfkTemplateName, isClosedVerbTemplateName, isFinishLoopTemplateName, loadAuthzState, mintAfkTemplateGrant, mintHumanOriginGrant, revokeGrant, showAuthzSnapshot, startUatLease, suspendUatLease, } from "@deftai/directive-core/authz";
25
+ /**
26
+ * Env markers that indicate an agent/host/CI shell even when stdin reports a TTY
27
+ * (pseudo-terminal residual; #3110 Greptile). Presence refuses mutating authz.
28
+ * Expanded for dogfood conf 5/5 — markers are fail-closed (any non-empty value).
13
29
  */
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";
30
+ export const AUTHZ_AGENT_SHELL_ENV_MARKERS = [
31
+ // Coding agents / IDEs
32
+ "CLAUDECODE",
33
+ "CLAUDE_CODE",
34
+ "CLAUDE_CODE_ENTRYPOINT",
35
+ "CURSOR_AGENT",
36
+ "CURSOR_TRACE_ID",
37
+ "CURSOR_SESSION_ID",
38
+ "AIDER",
39
+ "CONTINUE_CLI",
40
+ "CODEX_SANDBOX",
41
+ "CODEX_CI",
42
+ "OPENAI_CODEX",
43
+ "OPENCLAW",
44
+ "OPENCLAW_STATE_DIR",
45
+ "DEFT_PROBE_OPENCLAW",
46
+ "DEFT_HOOK_HOST",
47
+ "DEFT_AGENT_SHELL",
48
+ "DEFT_AGENT_RUNTIME",
49
+ "WARP_SESSION_ID",
50
+ "WARP_HARNESS",
51
+ "WARP_RUN_ID",
52
+ "GEMINI_CLI",
53
+ "AMP_CLI",
54
+ "SWARM_AGENT",
55
+ "AI_AGENT",
56
+ // CI / automation (never a human interactive operator mint)
57
+ "CI",
58
+ "CONTINUOUS_INTEGRATION",
59
+ "GITHUB_ACTIONS",
60
+ "GITLAB_CI",
61
+ "CIRCLECI",
62
+ "BUILDKITE",
63
+ "TRAVIS",
64
+ "JENKINS_URL",
65
+ "TEAMCITY_VERSION",
66
+ "TF_BUILD",
67
+ "APPVEYOR",
68
+ "BITBUCKET_BUILD_NUMBER",
69
+ "CODEBUILD_BUILD_ID",
70
+ ];
71
+ /** Phrase an operator must type on the controlling TTY after --confirm (#3110). */
72
+ export const AUTHZ_INTERACTIVE_CONFIRM_PHRASE = "mint";
15
73
  function parseOps(raw) {
16
74
  const allowed = new Set(AUTHZ_OPERATIONS);
17
75
  const out = [];
@@ -47,6 +105,7 @@ function parseArgv(argv) {
47
105
  template: null,
48
106
  target: null,
49
107
  format: "text",
108
+ confirm: false,
50
109
  };
51
110
  const args = [...argv];
52
111
  // Drop leading `--` separators from task-style invocation.
@@ -165,6 +224,10 @@ function parseArgv(argv) {
165
224
  base.target = args[++i] ?? null;
166
225
  continue;
167
226
  }
227
+ if (a === "--confirm") {
228
+ base.confirm = true;
229
+ continue;
230
+ }
168
231
  if (!a.startsWith("-") && base.cmd === "revoke" && base.grantId === null) {
169
232
  base.grantId = a;
170
233
  continue;
@@ -179,16 +242,26 @@ function helpText() {
179
242
  return [
180
243
  "Usage:",
181
244
  " deft authz:show [--format json]",
182
- " deft authz:uat-start -- --campaign <id> [--actor <name>] [--note <text>]",
183
- " deft authz:uat-suspend",
245
+ " deft authz:uat-start -- --campaign <id> [--actor <name>] [--note <text>] [--confirm]",
246
+ " deft authz:uat-suspend [--confirm]",
184
247
  " 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>",
248
+ " [--stories 2944] [--plan-ref <id>] [--repo owner/name] [--branch <b>] [--expires ISO] [--confirm]",
249
+ " deft authz:grant -- --template release-publish --target 0.30.0 [--actor <name>] [--expires ISO] [--confirm]",
250
+ " deft authz:grant -- --template finish-loop [--actor <name>] [--expires ISO] [--confirm]",
251
+ " deft authz:revoke -- <grant-id> [--confirm]",
189
252
  "",
190
253
  "Human-origin grants are minted only via this CLI (origin.kind=operator-cli).",
191
254
  "Self-authored xBRIEF/lifecycle/dispatch tokens never satisfy implement gates (#2944).",
255
+ "While any UAT lease is ACTIVE (#3110): ALL mutating verbs refuse unconditionally",
256
+ " (grant / uat-start / uat-suspend / revoke) — no TTY, --confirm, or phrase path.",
257
+ " Self-approval under UAT is impossible by construction. Mint grants BEFORE uat-start.",
258
+ "Outside UAT, mutating verbs require multi-factor human presence (#3110):",
259
+ " - Interactive TTY (stdin+stdout) + controlling terminal (/dev/tty|CONIN$)",
260
+ " - Explicit --confirm (argv flag alone never enough)",
261
+ " - Typed phrase 'mint' on the controlling TTY (PTY+--confirm alone never enough)",
262
+ " - Known agent/CI env markers always refuse (fail-closed).",
263
+ " End UAT only after the lease is cleared out-of-band (state edit / suspend path",
264
+ " that does not run while the lease is active) — or suspend before re-minting.",
192
265
  "",
193
266
  `AFK templates (#1095 / #871): ${AFK_TEMPLATE_NAMES.join(", ")}`,
194
267
  ` Closed-verb (#1095): ${CLOSED_VERB_TEMPLATE_NAMES.join(", ")} — require --target`,
@@ -197,7 +270,131 @@ function helpText() {
197
270
  " Env bypass for a single shell: DEFT_ALLOW_RELEASE_PUBLISH=1 / DEFT_ALLOW_FINISH_LOOP=1.",
198
271
  ].join("\n");
199
272
  }
200
- export function main(argv = process.argv.slice(2)) {
273
+ function looksLikeAgentShell(environ) {
274
+ for (const key of AUTHZ_AGENT_SHELL_ENV_MARKERS) {
275
+ const v = environ[key];
276
+ if (v !== undefined && String(v).trim().length > 0)
277
+ return true;
278
+ }
279
+ return false;
280
+ }
281
+ function defaultHasControllingTerminal() {
282
+ try {
283
+ const path = process.platform === "win32" ? "CONIN$" : "/dev/tty";
284
+ const fd = openSync(path, "r");
285
+ closeSync(fd);
286
+ return true;
287
+ }
288
+ catch {
289
+ return false;
290
+ }
291
+ }
292
+ function defaultReadInteractiveConfirm() {
293
+ // Read from the controlling terminal device — not redirected/piped stdin —
294
+ // so agent-controlled stdin alone cannot supply the confirm phrase (#3110).
295
+ let fd = null;
296
+ try {
297
+ const path = process.platform === "win32" ? "CONIN$" : "/dev/tty";
298
+ fd = openSync(path, "r");
299
+ const buf = Buffer.alloc(256);
300
+ const n = readSync(fd, buf, 0, buf.length, null);
301
+ if (n <= 0)
302
+ return null;
303
+ return buf.subarray(0, n).toString("utf8").trim();
304
+ }
305
+ catch {
306
+ return null;
307
+ }
308
+ finally {
309
+ if (fd !== null) {
310
+ try {
311
+ closeSync(fd);
312
+ }
313
+ catch {
314
+ /* ignore */
315
+ }
316
+ }
317
+ }
318
+ }
319
+ /**
320
+ * While any UAT lease is active, refuse ALL mutating authz CLI verbs (#3110).
321
+ *
322
+ * No multi-factor escape: TTY, `--confirm`, and typed phrase never authorize.
323
+ * Self-approval under UAT is impossible by construction — agents with a PTY
324
+ * cannot mint/suspend/revoke operator-cli authority during an active lease.
325
+ * Operators mint fix-cohort grants *before* `uat-start`.
326
+ *
327
+ * Returns exit code when blocked, or null when the command may continue.
328
+ */
329
+ function refuseMutatingAuthzWhileUatActive(projectRoot, cmd) {
330
+ if (cmd === "show")
331
+ return null;
332
+ const state = loadAuthzState(projectRoot);
333
+ if (state.uat === null || !state.uat.active)
334
+ return null;
335
+ process.stderr.write(`authz:${cmd}: refusing mutating authz while UAT lease is ACTIVE ` +
336
+ `(campaign=${state.uat.campaignId}). Under active UAT, grant / uat-start / ` +
337
+ "uat-suspend / revoke are hard-refused — no TTY, --confirm, or phrase path " +
338
+ "authorizes self-approval (#3110). Mint grants before uat-start; clear the " +
339
+ "lease out-of-band (edit .deft/authz/state.json as a human outside agent hooks) " +
340
+ "to end UAT.\n");
341
+ return 2;
342
+ }
343
+ /**
344
+ * Refuse non-interactive / agent-shell operator-cli stamps outside UAT (#3110).
345
+ *
346
+ * Multi-factor human-presence gate (applies only when UAT lease is inactive):
347
+ * 1. No known agent/CI env markers
348
+ * 2. Interactive TTY (stdin + stdout isTTY)
349
+ * 3. Controlling terminal device present (`/dev/tty` / `CONIN$`)
350
+ * 4. Explicit argv `--confirm` (flag alone never enough)
351
+ * 5. Interactive typed phrase `mint` (argv --confirm alone never enough even on PTY)
352
+ *
353
+ * Fail-closed: if a real human interactive path cannot be proven, refuse mint.
354
+ * Returns an exit code when blocked, or null when the mutation may proceed.
355
+ */
356
+ function refuseNonInteractiveMint(cmd, isTty, environ, confirm, hasControllingTerminal, readInteractiveConfirm) {
357
+ if (cmd === "show")
358
+ return null;
359
+ if (looksLikeAgentShell(environ)) {
360
+ process.stderr.write(`authz:${cmd}: refusing operator-cli stamp from an agent/host/CI shell ` +
361
+ `(detected agent or CI env marker). Mutating authz requires a human interactive ` +
362
+ "TTY without agent-shell markers, plus --confirm and typed phrase (#3110).\n");
363
+ return 2;
364
+ }
365
+ const tty = isTty();
366
+ if (!tty && !confirm) {
367
+ process.stderr.write(`authz:${cmd}: refusing non-interactive operator-cli stamp. ` +
368
+ "Mutating authz verbs require interactive TTY, --confirm, and typed phrase " +
369
+ `'${AUTHZ_INTERACTIVE_CONFIRM_PHRASE}' (#3110).\n`);
370
+ return 2;
371
+ }
372
+ if (!tty) {
373
+ process.stderr.write(`authz:${cmd}: refusing non-TTY operator-cli stamp. ` +
374
+ "--confirm alone never authorizes mint — interactive TTY is required (#3110).\n");
375
+ return 2;
376
+ }
377
+ if (!confirm) {
378
+ process.stderr.write(`authz:${cmd}: refusing operator-cli stamp without --confirm. ` +
379
+ "Interactive TTY alone never authorizes mint — pass --confirm explicitly (#3110).\n");
380
+ return 2;
381
+ }
382
+ if (!hasControllingTerminal()) {
383
+ process.stderr.write(`authz:${cmd}: refusing operator-cli stamp without a controlling terminal. ` +
384
+ "Open a real interactive console (not a headless/agent pipe) to mint (#3110).\n");
385
+ return 2;
386
+ }
387
+ process.stderr.write(`authz:${cmd}: type '${AUTHZ_INTERACTIVE_CONFIRM_PHRASE}' and press Enter to confirm operator mint: `);
388
+ const line = readInteractiveConfirm();
389
+ const phrase = (line ?? "").trim().toLowerCase();
390
+ if (phrase !== AUTHZ_INTERACTIVE_CONFIRM_PHRASE) {
391
+ process.stderr.write(`\nauthz:${cmd}: interactive confirm phrase mismatch (got ${JSON.stringify(line ?? "")}). ` +
392
+ `Type exactly '${AUTHZ_INTERACTIVE_CONFIRM_PHRASE}' on the controlling TTY (#3110).\n`);
393
+ return 2;
394
+ }
395
+ return null;
396
+ }
397
+ export function main(argv = process.argv.slice(2), seams = {}) {
201
398
  const args = parseArgv(argv);
202
399
  if (args.error === "help") {
203
400
  process.stdout.write(`${helpText()}\n`);
@@ -208,6 +405,23 @@ export function main(argv = process.argv.slice(2)) {
208
405
  process.stderr.write(`${helpText()}\n`);
209
406
  return 2;
210
407
  }
408
+ // Both stdin and stdout TTY — agent-allocated single-side PTY is not enough.
409
+ const isTty = seams.isTty ?? (() => process.stdin.isTTY === true && process.stdout.isTTY === true);
410
+ const environ = seams.environ ?? process.env;
411
+ const hasControllingTerminal = seams.hasControllingTerminal ?? defaultHasControllingTerminal;
412
+ const readInteractiveConfirm = seams.readInteractiveConfirm ?? defaultReadInteractiveConfirm;
413
+ /**
414
+ * Mutating-verb gate stack (#3110):
415
+ * 1. Active UAT → hard refuse (no multi-factor escape; self-approval impossible)
416
+ * 2. Else multi-factor: TTY + controlling tty + --confirm + typed phrase; agent/CI markers refuse
417
+ * Required-arg validation runs before this so missing --campaign / --ops still report clearly.
418
+ */
419
+ const gateConfirm = () => {
420
+ const uatBlocked = refuseMutatingAuthzWhileUatActive(args.projectRoot, args.cmd);
421
+ if (uatBlocked !== null)
422
+ return uatBlocked;
423
+ return refuseNonInteractiveMint(args.cmd, isTty, environ, args.confirm, hasControllingTerminal, readInteractiveConfirm);
424
+ };
211
425
  try {
212
426
  switch (args.cmd) {
213
427
  case "show": {
@@ -242,6 +456,9 @@ export function main(argv = process.argv.slice(2)) {
242
456
  process.stderr.write("authz:uat-start requires --campaign <id>\n");
243
457
  return 2;
244
458
  }
459
+ const blocked = gateConfirm();
460
+ if (blocked !== null)
461
+ return blocked;
245
462
  const { lease } = startUatLease({
246
463
  projectRoot: args.projectRoot,
247
464
  campaignId: args.campaign,
@@ -254,6 +471,9 @@ export function main(argv = process.argv.slice(2)) {
254
471
  return 0;
255
472
  }
256
473
  case "uat-suspend": {
474
+ const blocked = gateConfirm();
475
+ if (blocked !== null)
476
+ return blocked;
257
477
  const state = suspendUatLease({
258
478
  projectRoot: args.projectRoot,
259
479
  actor: args.actor,
@@ -278,6 +498,9 @@ export function main(argv = process.argv.slice(2)) {
278
498
  process.stderr.write(`authz:grant --template ${args.template} requires --target <version>\n`);
279
499
  return 2;
280
500
  }
501
+ const blocked = gateConfirm();
502
+ if (blocked !== null)
503
+ return blocked;
281
504
  const grant = mintAfkTemplateGrant({
282
505
  projectRoot: args.projectRoot,
283
506
  template: args.template,
@@ -309,6 +532,11 @@ export function main(argv = process.argv.slice(2)) {
309
532
  process.stderr.write("authz:grant requires --operations <edit,push,...> or --template <finish-loop|release-*> \n");
310
533
  return 2;
311
534
  }
535
+ {
536
+ const blocked = gateConfirm();
537
+ if (blocked !== null)
538
+ return blocked;
539
+ }
312
540
  const grant = mintHumanOriginGrant({
313
541
  projectRoot: args.projectRoot,
314
542
  actor: args.actor,
@@ -335,6 +563,11 @@ export function main(argv = process.argv.slice(2)) {
335
563
  process.stderr.write("authz:revoke requires <grant-id>\n");
336
564
  return 2;
337
565
  }
566
+ {
567
+ const blocked = gateConfirm();
568
+ if (blocked !== null)
569
+ return blocked;
570
+ }
338
571
  const revoked = revokeGrant({
339
572
  projectRoot: args.projectRoot,
340
573
  grantId: args.grantId,
@@ -10,9 +10,9 @@ 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", "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", "lifecycle-stats", "session-start", "session-ready", "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", "xbrief-create", "xbrief-verify", "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"];
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", "lifecycle-stats", "session-start", "session-ready", "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", "xbrief-create", "xbrief-verify", "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-l4-owner", "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
- export declare const CORE_MODULE_VERBS: readonly ["scm", "scm-readiness", "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"];
15
+ export declare const CORE_MODULE_VERBS: readonly ["scm", "scm-readiness", "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", "freshness-report"];
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). */
@@ -25,6 +25,8 @@ export declare const ESCALATION_ACTION_ALIAS_SUBCOMMANDS: Readonly<Record<string
25
25
  export declare const PLAN_SEQUENCE_ALIAS_SUBCOMMANDS: Readonly<Record<string, string>>;
26
26
  /** Colon aliases for product-signal subcommands (#2693). */
27
27
  export declare const PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS: Readonly<Record<string, string>>;
28
+ /** Colon aliases for freshness subcommands (#3117). */
29
+ export declare const FRESHNESS_ALIAS_SUBCOMMANDS: Readonly<Record<string, string>>;
28
30
  /** Task-style aliases (framework_commands / Taskfile names). */
29
31
  export declare const VERB_ALIASES: Readonly<Record<string, string>>;
30
32
  /** Pinned ghx version (display only) — keep in lockstep with .github/workflows/ci.yml env.GHX_VERSION. */
package/dist/dispatch.js CHANGED
@@ -126,6 +126,7 @@ export const CLI_MODULE_VERBS = [
126
126
  "rule-ownership-lint",
127
127
  "verify-story-ready",
128
128
  "verify-review-monitor",
129
+ "verify-l4-owner",
129
130
  "verify-subagent-alive",
130
131
  "review-monitor-register",
131
132
  "review-monitor-release",
@@ -185,6 +186,7 @@ export const CORE_MODULE_VERBS = [
185
186
  "feedback-file",
186
187
  "value-readback",
187
188
  "product-signal",
189
+ "freshness-report",
188
190
  ];
189
191
  /** Colon aliases for triage-actions (mirrors cli-router SUBCOMMAND_ROUTES). */
190
192
  export const TRIAGE_ACTION_ALIAS_SUBCOMMANDS = {
@@ -244,6 +246,14 @@ export const PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS = {
244
246
  "product-signal:bootstrap-sink": "bootstrap-sink",
245
247
  };
246
248
  const PRODUCT_SIGNAL_COLON_ALIASES = Object.fromEntries(Object.keys(PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS).map((alias) => [alias, "product-signal"]));
249
+ /** Colon aliases for freshness subcommands (#3117). */
250
+ export const FRESHNESS_ALIAS_SUBCOMMANDS = {
251
+ "freshness:report": "report",
252
+ "freshness:bind": "bind",
253
+ "session:freshness": "report",
254
+ freshness: "report",
255
+ };
256
+ const FRESHNESS_COLON_ALIASES = Object.fromEntries(Object.keys(FRESHNESS_ALIAS_SUBCOMMANDS).map((alias) => [alias, "freshness-report"]));
247
257
  /** Task-style aliases (framework_commands / Taskfile names). */
248
258
  export const VERB_ALIASES = {
249
259
  "hook:dispatch": "hook-dispatch",
@@ -268,6 +278,7 @@ export const VERB_ALIASES = {
268
278
  "vbrief:activate": "vbrief-activate",
269
279
  "verify:story-ready": "verify-story-ready",
270
280
  "verify:review-monitor": "verify-review-monitor",
281
+ "verify:l4-owner": "verify-l4-owner",
271
282
  "verify:subagent-alive": "verify-subagent-alive",
272
283
  "agent:monitor": "subagent-monitor",
273
284
  "review-monitor:register": "review-monitor-register",
@@ -319,6 +330,7 @@ export const VERB_ALIASES = {
319
330
  upgrade: "install-upgrade",
320
331
  "session:start": "session-start",
321
332
  "session:ready": "session-ready",
333
+ ...FRESHNESS_COLON_ALIASES,
322
334
  "lifecycle:event": "lifecycle-event",
323
335
  "lifecycle:stats": "lifecycle-stats",
324
336
  "toolchain:check": "toolchain-check",
@@ -2443,6 +2455,10 @@ async function loadCoreModuleHandler(verb, io) {
2443
2455
  const { mainEntry } = await import("@deftai/directive-core/dist/product-signal/submit.js");
2444
2456
  return mainEntry;
2445
2457
  }
2458
+ case "freshness-report": {
2459
+ const { mainEntry } = await import("@deftai/directive-core/dist/freshness/cli.js");
2460
+ return mainEntry;
2461
+ }
2446
2462
  default:
2447
2463
  throw new Error(`unknown core verb: ${verb}`);
2448
2464
  }
@@ -2545,6 +2561,14 @@ const CURATED_HELP_GROUPS = [
2545
2561
  name: "session:ready",
2546
2562
  summary: "One-shot recovery to gated write-ready (session + ritual + cache)",
2547
2563
  },
2564
+ {
2565
+ name: "freshness:report",
2566
+ summary: "Bound vs live deposit generation (current|stale_soft|stale_hard)",
2567
+ },
2568
+ {
2569
+ name: "freshness:bind",
2570
+ summary: "Bind live deposit generation into this session (no host restart)",
2571
+ },
2548
2572
  {
2549
2573
  name: "scm:status",
2550
2574
  summary: "Probe gh/ghx + auth readiness in this execution env (#2275)",
@@ -2681,6 +2705,7 @@ export async function dispatch(argv, io = defaultIo()) {
2681
2705
  const escalationSubcommand = verb !== undefined ? ESCALATION_ACTION_ALIAS_SUBCOMMANDS[verb] : undefined;
2682
2706
  const planSequenceSubcommand = verb !== undefined ? PLAN_SEQUENCE_ALIAS_SUBCOMMANDS[verb] : undefined;
2683
2707
  const productSignalSubcommand = verb !== undefined ? PRODUCT_SIGNAL_ALIAS_SUBCOMMANDS[verb] : undefined;
2708
+ const freshnessSubcommand = verb !== undefined ? FRESHNESS_ALIAS_SUBCOMMANDS[verb] : undefined;
2684
2709
  const handlerArgv = canonical === "framework-commands" && verb !== undefined && verb !== canonical
2685
2710
  ? [verb, ...rest]
2686
2711
  : triageSubcommand !== undefined && canonical === "triage-actions"
@@ -2695,7 +2720,9 @@ export async function dispatch(argv, io = defaultIo()) {
2695
2720
  ? [planSequenceSubcommand, ...rest]
2696
2721
  : productSignalSubcommand !== undefined && canonical === "product-signal"
2697
2722
  ? [productSignalSubcommand, ...rest]
2698
- : rest;
2723
+ : freshnessSubcommand !== undefined && canonical === "freshness-report"
2724
+ ? [freshnessSubcommand, ...rest]
2725
+ : rest;
2699
2726
  return await invokeHandler(handler, handlerArgv);
2700
2727
  }
2701
2728
  catch (err) {
@@ -1,13 +1,22 @@
1
1
  #!/usr/bin/env node
2
- interface ParsedArgs {
2
+ import type { LabelClient } from "@deftai/directive-core/dist/vbrief-reconcile/types.js";
3
+ export interface ParsedArgs {
3
4
  projectRoot: string;
4
5
  doList: boolean;
5
6
  doValidate: boolean;
7
+ doMirror: boolean;
8
+ apply: boolean;
9
+ json: boolean;
10
+ repo: string | null;
11
+ allowCrossRepo: boolean;
6
12
  error?: string;
7
13
  }
8
- /** Parse triage-classify CLI args, mirroring the Python argparse surface. */
14
+ /** Parse triage-classify CLI args (#1129 + #1423 Wave 1 mirror flags). */
9
15
  export declare function parseArgs(argv: string[]): ParsedArgs;
16
+ export interface RunOptions {
17
+ /** Injected LabelClient for tests (apply path). */
18
+ readonly labelClient?: LabelClient;
19
+ }
10
20
  /** Run the CLI and return the process exit code. */
11
- export declare function run(argv: string[]): number;
12
- export {};
21
+ export declare function run(argv: string[], options?: RunOptions): number;
13
22
  //# sourceMappingURL=triage-classify.d.ts.map
@@ -2,13 +2,18 @@
2
2
  import { statSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { listProject, validateProject } from "@deftai/directive-core/dist/triage/classify/index.js";
6
- /** Parse triage-classify CLI args, mirroring the Python argparse surface. */
5
+ import { labelMirrorOutcomeToJson, listProject, mirrorLabels, renderLabelMirrorReport, validateProject, } from "@deftai/directive-core/dist/triage/classify/index.js";
6
+ /** Parse triage-classify CLI args (#1129 + #1423 Wave 1 mirror flags). */
7
7
  export function parseArgs(argv) {
8
8
  const parsed = {
9
9
  projectRoot: ".",
10
10
  doList: false,
11
11
  doValidate: false,
12
+ doMirror: false,
13
+ apply: false,
14
+ json: false,
15
+ repo: null,
16
+ allowCrossRepo: false,
12
17
  };
13
18
  for (let i = 0; i < argv.length; i += 1) {
14
19
  const arg = argv[i];
@@ -18,6 +23,29 @@ export function parseArgs(argv) {
18
23
  else if (arg === "--validate") {
19
24
  parsed.doValidate = true;
20
25
  }
26
+ else if (arg === "--mirror") {
27
+ parsed.doMirror = true;
28
+ }
29
+ else if (arg === "--apply") {
30
+ parsed.apply = true;
31
+ }
32
+ else if (arg === "--json") {
33
+ parsed.json = true;
34
+ }
35
+ else if (arg === "--allow-cross-repo") {
36
+ parsed.allowCrossRepo = true;
37
+ }
38
+ else if (arg === "--repo") {
39
+ const value = argv[i + 1];
40
+ if (value === undefined) {
41
+ return { ...parsed, error: "argument --repo: expected one argument" };
42
+ }
43
+ parsed.repo = value;
44
+ i += 1;
45
+ }
46
+ else if (arg?.startsWith("--repo=")) {
47
+ parsed.repo = arg.slice("--repo=".length);
48
+ }
21
49
  else if (arg === "--project-root") {
22
50
  const value = argv[i + 1];
23
51
  if (value === undefined) {
@@ -36,10 +64,16 @@ export function parseArgs(argv) {
36
64
  return { ...parsed, error: `unrecognized arguments: ${arg}` };
37
65
  }
38
66
  }
67
+ if (parsed.apply && !parsed.doMirror) {
68
+ return {
69
+ ...parsed,
70
+ error: "--apply requires --mirror (Tier-1 label mirror, #1423)",
71
+ };
72
+ }
39
73
  return parsed;
40
74
  }
41
75
  /** Run the CLI and return the process exit code. */
42
- export function run(argv) {
76
+ export function run(argv, options = {}) {
43
77
  const args = parseArgs(argv);
44
78
  if (args.error !== undefined) {
45
79
  process.stderr.write(`ERR: ${args.error}\n`);
@@ -67,6 +101,23 @@ export function run(argv) {
67
101
  }
68
102
  return result.code;
69
103
  }
104
+ if (args.doMirror) {
105
+ const mirrorOpts = {
106
+ dryRun: !args.apply,
107
+ repo: args.repo,
108
+ allowCrossRepo: args.allowCrossRepo,
109
+ ...(options.labelClient !== undefined ? { client: options.labelClient } : {}),
110
+ };
111
+ const [code, outcome] = mirrorLabels(projectRoot, mirrorOpts);
112
+ if (args.json) {
113
+ process.stdout.write(`${JSON.stringify(labelMirrorOutcomeToJson(outcome), null, 2)}\n`);
114
+ }
115
+ else {
116
+ process.stdout.write(renderLabelMirrorReport(outcome));
117
+ }
118
+ return code;
119
+ }
120
+ // Default / --list: print effective rules
70
121
  process.stdout.write(listProject(projectRoot));
71
122
  return 0;
72
123
  }
@@ -1,12 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import { type AgentHookHealthResult, type AgentHookReadinessResult, type EvaluateResult } from "@deftai/directive-core/verify-env";
2
3
  type HookScope = "git" | "agent" | "all";
3
4
  interface ParsedArgs {
4
5
  projectRoot: string;
5
6
  quiet: boolean;
6
7
  scope: HookScope;
8
+ live: boolean;
7
9
  error?: string;
8
10
  }
11
+ export interface VerifyHooksInstalledCliSeams {
12
+ readonly evaluateGit?: (projectRoot: string) => EvaluateResult;
13
+ readonly evaluateAgent?: (projectRoot: string) => AgentHookHealthResult;
14
+ readonly evaluateReadiness?: (projectRoot: string) => AgentHookReadinessResult;
15
+ readonly writeOut?: (text: string) => void;
16
+ readonly writeErr?: (text: string) => void;
17
+ }
9
18
  export declare function parseArgs(argv: string[]): ParsedArgs;
10
- export declare function run(argv: string[]): number;
19
+ /** Verify git hooks, structural agent hooks, or functional agent-hook readiness. */
20
+ export declare function run(argv: string[], seams?: VerifyHooksInstalledCliSeams): number;
11
21
  export {};
12
22
  //# sourceMappingURL=verify-hooks-installed.d.ts.map
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { evaluate, evaluateAgentHooks } from "@deftai/directive-core/verify-env";
4
+ import { evaluate, evaluateAgentHookReadinessSafely, evaluateAgentHooks, } from "@deftai/directive-core/verify-env";
5
5
  export function parseArgs(argv) {
6
- const parsed = { projectRoot: ".", quiet: false, scope: "git" };
6
+ const parsed = { projectRoot: ".", quiet: false, scope: "git", live: false };
7
7
  for (let i = 0; i < argv.length; i += 1) {
8
8
  const arg = argv[i];
9
9
  if (arg === "--quiet")
10
10
  parsed.quiet = true;
11
+ else if (arg === "--live")
12
+ parsed.live = true;
11
13
  else if (arg === "--project-root") {
12
14
  const value = argv[i + 1];
13
15
  if (value === undefined) {
@@ -41,26 +43,39 @@ export function parseArgs(argv) {
41
43
  return { ...parsed, error: `unrecognized arguments: ${arg}` };
42
44
  }
43
45
  }
46
+ if (parsed.live && parsed.scope === "git") {
47
+ return { ...parsed, error: "argument --live requires --scope=agent or --scope=all" };
48
+ }
44
49
  return parsed;
45
50
  }
46
- export function run(argv) {
51
+ /** Verify git hooks, structural agent hooks, or functional agent-hook readiness. */
52
+ export function run(argv, seams = {}) {
47
53
  const args = parseArgs(argv);
54
+ const writeOut = seams.writeOut ?? ((text) => process.stdout.write(text));
55
+ const writeErr = seams.writeErr ?? ((text) => process.stderr.write(text));
48
56
  if (args.error !== undefined) {
49
- process.stderr.write(`${args.error}\n`);
57
+ writeErr(`${args.error}\n`);
50
58
  return 2;
51
59
  }
52
60
  const projectRoot = resolve(args.projectRoot);
61
+ const evaluateGit = seams.evaluateGit ?? evaluate;
62
+ const evaluateAgent = seams.evaluateAgent ?? evaluateAgentHooks;
63
+ const evaluateReadiness = seams.evaluateReadiness
64
+ ? (root) => evaluateAgentHookReadinessSafely(root, seams.evaluateReadiness)
65
+ : evaluateAgentHookReadinessSafely;
53
66
  const results = [
54
- ...(args.scope === "git" || args.scope === "all" ? [evaluate(projectRoot)] : []),
55
- ...(args.scope === "agent" || args.scope === "all" ? [evaluateAgentHooks(projectRoot)] : []),
67
+ ...(args.scope === "git" || args.scope === "all" ? [evaluateGit(projectRoot)] : []),
68
+ ...(args.scope === "agent" || args.scope === "all"
69
+ ? [args.live ? evaluateReadiness(projectRoot) : evaluateAgent(projectRoot)]
70
+ : []),
56
71
  ];
57
72
  if (!args.quiet) {
58
73
  for (const result of results) {
59
74
  if (result.stream === "stdout") {
60
- process.stdout.write(`${result.message}\n`);
75
+ writeOut(`${result.message}\n`);
61
76
  }
62
77
  else if (result.stream === "stderr") {
63
- process.stderr.write(`${result.message}\n`);
78
+ writeErr(`${result.message}\n`);
64
79
  }
65
80
  }
66
81
  }
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ pr: number | null;
4
+ projectRoot: string;
5
+ repo: string | null;
6
+ headSha: string | null;
7
+ reviewCycle: string | null;
8
+ emitJson: boolean;
9
+ help: boolean;
10
+ error?: string;
11
+ }
12
+ export declare function parseVerifyL4OwnerArgs(argv: readonly string[]): ParsedArgs;
13
+ export declare function run(argv: readonly string[]): number;
14
+ export {};
15
+ //# sourceMappingURL=verify-l4-owner.d.ts.map
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { evaluateL4OwnerGate, L4_OWNER_HELP, l4OwnerResultToJson, } from "@deftai/directive-core/review-monitor";
5
+ export function parseVerifyL4OwnerArgs(argv) {
6
+ const acc = {
7
+ pr: null,
8
+ projectRoot: ".",
9
+ repo: null,
10
+ headSha: null,
11
+ reviewCycle: null,
12
+ emitJson: false,
13
+ help: false,
14
+ };
15
+ for (let i = 0; i < argv.length; i += 1) {
16
+ const arg = argv[i];
17
+ if (arg === "--help" || arg === "-h") {
18
+ return { ...acc, help: true };
19
+ }
20
+ if (arg === "--json") {
21
+ acc.emitJson = true;
22
+ }
23
+ else if (arg === "--pr") {
24
+ const value = argv[i + 1];
25
+ if (value === undefined) {
26
+ return { ...acc, error: "argument --pr: expected one argument" };
27
+ }
28
+ const n = Number(value);
29
+ if (!Number.isInteger(n) || n <= 0) {
30
+ return { ...acc, error: `invalid --pr value: ${value}` };
31
+ }
32
+ acc.pr = n;
33
+ i += 1;
34
+ }
35
+ else if (arg?.startsWith("--pr=")) {
36
+ const n = Number(arg.slice("--pr=".length));
37
+ if (!Number.isInteger(n) || n <= 0) {
38
+ return { ...acc, error: `invalid --pr value: ${arg}` };
39
+ }
40
+ acc.pr = n;
41
+ }
42
+ else if (arg === "--repo") {
43
+ const value = argv[i + 1];
44
+ if (value === undefined) {
45
+ return { ...acc, error: "argument --repo: expected one argument" };
46
+ }
47
+ acc.repo = value;
48
+ i += 1;
49
+ }
50
+ else if (arg?.startsWith("--repo=")) {
51
+ acc.repo = arg.slice("--repo=".length);
52
+ }
53
+ else if (arg === "--head-sha") {
54
+ const value = argv[i + 1];
55
+ if (value === undefined) {
56
+ return { ...acc, error: "argument --head-sha: expected one argument" };
57
+ }
58
+ acc.headSha = value;
59
+ i += 1;
60
+ }
61
+ else if (arg?.startsWith("--head-sha=")) {
62
+ acc.headSha = arg.slice("--head-sha=".length);
63
+ }
64
+ else if (arg === "--project-root") {
65
+ const value = argv[i + 1];
66
+ if (value === undefined) {
67
+ return { ...acc, error: "argument --project-root: expected one argument" };
68
+ }
69
+ acc.projectRoot = value;
70
+ i += 1;
71
+ }
72
+ else if (arg?.startsWith("--project-root=")) {
73
+ acc.projectRoot = arg.slice("--project-root=".length);
74
+ }
75
+ else if (arg === "--review-cycle") {
76
+ const value = argv[i + 1];
77
+ if (value === undefined) {
78
+ return { ...acc, error: "argument --review-cycle: expected one argument" };
79
+ }
80
+ acc.reviewCycle = value;
81
+ i += 1;
82
+ }
83
+ else if (arg?.startsWith("--review-cycle=")) {
84
+ acc.reviewCycle = arg.slice("--review-cycle=".length);
85
+ }
86
+ else if (arg?.startsWith("-")) {
87
+ return { ...acc, error: `unrecognized argument: ${arg}` };
88
+ }
89
+ else {
90
+ return { ...acc, error: `unrecognized argument: ${arg}` };
91
+ }
92
+ }
93
+ return acc;
94
+ }
95
+ export function run(argv) {
96
+ const args = parseVerifyL4OwnerArgs(argv);
97
+ if (args.help) {
98
+ process.stdout.write(L4_OWNER_HELP);
99
+ return 0;
100
+ }
101
+ if (args.error !== undefined) {
102
+ process.stderr.write(`verify_l4_owner: ${args.error}\n`);
103
+ process.stderr.write("Try: task verify:l4-owner -- --help\n");
104
+ return 2;
105
+ }
106
+ if (args.pr === null) {
107
+ process.stderr.write("verify_l4_owner: --pr is required\n");
108
+ process.stderr.write("Try: task verify:l4-owner -- --help\n");
109
+ return 2;
110
+ }
111
+ const result = evaluateL4OwnerGate({
112
+ pr: args.pr,
113
+ projectRoot: resolve(args.projectRoot),
114
+ repo: args.repo,
115
+ headSha: args.headSha,
116
+ reviewCycle: args.reviewCycle,
117
+ });
118
+ if (args.emitJson) {
119
+ process.stdout.write(`${JSON.stringify(l4OwnerResultToJson(result), null, 2)}\n`);
120
+ }
121
+ else if (result.exitCode === 0) {
122
+ process.stdout.write(`${result.message}\n`);
123
+ }
124
+ else {
125
+ process.stderr.write(`${result.message}\n`);
126
+ }
127
+ return result.exitCode;
128
+ }
129
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
130
+ process.exit(run(process.argv.slice(2)));
131
+ }
132
+ //# sourceMappingURL=verify-l4-owner.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive",
3
- "version": "0.93.0",
3
+ "version": "0.95.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.93.0",
38
- "@deftai/directive-content": "^0.93.0"
37
+ "@deftai/directive-core": "^0.95.0",
38
+ "@deftai/directive-content": "^0.95.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b"