@deftai/directive 0.98.0 → 0.98.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -188,6 +188,9 @@ function routeNamespaceVerb(ns, verb, rest) {
188
188
  return { kind: "dispatch", argv: ["scope-decompose", ...rest] };
189
189
  if (verb === "undo")
190
190
  return { kind: "dispatch", argv: ["scope-undo", ...rest] };
191
+ if (verb === "record-approved-scope") {
192
+ return { kind: "dispatch", argv: ["scope-record-approved-scope", ...rest] };
193
+ }
191
194
  }
192
195
  if (ns === "verify") {
193
196
  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", "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-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-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"];
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", "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-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-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
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). */
package/dist/dispatch.js CHANGED
@@ -68,6 +68,7 @@ export const CLI_MODULE_VERBS = [
68
68
  "release-publish",
69
69
  "release-rollback",
70
70
  "scope-lifecycle",
71
+ "scope-record-approved-scope",
71
72
  "lifecycle-event",
72
73
  "lifecycle-stats",
73
74
  "session-start",
@@ -267,6 +268,7 @@ export const VERB_ALIASES = {
267
268
  "verify:forward-coverage": "verify-forward-coverage",
268
269
  "verify:test-boundary": "verify-test-boundary",
269
270
  "verify:scope-provenance": "verify-scope-provenance",
271
+ "scope:record-approved-scope": "scope-record-approved-scope",
270
272
  "verify:consumer-check-contract": "verify-consumer-check-contract",
271
273
  "coverage:hotspots": "coverage-hotspots",
272
274
  "verify:branch": "verify-branch",
@@ -2529,6 +2531,7 @@ const SCOPE_COMMAND_NAMES = [
2529
2531
  "scope:complete",
2530
2532
  "scope:demote",
2531
2533
  "scope:undo",
2534
+ "scope:record-approved-scope",
2532
2535
  ];
2533
2536
  /**
2534
2537
  * Deduplicated command names for `directive commands`, preferring colon-style
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ /** True when `candidate` is the same as or a descendant of `root` after realpath. */
3
+ export declare function isPathInsideRoot(root: string, candidate: string): boolean;
4
+ export interface ParsedArgs {
5
+ projectRoot: string;
6
+ xbriefPath: string;
7
+ actor: string;
8
+ kind: string;
9
+ /** Optional override for recorded xbriefRelPath (defaults: pending→active map). */
10
+ xbriefRelPath: string;
11
+ quiet: boolean;
12
+ error?: string;
13
+ }
14
+ /**
15
+ * Map pending/ → active/ for path-bound approval records.
16
+ * Returns null when the source cannot be expressed as a repo-relative path under projectRoot
17
+ * (absolute outside-root paths must not be stamped into xbriefRelPath — #3205 Greptile).
18
+ */
19
+ export declare function resolveApprovalXbriefRelPath(sourceRelOrAbs: string, projectRoot: string, override?: string): string | null;
20
+ export declare function parseArgs(argv: string[]): ParsedArgs;
21
+ export declare function run(argv: string[]): number;
22
+ //# sourceMappingURL=scope-record-approved-scope.d.ts.map
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for scope:record-approved-scope (#3205).
4
+ *
5
+ * Deposits a human-origin approved-scope digest under
6
+ * `.deft/approved-scope/<plan-id>.json` so verify:scope-provenance can
7
+ * authorize pending→active and operator-approved expansion without same-PR
8
+ * self-authorization.
9
+ */
10
+ import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
11
+ import { basename, join, relative, resolve, sep } from "node:path";
12
+ import { scopeProvenance } from "@deftai/directive-core";
13
+ import { isDirectEntrypoint } from "./entrypoint.js";
14
+ /** True when `candidate` is the same as or a descendant of `root` after realpath. */
15
+ export function isPathInsideRoot(root, candidate) {
16
+ let rootReal;
17
+ let candReal;
18
+ try {
19
+ rootReal = realpathSync(root);
20
+ // realpathSync fails if path does not exist; fall back to resolve for missing targets
21
+ candReal = existsSync(candidate) ? realpathSync(candidate) : resolve(candidate);
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ const rel = relative(rootReal, candReal);
27
+ if (rel === "")
28
+ return true;
29
+ if (rel.startsWith(`..${sep}`) ||
30
+ rel === ".." ||
31
+ rel.startsWith("../") ||
32
+ rel.startsWith("..\\")) {
33
+ return false;
34
+ }
35
+ // Absolute relative() means different drive/root (win32)
36
+ if (resolve(rel) === rel && rel.includes(":"))
37
+ return false;
38
+ return !rel.startsWith("..");
39
+ }
40
+ const { buildApprovedScopeRecord, isHumanApprovalStamp, writeApprovedScopeRecord } = scopeProvenance;
41
+ function usage() {
42
+ return ("usage: scope:record-approved-scope -- <xbrief-path> --actor <name> " +
43
+ "[--kind operator] [--project-root <dir>] [--xbrief-rel-path <posix>] [--quiet]\n" +
44
+ " Writes .deft/approved-scope/<plan-id>.json with a humanApproval stamp (#3205).\n" +
45
+ " Agent-shaped stamps are refused. Path-binds to xbrief/active/ by default when " +
46
+ "the source is under pending/ or active/.");
47
+ }
48
+ /**
49
+ * Map pending/ → active/ for path-bound approval records.
50
+ * Returns null when the source cannot be expressed as a repo-relative path under projectRoot
51
+ * (absolute outside-root paths must not be stamped into xbriefRelPath — #3205 Greptile).
52
+ */
53
+ export function resolveApprovalXbriefRelPath(sourceRelOrAbs, projectRoot, override) {
54
+ if (override !== undefined && override.trim().length > 0) {
55
+ const o = override.replace(/\\/g, "/").replace(/^\.\//, "");
56
+ if (o.startsWith("/") || /^[A-Za-z]:\//.test(o))
57
+ return null;
58
+ return o;
59
+ }
60
+ const root = resolve(projectRoot);
61
+ const abs = resolve(sourceRelOrAbs.includes(":") ||
62
+ sourceRelOrAbs.startsWith("/") ||
63
+ sourceRelOrAbs.startsWith("\\")
64
+ ? sourceRelOrAbs
65
+ : join(root, sourceRelOrAbs));
66
+ // Lexical + realpath containment (symlink to outside root must fail closed — #3205 Greptile).
67
+ if (!isPathInsideRoot(root, abs)) {
68
+ return null;
69
+ }
70
+ let rel = relative(root, abs).replace(/\\/g, "/");
71
+ if (rel.length === 0 || rel.startsWith("../") || rel === "..") {
72
+ return null;
73
+ }
74
+ try {
75
+ if (existsSync(abs) && lstatSync(abs).isSymbolicLink() && !isPathInsideRoot(root, abs)) {
76
+ return null;
77
+ }
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ // Normalize pending → active for future-active binding (issue #3205 multi-PR).
83
+ if (rel.startsWith("xbrief/pending/")) {
84
+ rel = `xbrief/active/${basename(rel)}`;
85
+ }
86
+ else if (rel.startsWith("vbrief/pending/")) {
87
+ rel = `vbrief/active/${basename(rel)}`;
88
+ }
89
+ return rel;
90
+ }
91
+ export function parseArgs(argv) {
92
+ const parsed = {
93
+ projectRoot: ".",
94
+ xbriefPath: "",
95
+ actor: "",
96
+ kind: "operator",
97
+ xbriefRelPath: "",
98
+ quiet: false,
99
+ };
100
+ const positionals = [];
101
+ for (let i = 0; i < argv.length; i += 1) {
102
+ const arg = argv[i];
103
+ if (arg === "--help" || arg === "-h") {
104
+ return { ...parsed, error: usage() };
105
+ }
106
+ if (arg === "--quiet") {
107
+ parsed.quiet = true;
108
+ }
109
+ else if (arg === "--project-root") {
110
+ const value = argv[i + 1];
111
+ if (value === undefined) {
112
+ return { ...parsed, error: "argument --project-root: expected one argument" };
113
+ }
114
+ parsed.projectRoot = value;
115
+ i += 1;
116
+ }
117
+ else if (arg?.startsWith("--project-root=")) {
118
+ parsed.projectRoot = arg.slice("--project-root=".length);
119
+ }
120
+ else if (arg === "--actor") {
121
+ const value = argv[i + 1];
122
+ if (value === undefined) {
123
+ return { ...parsed, error: "argument --actor: expected one argument" };
124
+ }
125
+ parsed.actor = value;
126
+ i += 1;
127
+ }
128
+ else if (arg?.startsWith("--actor=")) {
129
+ parsed.actor = arg.slice("--actor=".length);
130
+ }
131
+ else if (arg === "--kind") {
132
+ const value = argv[i + 1];
133
+ if (value === undefined) {
134
+ return { ...parsed, error: "argument --kind: expected one argument" };
135
+ }
136
+ parsed.kind = value;
137
+ i += 1;
138
+ }
139
+ else if (arg?.startsWith("--kind=")) {
140
+ parsed.kind = arg.slice("--kind=".length);
141
+ }
142
+ else if (arg === "--xbrief-rel-path") {
143
+ const value = argv[i + 1];
144
+ if (value === undefined) {
145
+ return { ...parsed, error: "argument --xbrief-rel-path: expected one argument" };
146
+ }
147
+ parsed.xbriefRelPath = value;
148
+ i += 1;
149
+ }
150
+ else if (arg?.startsWith("--xbrief-rel-path=")) {
151
+ parsed.xbriefRelPath = arg.slice("--xbrief-rel-path=".length);
152
+ }
153
+ else if (arg?.startsWith("-")) {
154
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
155
+ }
156
+ else if (arg !== undefined) {
157
+ positionals.push(arg);
158
+ }
159
+ }
160
+ if (positionals.length === 0) {
161
+ return { ...parsed, error: `missing xBRIEF path\n${usage()}` };
162
+ }
163
+ if (positionals.length > 1) {
164
+ return { ...parsed, error: `unexpected extra args: ${positionals.slice(1).join(" ")}` };
165
+ }
166
+ parsed.xbriefPath = positionals[0] ?? "";
167
+ if (parsed.actor.trim().length === 0) {
168
+ return {
169
+ ...parsed,
170
+ error: `argument --actor is required (human operator identity)\n${usage()}`,
171
+ };
172
+ }
173
+ return parsed;
174
+ }
175
+ export function run(argv) {
176
+ const args = parseArgs(argv);
177
+ if (args.error !== undefined) {
178
+ process.stderr.write(`scope_record_approved_scope: ${args.error}\n`);
179
+ return 2;
180
+ }
181
+ const projectRoot = resolve(args.projectRoot);
182
+ const xbriefAbs = resolve(projectRoot, args.xbriefPath);
183
+ if (!existsSync(xbriefAbs)) {
184
+ // Also try absolute path as given
185
+ const alt = resolve(args.xbriefPath);
186
+ if (!existsSync(alt)) {
187
+ process.stderr.write(`scope_record_approved_scope: xBRIEF not found: ${args.xbriefPath}\n`);
188
+ return 2;
189
+ }
190
+ }
191
+ const fullPath = existsSync(xbriefAbs) ? xbriefAbs : resolve(args.xbriefPath);
192
+ if (!isPathInsideRoot(projectRoot, fullPath)) {
193
+ process.stderr.write("scope_record_approved_scope: xBRIEF path escapes --project-root " +
194
+ "(symlink or absolute outside-root targets refused) (#3205).\n");
195
+ return 2;
196
+ }
197
+ let raw;
198
+ try {
199
+ raw = readFileSync(fullPath, "utf8");
200
+ }
201
+ catch (err) {
202
+ process.stderr.write(`scope_record_approved_scope: failed to read xBRIEF: ${String(err)}\n`);
203
+ return 2;
204
+ }
205
+ let payload;
206
+ try {
207
+ payload = JSON.parse(raw);
208
+ }
209
+ catch {
210
+ process.stderr.write("scope_record_approved_scope: xBRIEF is not valid JSON\n");
211
+ return 2;
212
+ }
213
+ const stamp = {
214
+ kind: args.kind.trim(),
215
+ actor: args.actor.trim(),
216
+ mintedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
217
+ mintedVia: "scope:record-approved-scope",
218
+ };
219
+ if (!isHumanApprovalStamp(stamp)) {
220
+ process.stderr.write("scope_record_approved_scope: refused agent/non-human stamp " +
221
+ `(kind=${stamp.kind}, actor=${stamp.actor}). Use a human operator actor ` +
222
+ "and kind in {operator,human,user,cli,github-user,interactive,renewed-approval} (#3205).\n");
223
+ return 1;
224
+ }
225
+ const xbriefRelPath = resolveApprovalXbriefRelPath(fullPath, projectRoot, args.xbriefRelPath.length > 0 ? args.xbriefRelPath : undefined);
226
+ if (xbriefRelPath === null) {
227
+ process.stderr.write("scope_record_approved_scope: xBRIEF path must resolve under --project-root " +
228
+ "(repo-relative path required for approval binding) (#3205).\n");
229
+ return 2;
230
+ }
231
+ const record = buildApprovedScopeRecord({
232
+ xbriefRelPath,
233
+ payload,
234
+ humanApproval: stamp,
235
+ xbriefRawText: raw,
236
+ });
237
+ const outPath = writeApprovedScopeRecord(projectRoot, record);
238
+ if (!args.quiet) {
239
+ process.stdout.write(`scope_record_approved_scope: wrote ${outPath}\n` +
240
+ ` planId: ${record.planId}\n` +
241
+ ` xbriefRelPath: ${record.xbriefRelPath}\n` +
242
+ ` fileScopeDigest: ${record.fileScopeDigest}\n` +
243
+ ` paths: ${record.fileScope.length}\n` +
244
+ ` humanApproval: ${stamp.kind}/${stamp.actor}\n` +
245
+ " Next: commit this file on the merge base (or a prior PR) before " +
246
+ "activation/expansion in the implementation change set (#3205).\n");
247
+ }
248
+ return 0;
249
+ }
250
+ if (isDirectEntrypoint(import.meta.url)) {
251
+ process.exit(run(process.argv.slice(2)));
252
+ }
253
+ //# sourceMappingURL=scope-record-approved-scope.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive",
3
- "version": "0.98.0",
3
+ "version": "0.98.1",
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.98.0",
38
- "@deftai/directive-content": "^0.98.0"
37
+ "@deftai/directive-core": "^0.98.1",
38
+ "@deftai/directive-content": "^0.98.1"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b"