@deftai/directive 0.119.12 → 0.120.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,514 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Agent-facing pre-PR controller entry (#4912 Limb 3).
4
+ * `deft pre-pr:run` starts the prescribed workflow. Opening the skill file
5
+ * is not completion. Generic mark-complete cannot mint a pass.
6
+ */
7
+ import { completeRun, computeControllerObservedHash, controllerPublisher, digestApprovedCriteria, evaluateLivePrePrCheck, getDefaultPrePrStore, markComplete, noteSkillFileOpen, observeCommandPhase, PRE_PR_PHASE_IDS, phaseSpec, resolveRecordFromStore, startControllerRun, submitReviewerReport, } from "@deftai/directive-core/pre-pr-controller";
8
+ import { isDirectEntrypoint } from "./entrypoint.js";
9
+ const USAGE = `Usage: deft pre-pr:run [options]
10
+
11
+ Start the repository-controlled pre-PR workflow. Opening the skill file is
12
+ not completion. A generic mark-complete cannot mint a pass.
13
+
14
+ Options:
15
+ --repo <owner/name>
16
+ --base-sha <sha>
17
+ --head-sha <sha>
18
+ --tree-hash <sha>
19
+ --pr-body-hash <hex>
20
+ --pr-node-id <id>
21
+ --approved-revision <sha>
22
+ --scope <path> (repeatable)
23
+ --acceptance <text>
24
+ --generation <n>
25
+ --skill-version <ver>
26
+ --policy-version <ver>
27
+ --run-id <id> lookup hint only
28
+ --complete publisher-backed complete (fails without observables)
29
+ --observe-command record a command-observable phase before --complete
30
+ --observe-semantic record semantic / final-no-change evidence
31
+ --phase <id>
32
+ --command <text>
33
+ --exit-code <n> required with --observe-command; omit is a parse error
34
+ --input-hash <hex>
35
+ --skip-reason <text> skip also requires a non-zero --exit-code
36
+ --supplied-contents-hash <hex>
37
+ --controller-observed-hash <hex>
38
+ optional; must match independently hashed reviewed files
39
+ --criteria-digest <hex>
40
+ --reviewed-file <path> (repeatable)
41
+ --reviewer-report-ref <ref>
42
+ --mark-complete always refuses; cannot mint a pass
43
+ --skill-open always refuses; opening the skill is not completion
44
+ --evaluate evaluate live binding against the private store
45
+ --json
46
+ --help
47
+ `;
48
+ export function parseArgs(argv) {
49
+ const parsed = {
50
+ repo: "",
51
+ baseSha: "",
52
+ headSha: "",
53
+ treeHash: "",
54
+ prBodyHash: "",
55
+ prNodeId: null,
56
+ approvedRevision: "",
57
+ scope: [],
58
+ acceptance: "",
59
+ generation: 1,
60
+ skillVersion: "0.1",
61
+ policyVersion: "1",
62
+ runId: null,
63
+ complete: false,
64
+ observeCommand: false,
65
+ observeSemantic: false,
66
+ phase: null,
67
+ command: "",
68
+ exitCode: null,
69
+ inputHash: null,
70
+ skipReason: null,
71
+ suppliedContentsHash: "",
72
+ controllerObservedHash: "",
73
+ criteriaDigest: null,
74
+ reviewedFiles: [],
75
+ reviewerReportRef: null,
76
+ markComplete: false,
77
+ skillOpen: false,
78
+ evaluate: false,
79
+ json: false,
80
+ help: false,
81
+ };
82
+ for (let i = 0; i < argv.length; i += 1) {
83
+ const arg = argv[i];
84
+ if (arg === "--help" || arg === "-h") {
85
+ parsed.help = true;
86
+ }
87
+ else if (arg === "--json") {
88
+ parsed.json = true;
89
+ }
90
+ else if (arg === "--complete") {
91
+ parsed.complete = true;
92
+ }
93
+ else if (arg === "--observe-command") {
94
+ parsed.observeCommand = true;
95
+ }
96
+ else if (arg === "--observe-semantic") {
97
+ parsed.observeSemantic = true;
98
+ }
99
+ else if (arg === "--phase" || arg?.startsWith("--phase=")) {
100
+ const value = arg === "--phase" ? argv[i + 1] : arg.slice("--phase=".length);
101
+ if (value === undefined)
102
+ return { ...parsed, error: "argument --phase: expected one argument" };
103
+ parsed.phase = value;
104
+ if (arg === "--phase")
105
+ i += 1;
106
+ }
107
+ else if (arg === "--command" || arg?.startsWith("--command=")) {
108
+ const value = arg === "--command" ? argv[i + 1] : arg.slice("--command=".length);
109
+ if (value === undefined)
110
+ return { ...parsed, error: "argument --command: expected one argument" };
111
+ parsed.command = value;
112
+ if (arg === "--command")
113
+ i += 1;
114
+ }
115
+ else if (arg === "--exit-code" || arg?.startsWith("--exit-code=")) {
116
+ const value = arg === "--exit-code" ? argv[i + 1] : arg.slice("--exit-code=".length);
117
+ if (value === undefined) {
118
+ return { ...parsed, error: "argument --exit-code: expected one argument" };
119
+ }
120
+ const n = Number.parseInt(value, 10);
121
+ if (!Number.isFinite(n)) {
122
+ return { ...parsed, error: "argument --exit-code: expected an integer" };
123
+ }
124
+ parsed.exitCode = n;
125
+ if (arg === "--exit-code")
126
+ i += 1;
127
+ }
128
+ else if (arg === "--input-hash" || arg?.startsWith("--input-hash=")) {
129
+ const value = arg === "--input-hash" ? argv[i + 1] : arg.slice("--input-hash=".length);
130
+ if (value === undefined) {
131
+ return { ...parsed, error: "argument --input-hash: expected one argument" };
132
+ }
133
+ parsed.inputHash = value;
134
+ if (arg === "--input-hash")
135
+ i += 1;
136
+ }
137
+ else if (arg === "--skip-reason" || arg?.startsWith("--skip-reason=")) {
138
+ const value = arg === "--skip-reason" ? argv[i + 1] : arg.slice("--skip-reason=".length);
139
+ if (value === undefined) {
140
+ return { ...parsed, error: "argument --skip-reason: expected one argument" };
141
+ }
142
+ parsed.skipReason = value;
143
+ if (arg === "--skip-reason")
144
+ i += 1;
145
+ }
146
+ else if (arg === "--supplied-contents-hash" || arg?.startsWith("--supplied-contents-hash=")) {
147
+ const value = arg === "--supplied-contents-hash"
148
+ ? argv[i + 1]
149
+ : arg.slice("--supplied-contents-hash=".length);
150
+ if (value === undefined) {
151
+ return { ...parsed, error: "argument --supplied-contents-hash: expected one argument" };
152
+ }
153
+ parsed.suppliedContentsHash = value;
154
+ if (arg === "--supplied-contents-hash")
155
+ i += 1;
156
+ }
157
+ else if (arg === "--controller-observed-hash" ||
158
+ arg?.startsWith("--controller-observed-hash=")) {
159
+ const value = arg === "--controller-observed-hash"
160
+ ? argv[i + 1]
161
+ : arg.slice("--controller-observed-hash=".length);
162
+ if (value === undefined) {
163
+ return {
164
+ ...parsed,
165
+ error: "argument --controller-observed-hash: expected one argument",
166
+ };
167
+ }
168
+ parsed.controllerObservedHash = value;
169
+ if (arg === "--controller-observed-hash")
170
+ i += 1;
171
+ }
172
+ else if (arg === "--criteria-digest" || arg?.startsWith("--criteria-digest=")) {
173
+ const value = arg === "--criteria-digest" ? argv[i + 1] : arg.slice("--criteria-digest=".length);
174
+ if (value === undefined) {
175
+ return { ...parsed, error: "argument --criteria-digest: expected one argument" };
176
+ }
177
+ parsed.criteriaDigest = value;
178
+ if (arg === "--criteria-digest")
179
+ i += 1;
180
+ }
181
+ else if (arg === "--reviewed-file" || arg?.startsWith("--reviewed-file=")) {
182
+ const value = arg === "--reviewed-file" ? argv[i + 1] : arg.slice("--reviewed-file=".length);
183
+ if (value === undefined) {
184
+ return { ...parsed, error: "argument --reviewed-file: expected one argument" };
185
+ }
186
+ parsed.reviewedFiles.push(value);
187
+ if (arg === "--reviewed-file")
188
+ i += 1;
189
+ }
190
+ else if (arg === "--reviewer-report-ref" || arg?.startsWith("--reviewer-report-ref=")) {
191
+ const value = arg === "--reviewer-report-ref" ? argv[i + 1] : arg.slice("--reviewer-report-ref=".length);
192
+ if (value === undefined) {
193
+ return { ...parsed, error: "argument --reviewer-report-ref: expected one argument" };
194
+ }
195
+ parsed.reviewerReportRef = value;
196
+ if (arg === "--reviewer-report-ref")
197
+ i += 1;
198
+ }
199
+ else if (arg === "--mark-complete") {
200
+ parsed.markComplete = true;
201
+ }
202
+ else if (arg === "--skill-open") {
203
+ parsed.skillOpen = true;
204
+ }
205
+ else if (arg === "--evaluate") {
206
+ parsed.evaluate = true;
207
+ }
208
+ else if (arg === "--repo" || arg?.startsWith("--repo=")) {
209
+ const value = arg === "--repo" ? argv[i + 1] : arg.slice("--repo=".length);
210
+ if (value === undefined)
211
+ return { ...parsed, error: "argument --repo: expected one argument" };
212
+ parsed.repo = value;
213
+ if (arg === "--repo")
214
+ i += 1;
215
+ }
216
+ else if (arg === "--base-sha" || arg?.startsWith("--base-sha=")) {
217
+ const value = arg === "--base-sha" ? argv[i + 1] : arg.slice("--base-sha=".length);
218
+ if (value === undefined)
219
+ return { ...parsed, error: "argument --base-sha: expected one argument" };
220
+ parsed.baseSha = value;
221
+ if (arg === "--base-sha")
222
+ i += 1;
223
+ }
224
+ else if (arg === "--head-sha" || arg?.startsWith("--head-sha=")) {
225
+ const value = arg === "--head-sha" ? argv[i + 1] : arg.slice("--head-sha=".length);
226
+ if (value === undefined)
227
+ return { ...parsed, error: "argument --head-sha: expected one argument" };
228
+ parsed.headSha = value;
229
+ if (arg === "--head-sha")
230
+ i += 1;
231
+ }
232
+ else if (arg === "--tree-hash" || arg?.startsWith("--tree-hash=")) {
233
+ const value = arg === "--tree-hash" ? argv[i + 1] : arg.slice("--tree-hash=".length);
234
+ if (value === undefined)
235
+ return { ...parsed, error: "argument --tree-hash: expected one argument" };
236
+ parsed.treeHash = value;
237
+ if (arg === "--tree-hash")
238
+ i += 1;
239
+ }
240
+ else if (arg === "--pr-body-hash" || arg?.startsWith("--pr-body-hash=")) {
241
+ const value = arg === "--pr-body-hash" ? argv[i + 1] : arg.slice("--pr-body-hash=".length);
242
+ if (value === undefined) {
243
+ return { ...parsed, error: "argument --pr-body-hash: expected one argument" };
244
+ }
245
+ parsed.prBodyHash = value;
246
+ if (arg === "--pr-body-hash")
247
+ i += 1;
248
+ }
249
+ else if (arg === "--pr-node-id" || arg?.startsWith("--pr-node-id=")) {
250
+ const value = arg === "--pr-node-id" ? argv[i + 1] : arg.slice("--pr-node-id=".length);
251
+ if (value === undefined) {
252
+ return { ...parsed, error: "argument --pr-node-id: expected one argument" };
253
+ }
254
+ parsed.prNodeId = value;
255
+ if (arg === "--pr-node-id")
256
+ i += 1;
257
+ }
258
+ else if (arg === "--approved-revision" || arg?.startsWith("--approved-revision=")) {
259
+ const value = arg === "--approved-revision" ? argv[i + 1] : arg.slice("--approved-revision=".length);
260
+ if (value === undefined) {
261
+ return { ...parsed, error: "argument --approved-revision: expected one argument" };
262
+ }
263
+ parsed.approvedRevision = value;
264
+ if (arg === "--approved-revision")
265
+ i += 1;
266
+ }
267
+ else if (arg === "--scope" || arg?.startsWith("--scope=")) {
268
+ const value = arg === "--scope" ? argv[i + 1] : arg.slice("--scope=".length);
269
+ if (value === undefined)
270
+ return { ...parsed, error: "argument --scope: expected one argument" };
271
+ parsed.scope.push(value);
272
+ if (arg === "--scope")
273
+ i += 1;
274
+ }
275
+ else if (arg === "--acceptance" || arg?.startsWith("--acceptance=")) {
276
+ const value = arg === "--acceptance" ? argv[i + 1] : arg.slice("--acceptance=".length);
277
+ if (value === undefined) {
278
+ return { ...parsed, error: "argument --acceptance: expected one argument" };
279
+ }
280
+ parsed.acceptance = value;
281
+ if (arg === "--acceptance")
282
+ i += 1;
283
+ }
284
+ else if (arg === "--generation" || arg?.startsWith("--generation=")) {
285
+ const value = arg === "--generation" ? argv[i + 1] : arg.slice("--generation=".length);
286
+ if (value === undefined) {
287
+ return { ...parsed, error: "argument --generation: expected one argument" };
288
+ }
289
+ const n = Number.parseInt(value, 10);
290
+ if (!Number.isFinite(n) || n < 1) {
291
+ return { ...parsed, error: "argument --generation: expected a positive integer" };
292
+ }
293
+ parsed.generation = n;
294
+ if (arg === "--generation")
295
+ i += 1;
296
+ }
297
+ else if (arg === "--skill-version" || arg?.startsWith("--skill-version=")) {
298
+ const value = arg === "--skill-version" ? argv[i + 1] : arg.slice("--skill-version=".length);
299
+ if (value === undefined) {
300
+ return { ...parsed, error: "argument --skill-version: expected one argument" };
301
+ }
302
+ parsed.skillVersion = value;
303
+ if (arg === "--skill-version")
304
+ i += 1;
305
+ }
306
+ else if (arg === "--policy-version" || arg?.startsWith("--policy-version=")) {
307
+ const value = arg === "--policy-version" ? argv[i + 1] : arg.slice("--policy-version=".length);
308
+ if (value === undefined) {
309
+ return { ...parsed, error: "argument --policy-version: expected one argument" };
310
+ }
311
+ parsed.policyVersion = value;
312
+ if (arg === "--policy-version")
313
+ i += 1;
314
+ }
315
+ else if (arg === "--run-id" || arg?.startsWith("--run-id=")) {
316
+ const value = arg === "--run-id" ? argv[i + 1] : arg.slice("--run-id=".length);
317
+ if (value === undefined)
318
+ return { ...parsed, error: "argument --run-id: expected one argument" };
319
+ parsed.runId = value;
320
+ if (arg === "--run-id")
321
+ i += 1;
322
+ }
323
+ else {
324
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
325
+ }
326
+ }
327
+ return parsed;
328
+ }
329
+ function emit(json, payload, text, err) {
330
+ const line = json ? `${JSON.stringify(payload)}\n` : `${text}\n`;
331
+ if (err)
332
+ process.stderr.write(line);
333
+ else
334
+ process.stdout.write(line);
335
+ }
336
+ function asPhaseId(value) {
337
+ return PRE_PR_PHASE_IDS.includes(value) ? value : null;
338
+ }
339
+ export function run(argv) {
340
+ const args = parseArgs(argv);
341
+ if (args.help) {
342
+ process.stdout.write(USAGE);
343
+ return 0;
344
+ }
345
+ if (args.error !== undefined) {
346
+ process.stderr.write(`pre_pr_run: ${args.error}\n`);
347
+ return 2;
348
+ }
349
+ if (args.skillOpen) {
350
+ const d = noteSkillFileOpen();
351
+ emit(args.json, d, d.message, true);
352
+ return 1;
353
+ }
354
+ const store = getDefaultPrePrStore();
355
+ if (args.markComplete) {
356
+ const d = markComplete(store, args.runId ?? "");
357
+ emit(args.json, d, d.message, true);
358
+ return 1;
359
+ }
360
+ if (args.observeCommand || args.observeSemantic) {
361
+ if (args.runId === null || args.runId.length === 0) {
362
+ process.stderr.write("pre_pr_run: observe requires --run-id\n");
363
+ return 2;
364
+ }
365
+ if (args.phase === null || args.phase.length === 0) {
366
+ process.stderr.write("pre_pr_run: observe requires --phase\n");
367
+ return 2;
368
+ }
369
+ const phase = asPhaseId(args.phase);
370
+ if (phase === null) {
371
+ process.stderr.write(`pre_pr_run: unknown phase ${args.phase}\n`);
372
+ return 2;
373
+ }
374
+ const rec = store.getById(args.runId);
375
+ if (args.observeCommand) {
376
+ if (args.exitCode === null) {
377
+ process.stderr.write("pre_pr_run: --observe-command requires --exit-code\n");
378
+ return 2;
379
+ }
380
+ if (args.skipReason !== null && args.skipReason.length > 0 && args.exitCode === 0) {
381
+ process.stderr.write("pre_pr_run: skip requires a non-zero --exit-code plus --skip-reason\n");
382
+ return 2;
383
+ }
384
+ const spec = phaseSpec(phase);
385
+ const d = observeCommandPhase(store, args.runId, {
386
+ phaseId: phase,
387
+ command: args.command.length > 0 ? args.command : (spec.command ?? ""),
388
+ exitCode: args.exitCode,
389
+ inputHash: args.inputHash ?? rec?.inputHash ?? "",
390
+ skipReason: args.skipReason,
391
+ });
392
+ emit(args.json, d, d.message, !d.ok);
393
+ return d.ok ? 0 : 1;
394
+ }
395
+ if (args.suppliedContentsHash.length === 0) {
396
+ process.stderr.write("pre_pr_run: --observe-semantic requires --supplied-contents-hash\n");
397
+ return 2;
398
+ }
399
+ if (args.reviewedFiles.length === 0) {
400
+ process.stderr.write("pre_pr_run: --observe-semantic requires --reviewed-file\n");
401
+ return 2;
402
+ }
403
+ const observed = computeControllerObservedHash({ reviewedFiles: args.reviewedFiles });
404
+ if (observed === null) {
405
+ process.stderr.write("pre_pr_run: --observe-semantic could not hash reviewed-file contents\n");
406
+ return 2;
407
+ }
408
+ if (args.controllerObservedHash.length > 0 && args.controllerObservedHash !== observed) {
409
+ process.stderr.write("pre_pr_run: --controller-observed-hash does not match independently hashed reviewed-file contents\n");
410
+ return 1;
411
+ }
412
+ if (args.suppliedContentsHash !== observed) {
413
+ process.stderr.write("pre_pr_run: --supplied-contents-hash does not match independently hashed reviewed-file contents\n");
414
+ return 1;
415
+ }
416
+ const d = submitReviewerReport(store, args.runId, {
417
+ phaseId: phase,
418
+ reviewedFileManifest: args.reviewedFiles,
419
+ suppliedContentsHash: args.suppliedContentsHash,
420
+ criteriaDigest: args.criteriaDigest ?? rec?.criteria.digest ?? "",
421
+ reviewerReportRef: args.reviewerReportRef,
422
+ controllerObservedHash: observed,
423
+ });
424
+ emit(args.json, d, d.message, !d.ok);
425
+ return d.ok ? 0 : 1;
426
+ }
427
+ if (args.evaluate) {
428
+ if (args.repo.length === 0 || args.baseSha.length === 0 || args.headSha.length === 0) {
429
+ process.stderr.write("pre_pr_run: --evaluate requires --repo --base-sha --head-sha\n");
430
+ return 2;
431
+ }
432
+ if (args.prBodyHash.length === 0) {
433
+ process.stderr.write("pre_pr_run: --evaluate requires --pr-body-hash\n");
434
+ return 2;
435
+ }
436
+ const record = resolveRecordFromStore(store, {
437
+ id: args.runId,
438
+ prNodeId: args.prNodeId,
439
+ });
440
+ const headSource = args.approvedRevision.length > 0
441
+ ? args.approvedRevision
442
+ : (record?.criteria.sourceRevisionSha ?? args.headSha);
443
+ const liveApproved = digestApprovedCriteria({
444
+ sourceRevisionSha: headSource,
445
+ scopePaths: args.scope,
446
+ acceptanceText: args.acceptance,
447
+ generation: args.generation,
448
+ });
449
+ const d = evaluateLivePrePrCheck({
450
+ store,
451
+ liveBinding: {
452
+ repo: args.repo,
453
+ baseSha: args.baseSha,
454
+ headSha: args.headSha,
455
+ prNodeId: args.prNodeId,
456
+ prBodyHash: args.prBodyHash,
457
+ },
458
+ presentedRunId: args.runId,
459
+ approvedCriteria: liveApproved,
460
+ currentGeneration: args.generation,
461
+ headCriteria: liveApproved,
462
+ });
463
+ emit(args.json, d, d.message, !d.ok);
464
+ return d.ok ? 0 : 1;
465
+ }
466
+ if (args.complete) {
467
+ if (args.runId === null || args.runId.length === 0) {
468
+ process.stderr.write("pre_pr_run: --complete requires --run-id\n");
469
+ return 2;
470
+ }
471
+ const d = completeRun(store, controllerPublisher(), args.runId);
472
+ emit(args.json, d, d.message, !d.ok);
473
+ return d.ok ? 0 : 1;
474
+ }
475
+ if (args.repo.length === 0 ||
476
+ args.baseSha.length === 0 ||
477
+ args.headSha.length === 0 ||
478
+ args.treeHash.length === 0 ||
479
+ args.prBodyHash.length === 0 ||
480
+ args.approvedRevision.length === 0) {
481
+ process.stderr.write("pre_pr_run: start requires --repo --base-sha --head-sha --tree-hash --pr-body-hash --approved-revision\n");
482
+ return 2;
483
+ }
484
+ const criteria = digestApprovedCriteria({
485
+ sourceRevisionSha: args.approvedRevision,
486
+ scopePaths: args.scope,
487
+ acceptanceText: args.acceptance,
488
+ generation: args.generation,
489
+ });
490
+ const started = startControllerRun(store, {
491
+ repo: args.repo,
492
+ baseSha: args.baseSha,
493
+ headSha: args.headSha,
494
+ treeHash: args.treeHash,
495
+ prBodyHash: args.prBodyHash,
496
+ prNodeId: args.prNodeId,
497
+ criteria,
498
+ skillVersion: args.skillVersion,
499
+ policyVersion: args.policyVersion,
500
+ approvedRevisionSha: args.approvedRevision,
501
+ runId: args.runId ?? undefined,
502
+ });
503
+ if (!started.ok) {
504
+ emit(args.json, started.decision, started.decision.message, true);
505
+ return 1;
506
+ }
507
+ const payload = { ok: started.ok, runId: started.runId, hint: "run id is a lookup hint only" };
508
+ emit(args.json, payload, `started ${started.runId} (lookup hint only; private store is authoritative)`, false);
509
+ return 0;
510
+ }
511
+ if (isDirectEntrypoint(import.meta.url)) {
512
+ process.exit(run(process.argv.slice(2)));
513
+ }
514
+ //# sourceMappingURL=pre-pr-run.js.map
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import { dirname, resolve } from "node:path";
2
3
  import { fileURLToPath } from "node:url";
3
4
  import { emitJson, evaluate, PREFLIGHT_USAGE_HINT } from "@deftai/directive-core/preflight";
4
5
  import { scanWorkClaimForBriefPath } from "@deftai/directive-core/scm";
6
+ import { refuseIfWslOwnershipBlocked } from "./wsl-ownership-guard.js";
5
7
  const HELP_TEXT = `usage: xbrief:preflight [--vbrief-path PATH | PATH] [--json] [--help]
6
8
 
7
9
  Implementation-intent gate (#810): exits 0 only when the xBRIEF is in
@@ -100,6 +102,10 @@ export function run(argv, scan = scanWorkClaimForBriefPath) {
100
102
  return 2;
101
103
  }
102
104
  const vbriefPath = args.vbriefPath;
105
+ // #1617: gate the brief's tree, not process.cwd() (cross-directory callers).
106
+ const blocked = refuseIfWslOwnershipBlocked(dirname(resolve(vbriefPath)));
107
+ if (blocked !== null)
108
+ return blocked;
103
109
  const result = evaluate(vbriefPath);
104
110
  let scanLines = [];
105
111
  try {
@@ -6,9 +6,12 @@ interface ParsedArgs {
6
6
  issue: number | null;
7
7
  quiet: boolean;
8
8
  skipGh: boolean;
9
+ /** Opt-in discovery when --issue is set; unscoped defaults to on (#3495). */
10
+ discover: boolean | null;
11
+ enforce: boolean;
9
12
  error?: string;
10
13
  }
11
- /** Parse verify-completed-tracked CLI args (#3264 / #3476). */
14
+ /** Parse verify-completed-tracked CLI args (#3264 / #3476 / #3495). */
12
15
  export declare function parseArgs(argv: string[]): ParsedArgs;
13
16
  /** Run the gate and return the process exit code. */
14
17
  export declare function run(argv: string[]): number;
@@ -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 { COMPLETED_TRACKED_PROGRESS_MIN_BLOBS, COMPLETED_TRACKED_PROGRESS_THRESHOLD_MS, evaluateCompletedTracked, shouldAnnounceProgress, shouldAnnounceUpFrontCount, } from "@deftai/directive-core/lifecycle";
4
+ import { COMPLETED_TRACKED_PROGRESS_MIN_BLOBS, COMPLETED_TRACKED_PROGRESS_THRESHOLD_MS, evaluateCompletedTracked, evaluateShippedClosedDiscovery, shouldAnnounceProgress, shouldAnnounceUpFrontCount, } from "@deftai/directive-core/lifecycle";
5
5
  function parseIssueNumber(raw) {
6
6
  const trimmed = raw.startsWith("#") ? raw.slice(1) : raw;
7
7
  if (!/^\d+$/.test(trimmed)) {
@@ -10,7 +10,7 @@ function parseIssueNumber(raw) {
10
10
  const value = Number(trimmed);
11
11
  return Number.isInteger(value) && value > 0 ? value : null;
12
12
  }
13
- /** Parse verify-completed-tracked CLI args (#3264 / #3476). */
13
+ /** Parse verify-completed-tracked CLI args (#3264 / #3476 / #3495). */
14
14
  export function parseArgs(argv) {
15
15
  const parsed = {
16
16
  projectRoot: ".",
@@ -19,6 +19,8 @@ export function parseArgs(argv) {
19
19
  issue: null,
20
20
  quiet: false,
21
21
  skipGh: false,
22
+ discover: null,
23
+ enforce: false,
22
24
  };
23
25
  for (let i = 0; i < argv.length; i += 1) {
24
26
  const arg = argv[i];
@@ -28,6 +30,15 @@ export function parseArgs(argv) {
28
30
  else if (arg === "--skip-gh") {
29
31
  parsed.skipGh = true;
30
32
  }
33
+ else if (arg === "--discover") {
34
+ parsed.discover = true;
35
+ }
36
+ else if (arg === "--no-discover") {
37
+ parsed.discover = false;
38
+ }
39
+ else if (arg === "--enforce") {
40
+ parsed.enforce = true;
41
+ }
31
42
  else if (arg === "--project-root") {
32
43
  const value = argv[i + 1];
33
44
  if (value === undefined) {
@@ -87,6 +98,16 @@ export function parseArgs(argv) {
87
98
  }
88
99
  return parsed;
89
100
  }
101
+ function shouldRunDiscovery(args) {
102
+ if (args.discover === false) {
103
+ return false;
104
+ }
105
+ if (args.discover === true || args.enforce) {
106
+ return true;
107
+ }
108
+ // Warn-first default: unscoped corpus scans discover; --issue N stays land-only.
109
+ return args.issue === null;
110
+ }
90
111
  /** Run the gate and return the process exit code. */
91
112
  export function run(argv) {
92
113
  const args = parseArgs(argv);
@@ -97,7 +118,7 @@ export function run(argv) {
97
118
  const projectRoot = resolve(args.projectRoot);
98
119
  const thresholdMs = resolveProgressThresholdMs();
99
120
  let announced = false;
100
- const result = evaluateCompletedTracked(projectRoot, {
121
+ const land = evaluateCompletedTracked(projectRoot, {
101
122
  quiet: args.quiet,
102
123
  repo: args.repo,
103
124
  tip: args.tip,
@@ -117,15 +138,45 @@ export function run(argv) {
117
138
  writeProgress(event);
118
139
  },
119
140
  });
120
- if (result.message.length > 0) {
121
- if (result.stream === "stdout") {
122
- process.stdout.write(`${result.message}\n`);
141
+ let discoveryCode = 0;
142
+ let discoveryMessage = "";
143
+ let discoveryStream = "none";
144
+ if (shouldRunDiscovery(args)) {
145
+ const discovery = evaluateShippedClosedDiscovery(projectRoot, {
146
+ quiet: args.quiet,
147
+ repo: args.repo,
148
+ tip: args.tip,
149
+ skipGh: args.skipGh,
150
+ enforce: args.enforce,
151
+ });
152
+ discoveryCode = discovery.code;
153
+ discoveryMessage = discovery.message;
154
+ discoveryStream = discovery.stream;
155
+ }
156
+ // Scoped land fail-closed must not be softened by discovery warn (#3495 Reject).
157
+ if (land.message.length > 0) {
158
+ if (land.stream === "stdout") {
159
+ process.stdout.write(`${land.message}\n`);
123
160
  }
124
- else if (result.stream === "stderr") {
125
- process.stderr.write(`${result.message}\n`);
161
+ else if (land.stream === "stderr") {
162
+ process.stderr.write(`${land.message}\n`);
126
163
  }
127
164
  }
128
- return result.code;
165
+ if (discoveryMessage.length > 0) {
166
+ if (discoveryStream === "stdout") {
167
+ process.stdout.write(`${discoveryMessage}\n`);
168
+ }
169
+ else if (discoveryStream === "stderr") {
170
+ process.stderr.write(`${discoveryMessage}\n`);
171
+ }
172
+ }
173
+ if (land.code === 2 || discoveryCode === 2) {
174
+ return 2;
175
+ }
176
+ if (land.code === 1 || discoveryCode === 1) {
177
+ return 1;
178
+ }
179
+ return 0;
129
180
  }
130
181
  function resolveProgressThresholdMs() {
131
182
  const raw = process.env.DEFT_COMPLETED_TRACKED_PROGRESS_MS;
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string;
4
+ quiet: boolean;
5
+ error?: string;
6
+ }
7
+ /** Parse verify-consumer-header-placeholder CLI args. */
8
+ export declare function parseArgs(argv: string[]): ParsedArgs;
9
+ /** Run the gate and return the process exit code. */
10
+ export declare function run(argv: string[]): number;
11
+ export {};
12
+ //# sourceMappingURL=verify-consumer-header-placeholder.d.ts.map