@deftai/directive 0.118.0 → 0.119.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.js CHANGED
@@ -24,6 +24,7 @@
24
24
  */
25
25
  import { existsSync } from "node:fs";
26
26
  import { isAbsolute, resolve } from "node:path";
27
+ import { onePrUnit } from "@deftai/directive-core";
27
28
  import { AFK_TEMPLATE_NAMES, AUTHZ_OPERATIONS, CLOSED_VERB_TEMPLATE_NAMES, FINISH_LOOP_TEMPLATE_NAME, formatDecomposeStructuralMintCommand, isAfkTemplateName, isClosedVerbTemplateName, isFinishLoopTemplateName, mintAfkTemplateGrant, mintDecomposeStructuralApplyGrant, mintHumanOriginGrant, revokeGrant, showAuthzSnapshot, startUatLease, suspendUatLease, toProjectRelativePosix, } from "@deftai/directive-core/authz";
28
29
  import { refuseMintWhileUatActive, refuseNonInteractiveMint, resolveHumanPresenceMintSeams, } from "./human-presence-mint.js";
29
30
  export { AUTHZ_AGENT_SHELL_ENV_MARKERS, AUTHZ_INTERACTIVE_CONFIRM_PHRASE, } from "./human-presence-mint.js";
@@ -416,6 +417,35 @@ export function main(argv = process.argv.slice(2), seams = {}) {
416
417
  " Authorization SoT: Wave 1 grant store (.deft/authz/grants) — not session-auth.\n");
417
418
  return 0;
418
419
  }
420
+ if (args.template !== null && args.template.trim().toLowerCase() === "one-pr-unit") {
421
+ if (args.repo === null || args.repo.trim().length === 0) {
422
+ process.stderr.write("authz:grant --template one-pr-unit requires --repo owner/name\n");
423
+ return 2;
424
+ }
425
+ if (args.issueIds.length < 2) {
426
+ process.stderr.write("authz:grant --template one-pr-unit requires --issue-ids with at least two origins\n");
427
+ return 2;
428
+ }
429
+ const blocked = gateConfirm();
430
+ if (blocked !== null)
431
+ return blocked;
432
+ if ((process.env.DEFT_ONE_PR_UNIT_APP ?? "").trim().length === 0) {
433
+ process.stderr.write("authz:grant --template one-pr-unit requires DEFT_ONE_PR_UNIT_APP (durable Directive App store). " +
434
+ "The in-process simulator is test-only and is discarded on process exit.\n");
435
+ return 2;
436
+ }
437
+ const claim = onePrUnit.mintOnePrUnitGrant({
438
+ actor: args.actor,
439
+ approvalRef: args.note ?? "authz:grant --template one-pr-unit",
440
+ rationale: args.note ?? "one-PR-unit",
441
+ origins: args.issueIds.map((issueId) => ({ repo: args.repo, issueId })),
442
+ repo: args.repo,
443
+ });
444
+ process.stdout.write(`✓ one-PR-unit claim minted id=${claim.id} state=${claim.state} (reserved unbound)\n`);
445
+ process.stdout.write(" Opaque id is not a bearer. Only the App store plus the bound PR node id authorizes.\n");
446
+ process.stdout.write(` origins=${claim.origins.map((o) => `${o.repo}#${o.issueId}`).join(",")}\n`);
447
+ return 0;
448
+ }
419
449
  // AFK template path (#1095 / #871): presets only — still mintHumanOriginGrant.
420
450
  if (args.template !== null && args.template.trim().length > 0) {
421
451
  if (!isAfkTemplateName(args.template)) {
@@ -190,6 +190,12 @@ function routeNamespaceVerb(ns, verb, rest) {
190
190
  if (verb === "record-approved-scope") {
191
191
  return { kind: "dispatch", argv: ["scope-record-approved-scope", ...rest] };
192
192
  }
193
+ if (verb === "record-observable-scope") {
194
+ return { kind: "dispatch", argv: ["scope-record-observable-scope", ...rest] };
195
+ }
196
+ if (verb === "record-intent-constraint") {
197
+ return { kind: "dispatch", argv: ["scope-record-intent-constraint", ...rest] };
198
+ }
193
199
  }
194
200
  if (ns === "verify") {
195
201
  const verifyStem = VERIFY_VERB_MAP[verb];
@@ -10,7 +10,7 @@ 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", "scope-record-approved-scope", "scm-sync-default", "lifecycle-event", "lifecycle-stats", "session-start", "session-ready", "occupancy-steal", "occupancy-release", "occupancy-heartbeat", "occupancy-grant", "plan-sequence", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "triage-strip-withdrawn", "triage-evaluate", "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-test-boundary", "verify-scope-provenance", "verify-consumer-check-contract", "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-telemetry-coverage", "verify-content-manifest", "verify-deposit-closure", "verify-consumer-hard-stops", "verify-closing-keywords", "verify-skill-external-fetch-gate", "verify-semantic-single-source", "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", "verify-spec-prd-fresh", "rule-ownership-lint", "verify-story-ready", "verify-review-monitor", "verify-l4-owner", "verify-subagent-alive", "verify-subagent-steer", "review-monitor-register", "review-monitor-release", "verify-tools", "verify-wip-cap", "verify-orphan-active", "verify-pr-closeout-attestable", "verify-lifecycle-visible", "verify-completed-tracked", "verify-completed-write-guard", "verify-literal-ac", "verify-ac", "verify-agents-md-budget", "verify-agents-md-advisory", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "verify-evaluator-surface", "verify-consumer-test-lane", "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", "release-wait-npm", "scope-lifecycle", "scope-record-approved-scope", "scope-record-observable-scope", "scope-record-intent-constraint", "scm-sync-default", "lifecycle-event", "lifecycle-stats", "session-start", "session-ready", "occupancy-steal", "occupancy-release", "occupancy-heartbeat", "occupancy-grant", "plan-sequence", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "triage-strip-withdrawn", "triage-evaluate", "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-test-boundary", "verify-scope-provenance", "verify-consumer-check-contract", "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-telemetry-coverage", "verify-content-manifest", "verify-deposit-closure", "verify-consumer-hard-stops", "verify-closing-keywords", "verify-skill-external-fetch-gate", "verify-semantic-single-source", "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", "verify-spec-prd-fresh", "rule-ownership-lint", "verify-story-ready", "verify-review-monitor", "verify-l4-owner", "verify-subagent-alive", "verify-subagent-steer", "review-monitor-register", "review-monitor-release", "verify-tools", "verify-wip-cap", "verify-orphan-active", "verify-pr-closeout-attestable", "verify-lifecycle-visible", "verify-completed-tracked", "verify-completed-write-guard", "verify-literal-ac", "verify-ac", "verify-agents-md-budget", "verify-agents-md-advisory", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "verify-evaluator-surface", "verify-observable-scope", "verify-intent-constraint", "verify-consumer-test-lane", "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", "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-pre-dispatch", "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", "decision-write", "decision-list", "docs-impact"];
16
16
  /** Colon aliases for triage-actions (mirrors cli-router SUBCOMMAND_ROUTES). */
package/dist/dispatch.js CHANGED
@@ -67,8 +67,11 @@ export const CLI_MODULE_VERBS = [
67
67
  "release-e2e",
68
68
  "release-publish",
69
69
  "release-rollback",
70
+ "release-wait-npm",
70
71
  "scope-lifecycle",
71
72
  "scope-record-approved-scope",
73
+ "scope-record-observable-scope",
74
+ "scope-record-intent-constraint",
72
75
  "scm-sync-default",
73
76
  "lifecycle-event",
74
77
  "lifecycle-stats",
@@ -162,6 +165,8 @@ export const CLI_MODULE_VERBS = [
162
165
  "verify-eval-health-relocation",
163
166
  "verify-eval-triggers-relocation",
164
167
  "verify-evaluator-surface",
168
+ "verify-observable-scope",
169
+ "verify-intent-constraint",
165
170
  "verify-consumer-test-lane",
166
171
  "eval-health",
167
172
  "eval-run",
@@ -294,6 +299,8 @@ export const VERB_ALIASES = {
294
299
  "verify:test-boundary": "verify-test-boundary",
295
300
  "verify:scope-provenance": "verify-scope-provenance",
296
301
  "scope:record-approved-scope": "scope-record-approved-scope",
302
+ "scope:record-observable-scope": "scope-record-observable-scope",
303
+ "scope:record-intent-constraint": "scope-record-intent-constraint",
297
304
  "verify:consumer-check-contract": "verify-consumer-check-contract",
298
305
  "coverage:hotspots": "coverage-hotspots",
299
306
  "verify:branch": "verify-branch",
@@ -313,6 +320,8 @@ export const VERB_ALIASES = {
313
320
  "verify:eval-health-relocation": "verify-eval-health-relocation",
314
321
  "verify:eval-triggers-relocation": "verify-eval-triggers-relocation",
315
322
  "verify:evaluator-surface": "verify-evaluator-surface",
323
+ "verify:observable-scope": "verify-observable-scope",
324
+ "verify:intent-constraint": "verify-intent-constraint",
316
325
  "verify:consumer-test-lane": "verify-consumer-test-lane",
317
326
  "verify:hooks-installed": "verify-hooks-installed",
318
327
  "verify:no-task-runtime": "verify-no-task-runtime",
@@ -424,6 +433,7 @@ export const VERB_ALIASES = {
424
433
  "scm:status": "scm-readiness",
425
434
  "scm:readiness": "scm-readiness",
426
435
  "scm:sync-default": "scm-sync-default",
436
+ "release:wait-npm": "release-wait-npm",
427
437
  };
428
438
  /** CLI modules living under verify-source-cli/ or content-validate-cli/ subdirs. */
429
439
  const SUBDIR_CLI_STEMS = {
@@ -2640,6 +2650,8 @@ const SCOPE_COMMAND_NAMES = [
2640
2650
  "scope:demote",
2641
2651
  "scope:undo",
2642
2652
  "scope:record-approved-scope",
2653
+ "scope:record-observable-scope",
2654
+ "scope:record-intent-constraint",
2643
2655
  ];
2644
2656
  /**
2645
2657
  * Deduplicated command names for `directive commands`, preferring colon-style
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function run(argv: string[]): number;
3
+ //# sourceMappingURL=release-wait-npm.d.ts.map
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { cmdReleaseWaitNpm } from "@deftai/directive-core/dist/release/cli-drift-report.js";
4
+ export function run(argv) {
5
+ return cmdReleaseWaitNpm(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=release-wait-npm.js.map
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { type HumanPresenceMintSeams } from "./human-presence-mint.js";
3
+ export interface ParsedArgs {
4
+ projectRoot: string;
5
+ xbriefPath: string;
6
+ actor: string;
7
+ kind: string;
8
+ quiet: boolean;
9
+ confirm: boolean;
10
+ help?: boolean;
11
+ error?: string;
12
+ }
13
+ export declare function parseArgs(argv: string[]): ParsedArgs;
14
+ export declare function run(argv: string[], seams?: HumanPresenceMintSeams): number;
15
+ //# sourceMappingURL=scope-record-intent-constraint.d.ts.map
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for scope:record-intent-constraint (#4541).
4
+ *
5
+ * Human-presence mint of `.deft/intent-constraint/<plan-id>.json` from
6
+ * `plan["x-directive/intentConstraint"]`. --actor is display-only.
7
+ */
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { basename, relative, resolve } from "node:path";
10
+ import { buildIntentConstraintRecord, extractIntentConstraintFromPlan, parseIntentConstraintContract, writeIntentConstraintRecord, } from "@deftai/directive-core/intent-constraint";
11
+ import { isDirectEntrypoint } from "./entrypoint.js";
12
+ import { refuseMintWhileUatActive, refuseNonInteractiveMint, resolveHumanPresenceMintSeams, } from "./human-presence-mint.js";
13
+ import { isPathInsideRoot } from "./scope-record-approved-scope.js";
14
+ function usage() {
15
+ return ("usage: scope:record-intent-constraint -- <xbrief-path> --actor <name> --confirm " +
16
+ "[--kind operator] [--project-root <dir>] [--quiet]\n" +
17
+ ' Writes .deft/intent-constraint/<plan-id>.json from plan["x-directive/intentConstraint"].\n' +
18
+ " --actor is display only and never authorizes mint. Mint requires a real TTY, " +
19
+ "controlling terminal, --confirm, and typed phrase mint (#3110). Agent/CI shells refuse.");
20
+ }
21
+ function extractPlanId(payload) {
22
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload))
23
+ return null;
24
+ const plan = payload.plan;
25
+ if (plan === null || typeof plan !== "object" || Array.isArray(plan))
26
+ return null;
27
+ const id = plan.id;
28
+ return typeof id === "string" && id.trim().length > 0 ? id.trim() : null;
29
+ }
30
+ export function parseArgs(argv) {
31
+ const parsed = {
32
+ projectRoot: ".",
33
+ xbriefPath: "",
34
+ actor: "",
35
+ kind: "operator",
36
+ quiet: false,
37
+ confirm: false,
38
+ };
39
+ const positionals = [];
40
+ for (let i = 0; i < argv.length; i += 1) {
41
+ const arg = argv[i];
42
+ if (arg === "--")
43
+ continue;
44
+ if (arg === "--help" || arg === "-h")
45
+ return { ...parsed, help: true };
46
+ if (arg === "--quiet")
47
+ parsed.quiet = true;
48
+ else if (arg === "--confirm")
49
+ parsed.confirm = true;
50
+ else if (arg === "--project-root") {
51
+ const value = argv[i + 1];
52
+ if (value === undefined)
53
+ return { ...parsed, error: "argument --project-root: expected one argument" };
54
+ parsed.projectRoot = value;
55
+ i += 1;
56
+ }
57
+ else if (arg?.startsWith("--project-root=")) {
58
+ parsed.projectRoot = arg.slice("--project-root=".length);
59
+ }
60
+ else if (arg === "--actor") {
61
+ const value = argv[i + 1];
62
+ if (value === undefined)
63
+ return { ...parsed, error: "argument --actor: expected one argument" };
64
+ parsed.actor = value;
65
+ i += 1;
66
+ }
67
+ else if (arg?.startsWith("--actor=")) {
68
+ parsed.actor = arg.slice("--actor=".length);
69
+ }
70
+ else if (arg === "--kind") {
71
+ const value = argv[i + 1];
72
+ if (value === undefined)
73
+ return { ...parsed, error: "argument --kind: expected one argument" };
74
+ parsed.kind = value;
75
+ i += 1;
76
+ }
77
+ else if (arg?.startsWith("--kind=")) {
78
+ parsed.kind = arg.slice("--kind=".length);
79
+ }
80
+ else if (arg?.startsWith("-")) {
81
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
82
+ }
83
+ else if (arg !== undefined) {
84
+ positionals.push(arg);
85
+ }
86
+ }
87
+ if (parsed.help)
88
+ return parsed;
89
+ if (positionals.length === 0)
90
+ return { ...parsed, error: `missing xBRIEF path\n${usage()}` };
91
+ if (positionals.length > 1) {
92
+ return { ...parsed, error: `unexpected extra args: ${positionals.slice(1).join(" ")}` };
93
+ }
94
+ parsed.xbriefPath = positionals[0] ?? "";
95
+ if (parsed.actor.trim().length === 0) {
96
+ return {
97
+ ...parsed,
98
+ error: `argument --actor is required (human operator identity)\n${usage()}`,
99
+ };
100
+ }
101
+ return parsed;
102
+ }
103
+ export function run(argv, seams = {}) {
104
+ const args = parseArgs(argv);
105
+ if (args.help) {
106
+ process.stdout.write(`${usage()}\n`);
107
+ return 0;
108
+ }
109
+ if (args.error !== undefined) {
110
+ process.stderr.write(`scope_record_intent_constraint: ${args.error}\n`);
111
+ return 2;
112
+ }
113
+ const projectRoot = resolve(args.projectRoot);
114
+ const uat = refuseMintWhileUatActive("scope:record-intent-constraint", projectRoot);
115
+ if (uat !== null)
116
+ return uat;
117
+ const resolved = resolveHumanPresenceMintSeams(seams);
118
+ const refused = refuseNonInteractiveMint({
119
+ verb: "scope:record-intent-constraint",
120
+ confirm: args.confirm,
121
+ isTty: resolved.isTty,
122
+ environ: resolved.environ,
123
+ hasControllingTerminal: resolved.hasControllingTerminal,
124
+ readInteractiveConfirm: resolved.readInteractiveConfirm,
125
+ });
126
+ if (refused !== null)
127
+ return refused;
128
+ const xbriefAbs = resolve(projectRoot, args.xbriefPath);
129
+ const fullPath = existsSync(xbriefAbs) ? xbriefAbs : resolve(args.xbriefPath);
130
+ if (!existsSync(fullPath)) {
131
+ process.stderr.write(`scope_record_intent_constraint: xBRIEF not found: ${args.xbriefPath}\n`);
132
+ return 2;
133
+ }
134
+ if (!isPathInsideRoot(projectRoot, fullPath)) {
135
+ process.stderr.write("scope_record_intent_constraint: xBRIEF path escapes --project-root.\n");
136
+ return 2;
137
+ }
138
+ let payload;
139
+ const rawText = readFileSync(fullPath, "utf8");
140
+ try {
141
+ payload = JSON.parse(rawText);
142
+ }
143
+ catch (err) {
144
+ process.stderr.write(`scope_record_intent_constraint: invalid JSON: ${String(err)}\n`);
145
+ return 2;
146
+ }
147
+ const planId = extractPlanId(payload) ?? basename(fullPath, ".xbrief.json");
148
+ const contract = parseIntentConstraintContract(extractIntentConstraintFromPlan(payload));
149
+ if ("error" in contract) {
150
+ process.stderr.write(`scope_record_intent_constraint: ${contract.error}\n`);
151
+ return 2;
152
+ }
153
+ const rel = relative(projectRoot, fullPath).replace(/\\/g, "/");
154
+ const rec = buildIntentConstraintRecord({
155
+ planId,
156
+ xbriefRelPath: rel.startsWith("xbrief/pending/") ? `xbrief/active/${basename(rel)}` : rel,
157
+ constraints: contract.constraints,
158
+ humanApproval: {
159
+ kind: args.kind,
160
+ actor: args.actor,
161
+ mintedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
162
+ mintedVia: "scope:record-intent-constraint",
163
+ },
164
+ });
165
+ if ("error" in rec) {
166
+ process.stderr.write(`scope_record_intent_constraint: ${rec.error}\n`);
167
+ return 2;
168
+ }
169
+ const path = writeIntentConstraintRecord(projectRoot, rec);
170
+ if (!args.quiet) {
171
+ process.stdout.write(`Wrote ${path}\nCommit this record on the merge base before the implementation change PR.\n`);
172
+ }
173
+ return 0;
174
+ }
175
+ if (isDirectEntrypoint(import.meta.url)) {
176
+ process.exit(run(process.argv.slice(2)));
177
+ }
178
+ //# sourceMappingURL=scope-record-intent-constraint.js.map
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { type HumanPresenceMintSeams } from "./human-presence-mint.js";
3
+ export interface ParsedArgs {
4
+ projectRoot: string;
5
+ xbriefPath: string;
6
+ actor: string;
7
+ kind: string;
8
+ quiet: boolean;
9
+ confirm: boolean;
10
+ help?: boolean;
11
+ error?: string;
12
+ }
13
+ export declare function parseArgs(argv: string[]): ParsedArgs;
14
+ export declare function run(argv: string[], seams?: HumanPresenceMintSeams): number;
15
+ //# sourceMappingURL=scope-record-observable-scope.d.ts.map
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for scope:record-observable-scope (#4495).
4
+ *
5
+ * Human-presence mint of `.deft/observable-scope/<plan-id>.json` from
6
+ * `plan["x-directive/observableChange"]`. --actor is display-only.
7
+ */
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { basename, relative, resolve } from "node:path";
10
+ import { buildObservableScopeRecord, extractObservableChangeFromPlan, parseObservableChangeContract, writeObservableScopeRecord, } from "@deftai/directive-core/observable-scope";
11
+ import { isDirectEntrypoint } from "./entrypoint.js";
12
+ import { refuseMintWhileUatActive, refuseNonInteractiveMint, resolveHumanPresenceMintSeams, } from "./human-presence-mint.js";
13
+ import { isPathInsideRoot } from "./scope-record-approved-scope.js";
14
+ function usage() {
15
+ return ("usage: scope:record-observable-scope -- <xbrief-path> --actor <name> --confirm " +
16
+ "[--kind operator] [--project-root <dir>] [--quiet]\n" +
17
+ ' Writes .deft/observable-scope/<plan-id>.json from plan["x-directive/observableChange"].\n' +
18
+ " --actor is display only and never authorizes mint. Mint requires a real TTY, " +
19
+ "controlling terminal, --confirm, and typed phrase mint (#3110). Agent/CI shells refuse.");
20
+ }
21
+ function extractPlanId(payload) {
22
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload))
23
+ return null;
24
+ const plan = payload.plan;
25
+ if (plan === null || typeof plan !== "object" || Array.isArray(plan))
26
+ return null;
27
+ const id = plan.id;
28
+ return typeof id === "string" && id.trim().length > 0 ? id.trim() : null;
29
+ }
30
+ export function parseArgs(argv) {
31
+ const parsed = {
32
+ projectRoot: ".",
33
+ xbriefPath: "",
34
+ actor: "",
35
+ kind: "operator",
36
+ quiet: false,
37
+ confirm: false,
38
+ };
39
+ const positionals = [];
40
+ for (let i = 0; i < argv.length; i += 1) {
41
+ const arg = argv[i];
42
+ if (arg === "--")
43
+ continue;
44
+ if (arg === "--help" || arg === "-h")
45
+ return { ...parsed, help: true };
46
+ if (arg === "--quiet")
47
+ parsed.quiet = true;
48
+ else if (arg === "--confirm")
49
+ parsed.confirm = true;
50
+ else if (arg === "--project-root") {
51
+ const value = argv[i + 1];
52
+ if (value === undefined)
53
+ return { ...parsed, error: "argument --project-root: expected one argument" };
54
+ parsed.projectRoot = value;
55
+ i += 1;
56
+ }
57
+ else if (arg?.startsWith("--project-root=")) {
58
+ parsed.projectRoot = arg.slice("--project-root=".length);
59
+ }
60
+ else if (arg === "--actor") {
61
+ const value = argv[i + 1];
62
+ if (value === undefined)
63
+ return { ...parsed, error: "argument --actor: expected one argument" };
64
+ parsed.actor = value;
65
+ i += 1;
66
+ }
67
+ else if (arg?.startsWith("--actor=")) {
68
+ parsed.actor = arg.slice("--actor=".length);
69
+ }
70
+ else if (arg === "--kind") {
71
+ const value = argv[i + 1];
72
+ if (value === undefined)
73
+ return { ...parsed, error: "argument --kind: expected one argument" };
74
+ parsed.kind = value;
75
+ i += 1;
76
+ }
77
+ else if (arg?.startsWith("--kind=")) {
78
+ parsed.kind = arg.slice("--kind=".length);
79
+ }
80
+ else if (arg?.startsWith("-")) {
81
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
82
+ }
83
+ else if (arg !== undefined) {
84
+ positionals.push(arg);
85
+ }
86
+ }
87
+ if (parsed.help)
88
+ return parsed;
89
+ if (positionals.length === 0)
90
+ return { ...parsed, error: `missing xBRIEF path\n${usage()}` };
91
+ if (positionals.length > 1) {
92
+ return { ...parsed, error: `unexpected extra args: ${positionals.slice(1).join(" ")}` };
93
+ }
94
+ parsed.xbriefPath = positionals[0] ?? "";
95
+ if (parsed.actor.trim().length === 0) {
96
+ return {
97
+ ...parsed,
98
+ error: `argument --actor is required (human operator identity)\n${usage()}`,
99
+ };
100
+ }
101
+ return parsed;
102
+ }
103
+ export function run(argv, seams = {}) {
104
+ const args = parseArgs(argv);
105
+ if (args.help) {
106
+ process.stdout.write(`${usage()}\n`);
107
+ return 0;
108
+ }
109
+ if (args.error !== undefined) {
110
+ process.stderr.write(`scope_record_observable_scope: ${args.error}\n`);
111
+ return 2;
112
+ }
113
+ const projectRoot = resolve(args.projectRoot);
114
+ const uat = refuseMintWhileUatActive("scope:record-observable-scope", projectRoot);
115
+ if (uat !== null)
116
+ return uat;
117
+ const resolved = resolveHumanPresenceMintSeams(seams);
118
+ const refused = refuseNonInteractiveMint({
119
+ verb: "scope:record-observable-scope",
120
+ confirm: args.confirm,
121
+ isTty: resolved.isTty,
122
+ environ: resolved.environ,
123
+ hasControllingTerminal: resolved.hasControllingTerminal,
124
+ readInteractiveConfirm: resolved.readInteractiveConfirm,
125
+ });
126
+ if (refused !== null)
127
+ return refused;
128
+ const xbriefAbs = resolve(projectRoot, args.xbriefPath);
129
+ const fullPath = existsSync(xbriefAbs) ? xbriefAbs : resolve(args.xbriefPath);
130
+ if (!existsSync(fullPath)) {
131
+ process.stderr.write(`scope_record_observable_scope: xBRIEF not found: ${args.xbriefPath}\n`);
132
+ return 2;
133
+ }
134
+ if (!isPathInsideRoot(projectRoot, fullPath)) {
135
+ process.stderr.write("scope_record_observable_scope: xBRIEF path escapes --project-root.\n");
136
+ return 2;
137
+ }
138
+ let payload;
139
+ const rawText = readFileSync(fullPath, "utf8");
140
+ try {
141
+ payload = JSON.parse(rawText);
142
+ }
143
+ catch (err) {
144
+ process.stderr.write(`scope_record_observable_scope: invalid JSON: ${String(err)}\n`);
145
+ return 2;
146
+ }
147
+ const planId = extractPlanId(payload) ?? basename(fullPath, ".xbrief.json");
148
+ const contract = parseObservableChangeContract(extractObservableChangeFromPlan(payload));
149
+ if ("error" in contract) {
150
+ process.stderr.write(`scope_record_observable_scope: ${contract.error}\n`);
151
+ return 2;
152
+ }
153
+ const rel = relative(projectRoot, fullPath).replace(/\\/g, "/");
154
+ const rec = buildObservableScopeRecord({
155
+ planId,
156
+ xbriefRelPath: rel.startsWith("xbrief/pending/") ? `xbrief/active/${basename(rel)}` : rel,
157
+ allowedChanges: contract.allowedChanges,
158
+ mustPreserve: contract.mustPreserve,
159
+ changeKind: contract.changeKind,
160
+ humanApproval: {
161
+ kind: args.kind,
162
+ actor: args.actor,
163
+ mintedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
164
+ mintedVia: "scope:record-observable-scope",
165
+ },
166
+ });
167
+ if ("error" in rec) {
168
+ process.stderr.write(`scope_record_observable_scope: ${rec.error}\n`);
169
+ return 2;
170
+ }
171
+ const path = writeObservableScopeRecord(projectRoot, rec);
172
+ if (!args.quiet) {
173
+ process.stdout.write(`Wrote ${path}\nCommit this record on the merge base before the UI change PR.\n`);
174
+ }
175
+ return 0;
176
+ }
177
+ if (isDirectEntrypoint(import.meta.url)) {
178
+ process.exit(run(process.argv.slice(2)));
179
+ }
180
+ //# sourceMappingURL=scope-record-observable-scope.js.map
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { type CeremonyDepth, type CeremonyDialInputs } from "@deftai/directive-core/policy";
3
- import { type PrimaryClaimException, type SessionCeremonyTier } from "@deftai/directive-core/session";
3
+ import { type PrimaryClaimException, type SessionCeremonyTier, type SessionPosture } from "@deftai/directive-core/session";
4
4
  export interface ParsedSessionStartArgs {
5
5
  projectRoot: string;
6
6
  deferValues: string[];
@@ -43,6 +43,8 @@ export interface ParsedSessionStartArgs {
43
43
  primaryClaimException: PrimaryClaimException | null;
44
44
  /** #3611: explicit lifecycle owner injected by a host hook bridge. */
45
45
  sessionId: string | null;
46
+ /** Trusted --posture argv (#4444). */
47
+ posture: SessionPosture | null;
46
48
  error?: string;
47
49
  }
48
50
  /** Parse session:start CLI args, mirroring scripts/session_start.py. */
@@ -2,7 +2,7 @@
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { CEREMONY_DEPTHS, normalizeCeremonyModelTier, normalizeCeremonyProjectShape, normalizeCeremonyTaskSize, selectCeremonyDepth, } from "@deftai/directive-core/policy";
5
- import { COLD_CEREMONY_TIER, PRIMARY_CLAIM_EXCEPTIONS, parseDeferrals, READ_ONLY_POSTURE, REARM_CEREMONY_TIER, resolveProductionHostEffortDescriptor, ritualStatePath, runSessionStart, SESSION_CEREMONY_TIERS, } from "@deftai/directive-core/session";
5
+ import { COLD_CEREMONY_TIER, PRIMARY_CLAIM_EXCEPTIONS, parseDeferrals, parseSessionPostureToken, READ_ONLY_POSTURE, REARM_CEREMONY_TIER, REQUIREMENTS_POSTURE, resolveProductionHostEffortDescriptor, ritualStatePath, runSessionStart, SESSION_CEREMONY_TIERS, } from "@deftai/directive-core/session";
6
6
  function parseCeremonyDepth(raw) {
7
7
  const value = raw.trim().toLowerCase();
8
8
  if (CEREMONY_DEPTHS.includes(value)) {
@@ -44,6 +44,7 @@ export function parseArgs(argv) {
44
44
  occupant: null,
45
45
  primaryClaimException: null,
46
46
  sessionId: null,
47
+ posture: null,
47
48
  };
48
49
  const dialInputs = {};
49
50
  for (let i = 0; i < argv.length; i += 1) {
@@ -372,6 +373,53 @@ export function parseArgs(argv) {
372
373
  }
373
374
  parsed.occupant = value;
374
375
  }
376
+ else if (arg === "--posture") {
377
+ const value = argv[i + 1];
378
+ if (value === undefined || value.startsWith("--")) {
379
+ return {
380
+ ...parsed,
381
+ error: "argument --posture: expected one argument (read-only|mutation|requirements)",
382
+ };
383
+ }
384
+ const parsedPosture = parseSessionPostureToken(value);
385
+ if (parsedPosture.error !== null) {
386
+ return { ...parsed, error: parsedPosture.error };
387
+ }
388
+ if (parsedPosture.token === "assist") {
389
+ return {
390
+ ...parsed,
391
+ error: "argument --posture=docs|assist is scratch-only; set DEFT_SESSION_POSTURE=assist. Tracked docs use --posture=requirements",
392
+ };
393
+ }
394
+ if (parsedPosture.token === null) {
395
+ return {
396
+ ...parsed,
397
+ error: "argument --posture: expected one argument (read-only|mutation|requirements)",
398
+ };
399
+ }
400
+ parsed.posture = parsedPosture.token;
401
+ i += 1;
402
+ }
403
+ else if (arg?.startsWith("--posture=")) {
404
+ const value = arg.slice("--posture=".length);
405
+ const parsedPosture = parseSessionPostureToken(value);
406
+ if (parsedPosture.error !== null) {
407
+ return { ...parsed, error: parsedPosture.error };
408
+ }
409
+ if (parsedPosture.token === "assist") {
410
+ return {
411
+ ...parsed,
412
+ error: "argument --posture=docs|assist is scratch-only; set DEFT_SESSION_POSTURE=assist. Tracked docs use --posture=requirements",
413
+ };
414
+ }
415
+ if (parsedPosture.token === null) {
416
+ return {
417
+ ...parsed,
418
+ error: "argument --posture: expected one argument (read-only|mutation|requirements)",
419
+ };
420
+ }
421
+ parsed.posture = parsedPosture.token;
422
+ }
375
423
  else {
376
424
  return { ...parsed, error: `unrecognized argument: ${arg}` };
377
425
  }
@@ -427,7 +475,7 @@ export function run(argv) {
427
475
  result = runSessionStart(projectRoot, {
428
476
  deferrals,
429
477
  writeHistory: !args.noHistory,
430
- posture: args.readOnly ? READ_ONLY_POSTURE : undefined,
478
+ posture: args.readOnly ? READ_ONLY_POSTURE : (args.posture ?? undefined),
431
479
  allowOptionalNetwork: args.withNetwork ? true : undefined,
432
480
  ceremonyTier: args.ceremonyTier,
433
481
  ceremonyDialInputs: args.ceremonyDialInputs,
@@ -474,7 +522,7 @@ export function run(argv) {
474
522
  }
475
523
  if (result.code === 0) {
476
524
  const posture = result.payload.posture;
477
- if (posture === READ_ONLY_POSTURE) {
525
+ if (posture === READ_ONLY_POSTURE || posture === REQUIREMENTS_POSTURE) {
478
526
  process.stdout.write(`[deft] ${String(result.payload.message)}\n`);
479
527
  }
480
528
  else {
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string;
4
+ originRef?: string;
5
+ staged: boolean;
6
+ quiet: boolean;
7
+ planId?: string;
8
+ error?: string;
9
+ }
10
+ /** Parse verify-intent-constraint CLI args. */
11
+ export declare function parseArgs(argv: string[]): ParsedArgs;
12
+ /** Run the gate and return the process exit code. */
13
+ export declare function run(argv: string[]): number;
14
+ export {};
15
+ //# sourceMappingURL=verify-intent-constraint.d.ts.map
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { evaluateIntentConstraint } from "@deftai/directive-core/intent-constraint";
5
+ /** Parse verify-intent-constraint CLI args. */
6
+ export function parseArgs(argv) {
7
+ const parsed = { projectRoot: ".", staged: false, quiet: false };
8
+ for (let i = 0; i < argv.length; i += 1) {
9
+ const arg = argv[i];
10
+ if (arg === "--quiet") {
11
+ parsed.quiet = true;
12
+ }
13
+ else if (arg === "--staged") {
14
+ parsed.staged = true;
15
+ }
16
+ else if (arg === "--project-root") {
17
+ const value = argv[i + 1];
18
+ if (value === undefined) {
19
+ return { ...parsed, error: "argument --project-root: expected one argument" };
20
+ }
21
+ parsed.projectRoot = value;
22
+ i += 1;
23
+ }
24
+ else if (arg?.startsWith("--project-root=")) {
25
+ parsed.projectRoot = arg.slice("--project-root=".length);
26
+ }
27
+ else if (arg === "--origin-ref") {
28
+ const value = argv[i + 1];
29
+ if (value === undefined) {
30
+ return { ...parsed, error: "argument --origin-ref: expected one argument" };
31
+ }
32
+ parsed.originRef = value;
33
+ i += 1;
34
+ }
35
+ else if (arg?.startsWith("--origin-ref=")) {
36
+ parsed.originRef = arg.slice("--origin-ref=".length);
37
+ }
38
+ else if (arg === "--plan-id") {
39
+ const value = argv[i + 1];
40
+ if (value === undefined) {
41
+ return { ...parsed, error: "argument --plan-id: expected one argument" };
42
+ }
43
+ parsed.planId = value;
44
+ i += 1;
45
+ }
46
+ else if (arg?.startsWith("--plan-id=")) {
47
+ parsed.planId = arg.slice("--plan-id=".length);
48
+ }
49
+ else if (arg === "--base-ref") {
50
+ return {
51
+ ...parsed,
52
+ error: "unrecognized argument: --base-ref (baseline is the computed merge base; use --origin-ref to name the origin default)",
53
+ };
54
+ }
55
+ else {
56
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
57
+ }
58
+ }
59
+ return parsed;
60
+ }
61
+ /** Run the gate and return the process exit code. */
62
+ export function run(argv) {
63
+ const args = parseArgs(argv);
64
+ if (args.error !== undefined) {
65
+ process.stderr.write(`verify_intent_constraint: ${args.error}\n`);
66
+ return 2;
67
+ }
68
+ const result = evaluateIntentConstraint({
69
+ projectRoot: resolve(args.projectRoot),
70
+ originRef: args.originRef,
71
+ staged: args.staged,
72
+ quiet: args.quiet,
73
+ planId: args.planId,
74
+ });
75
+ if (result.message.length > 0) {
76
+ if (result.stream === "stdout")
77
+ process.stdout.write(`${result.message}\n`);
78
+ else if (result.stream === "stderr")
79
+ process.stderr.write(`${result.message}\n`);
80
+ }
81
+ return result.code;
82
+ }
83
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
84
+ process.exit(run(process.argv.slice(2)));
85
+ }
86
+ //# sourceMappingURL=verify-intent-constraint.js.map
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string;
4
+ originRef?: string;
5
+ staged: boolean;
6
+ quiet: boolean;
7
+ planId?: string;
8
+ error?: string;
9
+ }
10
+ /** Parse verify-observable-scope CLI args. */
11
+ export declare function parseArgs(argv: string[]): ParsedArgs;
12
+ /** Run the gate and return the process exit code. */
13
+ export declare function run(argv: string[]): number;
14
+ export {};
15
+ //# sourceMappingURL=verify-observable-scope.d.ts.map
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { evaluateObservableScope } from "@deftai/directive-core/observable-scope";
5
+ /** Parse verify-observable-scope CLI args. */
6
+ export function parseArgs(argv) {
7
+ const parsed = { projectRoot: ".", staged: false, quiet: false };
8
+ for (let i = 0; i < argv.length; i += 1) {
9
+ const arg = argv[i];
10
+ if (arg === "--quiet") {
11
+ parsed.quiet = true;
12
+ }
13
+ else if (arg === "--staged") {
14
+ parsed.staged = true;
15
+ }
16
+ else if (arg === "--project-root") {
17
+ const value = argv[i + 1];
18
+ if (value === undefined) {
19
+ return { ...parsed, error: "argument --project-root: expected one argument" };
20
+ }
21
+ parsed.projectRoot = value;
22
+ i += 1;
23
+ }
24
+ else if (arg?.startsWith("--project-root=")) {
25
+ parsed.projectRoot = arg.slice("--project-root=".length);
26
+ }
27
+ else if (arg === "--origin-ref") {
28
+ const value = argv[i + 1];
29
+ if (value === undefined) {
30
+ return { ...parsed, error: "argument --origin-ref: expected one argument" };
31
+ }
32
+ parsed.originRef = value;
33
+ i += 1;
34
+ }
35
+ else if (arg?.startsWith("--origin-ref=")) {
36
+ parsed.originRef = arg.slice("--origin-ref=".length);
37
+ }
38
+ else if (arg === "--plan-id") {
39
+ const value = argv[i + 1];
40
+ if (value === undefined) {
41
+ return { ...parsed, error: "argument --plan-id: expected one argument" };
42
+ }
43
+ parsed.planId = value;
44
+ i += 1;
45
+ }
46
+ else if (arg?.startsWith("--plan-id=")) {
47
+ parsed.planId = arg.slice("--plan-id=".length);
48
+ }
49
+ else if (arg === "--base-ref") {
50
+ return {
51
+ ...parsed,
52
+ error: "unrecognized argument: --base-ref (baseline is the computed merge base; use --origin-ref to name the origin default)",
53
+ };
54
+ }
55
+ else {
56
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
57
+ }
58
+ }
59
+ return parsed;
60
+ }
61
+ /** Run the gate and return the process exit code. */
62
+ export function run(argv) {
63
+ const args = parseArgs(argv);
64
+ if (args.error !== undefined) {
65
+ process.stderr.write(`verify_observable_scope: ${args.error}\n`);
66
+ return 2;
67
+ }
68
+ const result = evaluateObservableScope({
69
+ projectRoot: resolve(args.projectRoot),
70
+ originRef: args.originRef,
71
+ staged: args.staged,
72
+ quiet: args.quiet,
73
+ planId: args.planId,
74
+ });
75
+ if (result.message.length > 0) {
76
+ if (result.stream === "stdout")
77
+ process.stdout.write(`${result.message}\n`);
78
+ else if (result.stream === "stderr")
79
+ process.stderr.write(`${result.message}\n`);
80
+ }
81
+ return result.code;
82
+ }
83
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
84
+ process.exit(run(process.argv.slice(2)));
85
+ }
86
+ //# sourceMappingURL=verify-observable-scope.js.map
@@ -4,6 +4,7 @@ interface ParsedArgs {
4
4
  repo: string | null;
5
5
  pr: number | null;
6
6
  quiet: boolean;
7
+ onePrUnitId: string | null;
7
8
  error?: string;
8
9
  }
9
10
  /** Parse verify-pr-closeout-attestable CLI args. */
@@ -12,7 +12,13 @@ function parsePrNumber(raw) {
12
12
  }
13
13
  /** Parse verify-pr-closeout-attestable CLI args. */
14
14
  export function parseArgs(argv) {
15
- const parsed = { projectRoot: ".", repo: null, pr: null, quiet: false };
15
+ const parsed = {
16
+ projectRoot: ".",
17
+ repo: null,
18
+ pr: null,
19
+ quiet: false,
20
+ onePrUnitId: null,
21
+ };
16
22
  for (let i = 0; i < argv.length; i += 1) {
17
23
  const arg = argv[i];
18
24
  if (arg === "--quiet") {
@@ -60,6 +66,17 @@ export function parseArgs(argv) {
60
66
  }
61
67
  parsed.pr = pr;
62
68
  }
69
+ else if (arg === "--one-pr-unit") {
70
+ const value = argv[i + 1];
71
+ if (value === undefined) {
72
+ return { ...parsed, error: "argument --one-pr-unit: expected one argument" };
73
+ }
74
+ parsed.onePrUnitId = value;
75
+ i += 1;
76
+ }
77
+ else if (arg?.startsWith("--one-pr-unit=")) {
78
+ parsed.onePrUnitId = arg.slice("--one-pr-unit=".length);
79
+ }
63
80
  else {
64
81
  return { ...parsed, error: `unrecognized argument: ${arg}` };
65
82
  }
@@ -79,6 +96,7 @@ export function run(argv) {
79
96
  const result = evaluate(resolve(args.projectRoot), args.pr, {
80
97
  repo: args.repo,
81
98
  quiet: args.quiet,
99
+ onePrUnitId: args.onePrUnitId,
82
100
  });
83
101
  if (result.message.length > 0) {
84
102
  if (result.stream === "stdout") {
@@ -118,12 +118,12 @@ export function resolveClosingKeywordsSource(env, runGit = defaultRunGit) {
118
118
  export function buildClosingKeywordsCheckArgv(env, extra = [], runGit = defaultRunGit) {
119
119
  const source = resolveClosingKeywordsSource(env, runGit);
120
120
  if (source.kind === "pr") {
121
- return { argv: ["--mode", "fp", "--pr", source.pr, ...extra] };
121
+ return { argv: ["--mode", "both", "--pr", source.pr, ...extra] };
122
122
  }
123
123
  if (source.kind === "missing-base" || source.kind === "stale-base") {
124
124
  return { argv: [], error: source.reason };
125
125
  }
126
- return { argv: ["--mode", "fp", "--from-git-range", source.range, ...extra] };
126
+ return { argv: ["--mode", "both", "--from-git-range", source.range, ...extra] };
127
127
  }
128
128
  export function run(argv, env = process.env, invoke = cmdPrCheckClosingKeywords, runGit = defaultRunGit) {
129
129
  const built = buildClosingKeywordsCheckArgv(env, argv, runGit);
@@ -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 { evaluate, gitPorcelain, parseAllocationSection } from "@deftai/directive-core/story-ready";
5
+ import { evaluate, gitPorcelain, gitRevParseAbbrev, parseAllocationSection, } from "@deftai/directive-core/story-ready";
6
6
  import { ROUTING_GATED_DISPATCH_PROVIDERS, resolveDispatchProvider, verifyRouting, } from "@deftai/directive-core/swarm";
7
7
  /** Parse verify-story-ready CLI args, mirroring the Python argparse surface. */
8
8
  export function parseArgs(argv) {
@@ -149,6 +149,7 @@ export function run(argv) {
149
149
  allowDirty: args.allowDirty,
150
150
  parsed,
151
151
  projectRoot,
152
+ onePrUnitBranch: gitRevParseAbbrev(projectRoot),
152
153
  });
153
154
  if (result.exitCode === 0 && !args.skipRouting) {
154
155
  const provider = resolveDispatchProvider(process.env);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive",
3
- "version": "0.118.0",
3
+ "version": "0.119.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.118.0",
38
- "@deftai/directive-content": "^0.118.0"
37
+ "@deftai/directive-core": "^0.119.0",
38
+ "@deftai/directive-content": "^0.119.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b"