@deftai/directive 0.103.0 → 0.105.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/verify-ac.js CHANGED
@@ -9,8 +9,9 @@
9
9
  import { existsSync, readdirSync, readFileSync } from "node:fs";
10
10
  import { join, resolve } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
+ import { resolveSessionCompletedVerifyAcTarget } from "@deftai/directive-core/check";
12
13
  import { formatRejectedLedger, resolveLiteralAcceptanceDetailed, } from "@deftai/directive-core/literal-acceptance";
13
- import { evaluateVerifyAcFromPath, readPlanAcceptance, } from "@deftai/directive-core/product-first-done-gate";
14
+ import { emitVerifyAcTerminalOutcome, evaluateVerifyAcFromPath, readPlanAcceptance, resolveAcceptanceGateProfile, } from "@deftai/directive-core/product-first-done-gate";
14
15
  /** Parse verify:ac CLI args. */
15
16
  export function parseArgs(argv) {
16
17
  const parsed = {
@@ -102,9 +103,20 @@ function listActiveXbriefs(projectRoot) {
102
103
  dir: dirs.join(" + "),
103
104
  };
104
105
  }
106
+ /** Three-state exit: pass=0, fail=1, config=2. A printed pass must not leak 201. */
107
+ export function clampVerifyAcExit(ok, code) {
108
+ if (ok)
109
+ return 0;
110
+ if (code === 2)
111
+ return 2;
112
+ return 1;
113
+ }
105
114
  /** Evaluate one or many xBRIEF paths; return worst non-zero code (fail closed). */
106
115
  function evaluatePaths(paths, options) {
107
116
  let worst = 0;
117
+ // One shared option profile per reader (#3497) — scope:complete resolves its own
118
+ // profile from the same table, so the two readers cannot drift apart again.
119
+ const profile = resolveAcceptanceGateProfile(options.softMissingXbrief ? "check" : "standalone");
108
120
  for (const path of paths) {
109
121
  if (!options.quiet && paths.length > 1) {
110
122
  process.stdout.write(`verify:ac — evaluating ${path}\n`);
@@ -113,7 +125,9 @@ function evaluatePaths(paths, options) {
113
125
  projectRoot: options.projectRoot,
114
126
  quiet: options.quiet,
115
127
  softMissingXbrief: options.softMissingXbrief,
116
- checkIntegrated: options.softMissingXbrief,
128
+ checkIntegrated: profile.checkIntegrated,
129
+ captureFromNarratives: profile.captureFromNarratives,
130
+ reuseMode: profile.reuseMode,
117
131
  env: process.env,
118
132
  });
119
133
  if (result.message.length > 0) {
@@ -124,9 +138,10 @@ function evaluatePaths(paths, options) {
124
138
  process.stderr.write(`${result.message}\n`);
125
139
  }
126
140
  }
127
- if (result.code !== 0 && (worst === 0 || result.code > worst)) {
141
+ const code = clampVerifyAcExit(result.ok, result.code);
142
+ if (code !== 0 && (worst === 0 || code > worst)) {
128
143
  // Prefer code 1 (fail) over 2 when both present — still non-zero.
129
- worst = result.code;
144
+ worst = code;
130
145
  }
131
146
  }
132
147
  return worst;
@@ -142,6 +157,11 @@ export function run(argv) {
142
157
  const args = parseArgs(argv);
143
158
  if (args.error !== undefined) {
144
159
  process.stderr.write(`verify_ac: ${args.error}\n`);
160
+ emitVerifyAcTerminalOutcome({
161
+ projectRoot: resolve(args.projectRoot),
162
+ env: process.env,
163
+ outcome: "config-error",
164
+ });
145
165
  return 2;
146
166
  }
147
167
  const projectRoot = resolve(args.projectRoot);
@@ -167,19 +187,55 @@ export function run(argv) {
167
187
  " Pass an explicit path, or use check composition (--soft-missing-xbrief) to evaluate all.\n" +
168
188
  " Usage: task verify:ac -- <path-to-active.xbrief.json>\n" +
169
189
  " Refs #3284 product-first done-gate\n");
190
+ emitVerifyAcTerminalOutcome({
191
+ projectRoot,
192
+ env: process.env,
193
+ outcome: "config-error",
194
+ });
170
195
  return 1;
171
196
  }
172
197
  }
173
198
  else if (args.softMissingXbrief) {
174
- if (!args.quiet) {
199
+ const completed = resolveSessionCompletedVerifyAcTarget({
200
+ projectRoot,
201
+ env: process.env,
202
+ });
203
+ if (completed.kind === "target") {
204
+ paths = [completed.path];
205
+ if (!args.quiet) {
206
+ process.stdout.write(`verify:ac targeting just-completed brief (#3357): ${completed.path}\n`);
207
+ }
208
+ }
209
+ else if (completed.kind === "cannot") {
210
+ process.stderr.write(`${completed.message}\n`);
211
+ emitVerifyAcTerminalOutcome({
212
+ projectRoot,
213
+ env: process.env,
214
+ outcome: "config-error",
215
+ });
216
+ return 1;
217
+ }
218
+ else if (!args.quiet) {
175
219
  process.stdout.write("verify:ac skipped (#3284 soft-missing): no active xBRIEF in xbrief/active/\n");
176
220
  }
177
- return 0;
221
+ if (completed.kind === "none") {
222
+ emitVerifyAcTerminalOutcome({
223
+ projectRoot,
224
+ env: process.env,
225
+ outcome: "soft-missing",
226
+ });
227
+ return 0;
228
+ }
178
229
  }
179
230
  else {
180
231
  process.stderr.write("verify_ac: pass an xBRIEF path or ensure exactly one artifact in xbrief/active/\n" +
181
232
  " Usage: task verify:ac -- <path-to-active.xbrief.json>\n" +
182
233
  " Refs #3284 product-first done-gate (mechanism #3267)\n");
234
+ emitVerifyAcTerminalOutcome({
235
+ projectRoot,
236
+ env: process.env,
237
+ outcome: "config-error",
238
+ });
183
239
  return 2;
184
240
  }
185
241
  }
@@ -200,14 +256,24 @@ export function run(argv) {
200
256
  }
201
257
  const acceptance = readPlanAcceptance(plan);
202
258
  const resolved = resolveLiteralAcceptanceDetailed(plan, { captureFromNarratives: true });
259
+ // Executor reads plan.acceptance.commands when the #3267 ledger is empty (#3449).
260
+ const executorCommands = resolved.commands.length > 0
261
+ ? resolved.commands
262
+ : acceptance.commands.map((c) => ({
263
+ command: c.command,
264
+ source: "explicit",
265
+ sourceSpan: "plan.acceptance.commands",
266
+ cwd: c.cwd ?? null,
267
+ expectedExitCode: c.expectedExitCode ?? 0,
268
+ }));
203
269
  reports.push({
204
270
  xbrief: xbriefPath,
205
271
  source_rung: acceptance.source_rung,
206
272
  none_stated: acceptance.none_stated,
207
273
  acceptance_commands: acceptance.commands,
208
- count: resolved.commands.length,
274
+ count: executorCommands.length,
209
275
  rejected_count: resolved.rejected.length,
210
- commands: resolved.commands.map((c) => ({
276
+ commands: executorCommands.map((c) => ({
211
277
  command: c.command,
212
278
  source: c.source,
213
279
  sourceSpan: c.sourceSpan ?? null,
@@ -3,11 +3,12 @@ interface ParsedArgs {
3
3
  projectRoot: string;
4
4
  repo: string | null;
5
5
  tip: string | null;
6
+ issue: number | null;
6
7
  quiet: boolean;
7
8
  skipGh: boolean;
8
9
  error?: string;
9
10
  }
10
- /** Parse verify-completed-tracked CLI args (#3264). */
11
+ /** Parse verify-completed-tracked CLI args (#3264 / #3476). */
11
12
  export declare function parseArgs(argv: string[]): ParsedArgs;
12
13
  /** Run the gate and return the process exit code. */
13
14
  export declare function run(argv: string[]): number;
@@ -2,12 +2,21 @@
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { evaluateCompletedTracked } from "@deftai/directive-core/lifecycle";
5
- /** Parse verify-completed-tracked CLI args (#3264). */
5
+ function parseIssueNumber(raw) {
6
+ const trimmed = raw.startsWith("#") ? raw.slice(1) : raw;
7
+ if (!/^\d+$/.test(trimmed)) {
8
+ return null;
9
+ }
10
+ const value = Number(trimmed);
11
+ return Number.isInteger(value) && value > 0 ? value : null;
12
+ }
13
+ /** Parse verify-completed-tracked CLI args (#3264 / #3476). */
6
14
  export function parseArgs(argv) {
7
15
  const parsed = {
8
16
  projectRoot: ".",
9
17
  repo: null,
10
18
  tip: null,
19
+ issue: null,
11
20
  quiet: false,
12
21
  skipGh: false,
13
22
  };
@@ -52,6 +61,26 @@ export function parseArgs(argv) {
52
61
  else if (arg?.startsWith("--tip=")) {
53
62
  parsed.tip = arg.slice("--tip=".length);
54
63
  }
64
+ else if (arg === "--issue") {
65
+ const value = argv[i + 1];
66
+ if (value === undefined) {
67
+ return { ...parsed, error: "argument --issue: expected one argument" };
68
+ }
69
+ const issue = parseIssueNumber(value);
70
+ if (issue === null) {
71
+ return { ...parsed, error: `argument --issue: expected a positive integer, got ${value}` };
72
+ }
73
+ parsed.issue = issue;
74
+ i += 1;
75
+ }
76
+ else if (arg?.startsWith("--issue=")) {
77
+ const value = arg.slice("--issue=".length);
78
+ const issue = parseIssueNumber(value);
79
+ if (issue === null) {
80
+ return { ...parsed, error: `argument --issue: expected a positive integer, got ${value}` };
81
+ }
82
+ parsed.issue = issue;
83
+ }
55
84
  else {
56
85
  return { ...parsed, error: `unrecognized argument: ${arg}` };
57
86
  }
@@ -70,6 +99,7 @@ export function run(argv) {
70
99
  quiet: args.quiet,
71
100
  repo: args.repo,
72
101
  tip: args.tip,
102
+ issue: args.issue,
73
103
  skipGh: args.skipGh,
74
104
  });
75
105
  if (result.message.length > 0) {
@@ -128,6 +128,13 @@ export function run(argv) {
128
128
  reason: r.reason,
129
129
  sourceSpan: r.sourceSpan ?? null,
130
130
  })),
131
+ // Prose-derived captures demoted by structured acceptance commands (#3484).
132
+ advisory_rejected_count: resolved.advisoryRejected.length,
133
+ advisory_rejected: resolved.advisoryRejected.map((r) => ({
134
+ command: r.command,
135
+ reason: r.reason,
136
+ sourceSpan: r.sourceSpan ?? null,
137
+ })),
131
138
  };
132
139
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
133
140
  if (resolved.rejected.length > 0) {
@@ -2,6 +2,7 @@
2
2
  interface ParsedArgs {
3
3
  projectRoot: string;
4
4
  repo: string | null;
5
+ issue: number | null;
5
6
  quiet: boolean;
6
7
  skipGh: boolean;
7
8
  error?: string;
@@ -2,11 +2,20 @@
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { evaluate } from "@deftai/directive-core/orphan-active";
5
+ function parseIssueNumber(raw) {
6
+ const trimmed = raw.startsWith("#") ? raw.slice(1) : raw;
7
+ if (!/^\d+$/.test(trimmed)) {
8
+ return null;
9
+ }
10
+ const value = Number(trimmed);
11
+ return Number.isInteger(value) && value > 0 ? value : null;
12
+ }
5
13
  /** Parse verify-orphan-active CLI args. */
6
14
  export function parseArgs(argv) {
7
15
  const parsed = {
8
16
  projectRoot: ".",
9
17
  repo: null,
18
+ issue: null,
10
19
  quiet: false,
11
20
  skipGh: false,
12
21
  };
@@ -40,6 +49,26 @@ export function parseArgs(argv) {
40
49
  else if (arg?.startsWith("--repo=")) {
41
50
  parsed.repo = arg.slice("--repo=".length);
42
51
  }
52
+ else if (arg === "--issue") {
53
+ const value = argv[i + 1];
54
+ if (value === undefined) {
55
+ return { ...parsed, error: "argument --issue: expected one argument" };
56
+ }
57
+ const issue = parseIssueNumber(value);
58
+ if (issue === null) {
59
+ return { ...parsed, error: `argument --issue: expected a positive integer, got ${value}` };
60
+ }
61
+ parsed.issue = issue;
62
+ i += 1;
63
+ }
64
+ else if (arg?.startsWith("--issue=")) {
65
+ const value = arg.slice("--issue=".length);
66
+ const issue = parseIssueNumber(value);
67
+ if (issue === null) {
68
+ return { ...parsed, error: `argument --issue: expected a positive integer, got ${value}` };
69
+ }
70
+ parsed.issue = issue;
71
+ }
43
72
  else {
44
73
  return { ...parsed, error: `unrecognized argument: ${arg}` };
45
74
  }
@@ -58,6 +87,7 @@ export function run(argv) {
58
87
  quiet: args.quiet,
59
88
  repo: args.repo,
60
89
  skipGh: args.skipGh,
90
+ issue: args.issue,
61
91
  });
62
92
  if (result.message.length > 0) {
63
93
  if (result.stream === "stdout") {
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { type DirectivePosture } from "@deftai/directive-core/session";
2
+ import { type DirectivePosture, type VerifyResult } from "@deftai/directive-core/session";
3
+ export interface VerifySessionRitualRunDeps {
4
+ readonly verifySessionRitual?: (projectRoot: string, options: {
5
+ tier: "quick" | "gated";
6
+ posture?: DirectivePosture;
7
+ }) => VerifyResult;
8
+ }
3
9
  interface ParsedArgs {
4
10
  projectRoot: string;
5
11
  tier: "quick" | "gated";
@@ -9,7 +15,9 @@ interface ParsedArgs {
9
15
  }
10
16
  /** Parse verify-session-ritual CLI args, mirroring scripts/verify_session_ritual.py. */
11
17
  export declare function parseArgs(argv: string[]): ParsedArgs;
18
+ /** True when the ritual failure is the gated cache_fresh step (#3506 / #3507). */
19
+ export declare function isCacheFreshFailureMessage(message: string): boolean;
12
20
  /** Run the gate and return the process exit code. */
13
- export declare function run(argv: string[]): number;
21
+ export declare function run(argv: string[], deps?: VerifySessionRitualRunDeps): number;
14
22
  export {};
15
23
  //# sourceMappingURL=verify-session-ritual.d.ts.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { emitBypassWarning, emitVerifyJson, verifySessionRitual, } from "@deftai/directive-core/session";
4
+ import { emitBypassWarning, emitVerifyJson, formatCacheFreshDeferSoftPath, formatRitualRecoveryInstruction, verifySessionRitual, } from "@deftai/directive-core/session";
5
5
  function parsePosture(value) {
6
6
  if (value === "read-only")
7
7
  return "read-only";
@@ -86,15 +86,20 @@ export function parseArgs(argv) {
86
86
  }
87
87
  return parsed;
88
88
  }
89
+ /** True when the ritual failure is the gated cache_fresh step (#3506 / #3507). */
90
+ export function isCacheFreshFailureMessage(message) {
91
+ return message.includes("cache_fresh") || message.includes("cache-fresh");
92
+ }
89
93
  /** Run the gate and return the process exit code. */
90
- export function run(argv) {
94
+ export function run(argv, deps = {}) {
91
95
  const args = parseArgs(argv);
92
96
  if (args.error !== undefined) {
93
97
  process.stderr.write(`verify_session_ritual: ${args.error}\n`);
94
98
  return 2;
95
99
  }
96
100
  const projectRoot = resolve(args.projectRoot);
97
- const result = verifySessionRitual(projectRoot, {
101
+ const verify = deps.verifySessionRitual ?? verifySessionRitual;
102
+ const result = verify(projectRoot, {
98
103
  tier: args.tier,
99
104
  posture: args.posture ?? undefined,
100
105
  });
@@ -108,6 +113,14 @@ export function run(argv) {
108
113
  process.stdout.write(`${result.message}\n`);
109
114
  }
110
115
  }
116
+ else if (result.code === 1) {
117
+ const recovery = formatRitualRecoveryInstruction(result.recoveryTier ?? "cold");
118
+ const lines = [result.message, recovery];
119
+ if (isCacheFreshFailureMessage(result.message)) {
120
+ lines.push(formatCacheFreshDeferSoftPath());
121
+ }
122
+ process.stderr.write(`${lines.join("\n")}\n`);
123
+ }
111
124
  else {
112
125
  process.stderr.write(`${result.message}\n`);
113
126
  }
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string;
4
+ enforce: boolean;
5
+ help?: boolean;
6
+ error?: string;
7
+ }
8
+ /** Parse verify-telemetry-coverage CLI args (#3362). */
9
+ export declare function parseArgs(argv: string[]): ParsedArgs;
10
+ /** Run the gate and return the process exit code. */
11
+ export declare function run(argv: string[]): number;
12
+ export {};
13
+ //# sourceMappingURL=verify-telemetry-coverage.d.ts.map
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for task verify:telemetry-coverage (#3362).
4
+ * Warn-only by default; pass --enforce to fail closed.
5
+ */
6
+ import { resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { evaluateTelemetryCoverage } from "@deftai/directive-core/verify-source";
9
+ /** Parse verify-telemetry-coverage CLI args (#3362). */
10
+ export function parseArgs(argv) {
11
+ const parsed = { projectRoot: ".", enforce: false };
12
+ for (let i = 0; i < argv.length; i += 1) {
13
+ const arg = argv[i];
14
+ if (arg === "--project-root") {
15
+ const value = argv[i + 1];
16
+ if (value === undefined) {
17
+ return { ...parsed, error: "argument --project-root: expected one argument" };
18
+ }
19
+ parsed.projectRoot = value;
20
+ i += 1;
21
+ }
22
+ else if (arg?.startsWith("--project-root=")) {
23
+ parsed.projectRoot = arg.slice("--project-root=".length);
24
+ }
25
+ else if (arg === "--enforce") {
26
+ parsed.enforce = true;
27
+ }
28
+ else if (arg === "--help" || arg === "-h") {
29
+ return { ...parsed, help: true };
30
+ }
31
+ else {
32
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
33
+ }
34
+ }
35
+ return parsed;
36
+ }
37
+ const HELP_TEXT = "Usage: verify-telemetry-coverage [--project-root <path>] [--enforce]\n" +
38
+ " Dead-surface detector for run-summary event kinds (#3362).\n" +
39
+ " Default: warn-only (exit 0 with advisory report).\n" +
40
+ " --enforce: fail closed (exit 1) when a kind has no caller or fixture.\n";
41
+ /** Run the gate and return the process exit code. */
42
+ export function run(argv) {
43
+ const args = parseArgs(argv);
44
+ if (args.help === true) {
45
+ process.stdout.write(HELP_TEXT);
46
+ return 0;
47
+ }
48
+ if (args.error !== undefined) {
49
+ process.stderr.write(`verify_telemetry_coverage: ${args.error}\n`);
50
+ return 2;
51
+ }
52
+ const result = evaluateTelemetryCoverage({
53
+ projectRoot: resolve(args.projectRoot),
54
+ enforce: args.enforce,
55
+ });
56
+ if (result.stream === "stdout") {
57
+ process.stdout.write(`${result.message}\n`);
58
+ }
59
+ else {
60
+ process.stderr.write(`${result.message}\n`);
61
+ }
62
+ return result.code;
63
+ }
64
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
65
+ process.exit(run(process.argv.slice(2)));
66
+ }
67
+ //# sourceMappingURL=verify-telemetry-coverage.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive",
3
- "version": "0.103.0",
3
+ "version": "0.105.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.103.0",
38
- "@deftai/directive-content": "^0.103.0"
37
+ "@deftai/directive-core": "^0.105.0",
38
+ "@deftai/directive-content": "^0.105.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b"