@deftai/directive 0.107.0 → 0.109.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.
Files changed (32) hide show
  1. package/dist/dispatch.d.ts +1 -1
  2. package/dist/dispatch.js +87 -51
  3. package/dist/doctor.js +6 -0
  4. package/dist/occupancy-grant.d.ts +18 -0
  5. package/dist/occupancy-grant.js +120 -0
  6. package/dist/occupancy-heartbeat.d.ts +8 -0
  7. package/dist/occupancy-heartbeat.js +62 -0
  8. package/dist/occupancy-release.d.ts +1 -0
  9. package/dist/occupancy-release.js +26 -2
  10. package/dist/occupancy-steal.d.ts +1 -0
  11. package/dist/occupancy-steal.js +32 -4
  12. package/dist/render-cli/prd-render-cli.js +6 -10
  13. package/dist/session-ready.d.ts +2 -0
  14. package/dist/session-ready.js +37 -5
  15. package/dist/session-start.d.ts +2 -0
  16. package/dist/session-start.js +39 -6
  17. package/dist/toolchain-check.d.ts +1 -0
  18. package/dist/toolchain-check.js +41 -2
  19. package/dist/verify-orphan-active.d.ts +2 -0
  20. package/dist/verify-orphan-active.js +30 -0
  21. package/dist/verify-pr-closeout-attestable.d.ts +14 -0
  22. package/dist/verify-pr-closeout-attestable.js +96 -0
  23. package/dist/verify-source-cli/verify-closing-keywords.d.ts +35 -0
  24. package/dist/verify-source-cli/verify-closing-keywords.js +139 -0
  25. package/dist/verify-source-cli/verify-consumer-hard-stops.d.ts +16 -0
  26. package/dist/verify-source-cli/verify-consumer-hard-stops.js +99 -0
  27. package/dist/verify-source-cli/verify-deposit-closure.d.ts +12 -0
  28. package/dist/verify-source-cli/verify-deposit-closure.js +52 -0
  29. package/dist/verify-source-cli/verify-semantic-single-source.d.ts +11 -0
  30. package/dist/verify-source-cli/verify-semantic-single-source.js +49 -0
  31. package/dist/verify-subagent-alive.js +5 -1
  32. package/package.json +3 -3
@@ -1,13 +1,11 @@
1
1
  /**
2
2
  * Thin CLI wrapper for prd-render (mirrors ``scripts/prd_render.py``).
3
3
  *
4
- * Supports `--project-root <dir>` to resolve the spec artifact via the layout
5
- * resolver (#2132), preferring `xbrief/specification.xbrief.json` on migrated
6
- * trees and falling back to `vbrief/specification.vbrief.json` on legacy trees.
4
+ * Supports `--project-root <dir>` to resolve full-spec or greenfield authority
5
+ * through the shared authority resolver (#2132 / #3598).
7
6
  * When `--spec` is explicitly provided it takes precedence over `--project-root`.
8
7
  * Direct `--spec` / `--output` / `--force` flags are still accepted unchanged.
9
8
  */
10
- import { layout } from "@deftai/directive-core";
11
9
  import { parsePrdArgv, prdRenderMain } from "@deftai/directive-core/render";
12
10
  function parsePrdRenderCliArgv(argv) {
13
11
  const remaining = [];
@@ -30,12 +28,10 @@ function parsePrdRenderCliArgv(argv) {
30
28
  export function runPrdRenderCli(argv) {
31
29
  const { projectRoot, remaining } = parsePrdRenderCliArgv(argv);
32
30
  const parsedArgs = parsePrdArgv(remaining);
33
- const spec = parsedArgs.spec !== undefined
34
- ? parsedArgs.spec
35
- : projectRoot !== undefined
36
- ? layout.resolveSpecArtifactPath(projectRoot)
37
- : undefined;
38
- prdRenderMain({ ...parsedArgs, spec });
31
+ prdRenderMain({
32
+ ...parsedArgs,
33
+ projectRoot: parsedArgs.spec === undefined ? projectRoot : undefined,
34
+ });
39
35
  return 0;
40
36
  }
41
37
  //# sourceMappingURL=prd-render-cli.js.map
@@ -4,6 +4,8 @@ export interface ParsedSessionReadyArgs {
4
4
  emitJson: boolean;
5
5
  withNetwork: boolean;
6
6
  repo: string | null;
7
+ /** #3611: explicit lifecycle owner injected by a host hook bridge. */
8
+ sessionId: string | null;
7
9
  error?: string;
8
10
  }
9
11
  /** Parse session:ready CLI args. */
@@ -15,6 +15,7 @@ export function parseArgs(argv) {
15
15
  emitJson: false,
16
16
  withNetwork: false,
17
17
  repo: null,
18
+ sessionId: null,
18
19
  };
19
20
  for (let i = 0; i < argv.length; i += 1) {
20
21
  const arg = argv[i];
@@ -33,13 +34,13 @@ export function parseArgs(argv) {
33
34
  if (arg === "--help" || arg === "-h") {
34
35
  return {
35
36
  ...parsed,
36
- error: "usage: session:ready [--project-root <path>] [--repo OWNER/NAME] [--with-network] [--json]\n" +
37
+ error: "usage: session:ready [--project-root <path>] [--repo OWNER/NAME] [--session-id <id>] [--with-network] [--json]\n" +
37
38
  " One-shot recovery: session:start (if needed) + gated ritual + cache recovery (#2993).",
38
39
  };
39
40
  }
40
41
  if (arg === "--project-root") {
41
42
  const value = argv[i + 1];
42
- if (value === undefined) {
43
+ if (value === undefined || value.startsWith("--")) {
43
44
  return { ...parsed, error: "argument --project-root: expected one argument" };
44
45
  }
45
46
  parsed.projectRoot = value;
@@ -47,12 +48,37 @@ export function parseArgs(argv) {
47
48
  continue;
48
49
  }
49
50
  if (arg.startsWith("--project-root=")) {
50
- parsed.projectRoot = arg.slice("--project-root=".length);
51
+ const value = arg.slice("--project-root=".length);
52
+ if (value.length === 0 || value.startsWith("--")) {
53
+ return { ...parsed, error: "argument --project-root: expected one argument" };
54
+ }
55
+ parsed.projectRoot = value;
56
+ continue;
57
+ }
58
+ if (arg === "--session-id") {
59
+ const value = argv[i + 1];
60
+ if (value === undefined || value.startsWith("--")) {
61
+ return { ...parsed, error: "argument --session-id: expected one argument" };
62
+ }
63
+ const sessionId = value.trim();
64
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
65
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
66
+ }
67
+ parsed.sessionId = sessionId;
68
+ i += 1;
69
+ continue;
70
+ }
71
+ if (arg.startsWith("--session-id=")) {
72
+ const sessionId = arg.slice("--session-id=".length).trim();
73
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
74
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
75
+ }
76
+ parsed.sessionId = sessionId;
51
77
  continue;
52
78
  }
53
79
  if (arg === "--repo") {
54
80
  const value = argv[i + 1];
55
- if (value === undefined) {
81
+ if (value === undefined || value.startsWith("--")) {
56
82
  return { ...parsed, error: "argument --repo: expected one argument" };
57
83
  }
58
84
  parsed.repo = value;
@@ -60,7 +86,11 @@ export function parseArgs(argv) {
60
86
  continue;
61
87
  }
62
88
  if (arg.startsWith("--repo=")) {
63
- parsed.repo = arg.slice("--repo=".length);
89
+ const value = arg.slice("--repo=".length);
90
+ if (value.length === 0 || value.startsWith("--")) {
91
+ return { ...parsed, error: "argument --repo: expected one argument" };
92
+ }
93
+ parsed.repo = value;
64
94
  continue;
65
95
  }
66
96
  if (arg.startsWith("-")) {
@@ -80,6 +110,7 @@ export function run(argv = process.argv.slice(2)) {
80
110
  const projectRoot = resolve(args.projectRoot);
81
111
  const result = runSessionReady(projectRoot, {
82
112
  repo: args.repo,
113
+ sessionId: args.sessionId ?? undefined,
83
114
  sessionStartOptions: {
84
115
  allowOptionalNetwork: args.withNetwork ? true : undefined,
85
116
  writeHistory: false,
@@ -89,6 +120,7 @@ export function run(argv = process.argv.slice(2)) {
89
120
  process.stdout.write(`${JSON.stringify({
90
121
  ready: result.code === 0,
91
122
  exit_code: result.code,
123
+ session_id: result.sessionId,
92
124
  path: result.path,
93
125
  message: result.message,
94
126
  steps: result.steps,
@@ -36,6 +36,8 @@ export interface ParsedSessionStartArgs {
36
36
  steal: boolean;
37
37
  confirm: boolean;
38
38
  occupant: string | null;
39
+ /** #3611: explicit lifecycle owner injected by a host hook bridge. */
40
+ sessionId: string | null;
39
41
  error?: string;
40
42
  }
41
43
  /** Parse session:start CLI args, mirroring scripts/session_start.py. */
@@ -34,6 +34,7 @@ export function parseArgs(argv) {
34
34
  steal: false,
35
35
  confirm: false,
36
36
  occupant: null,
37
+ sessionId: null,
37
38
  };
38
39
  const dialInputs = {};
39
40
  for (let i = 0; i < argv.length; i += 1) {
@@ -82,25 +83,33 @@ export function parseArgs(argv) {
82
83
  }
83
84
  else if (arg === "--project-root") {
84
85
  const value = argv[i + 1];
85
- if (value === undefined) {
86
+ if (value === undefined || value.startsWith("--")) {
86
87
  return { ...parsed, error: "argument --project-root: expected one argument" };
87
88
  }
88
89
  parsed.projectRoot = value;
89
90
  i += 1;
90
91
  }
91
92
  else if (arg?.startsWith("--project-root=")) {
92
- parsed.projectRoot = arg.slice("--project-root=".length);
93
+ const value = arg.slice("--project-root=".length);
94
+ if (value.length === 0 || value.startsWith("--")) {
95
+ return { ...parsed, error: "argument --project-root: expected one argument" };
96
+ }
97
+ parsed.projectRoot = value;
93
98
  }
94
99
  else if (arg === "--defer") {
95
100
  const value = argv[i + 1];
96
- if (value === undefined) {
101
+ if (value === undefined || value.startsWith("--")) {
97
102
  return { ...parsed, error: "argument --defer: expected one argument" };
98
103
  }
99
104
  parsed.deferValues.push(value);
100
105
  i += 1;
101
106
  }
102
107
  else if (arg?.startsWith("--defer=")) {
103
- parsed.deferValues.push(arg.slice("--defer=".length));
108
+ const value = arg.slice("--defer=".length);
109
+ if (value.length === 0 || value.startsWith("--")) {
110
+ return { ...parsed, error: "argument --defer: expected one argument" };
111
+ }
112
+ parsed.deferValues.push(value);
104
113
  }
105
114
  else if (arg === "--task-size" || arg === "--ceremony-task-size") {
106
115
  const value = argv[i + 1];
@@ -282,16 +291,39 @@ export function parseArgs(argv) {
282
291
  else if (arg === "--confirm") {
283
292
  parsed.confirm = true;
284
293
  }
294
+ else if (arg === "--session-id") {
295
+ const value = argv[i + 1];
296
+ if (value === undefined || value.startsWith("--")) {
297
+ return { ...parsed, error: "argument --session-id: expected one argument" };
298
+ }
299
+ const sessionId = value.trim();
300
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
301
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
302
+ }
303
+ parsed.sessionId = sessionId;
304
+ i += 1;
305
+ }
306
+ else if (arg?.startsWith("--session-id=")) {
307
+ const sessionId = arg.slice("--session-id=".length).trim();
308
+ if (sessionId.length === 0 || sessionId.startsWith("--")) {
309
+ return { ...parsed, error: "argument --session-id: expected a non-empty value" };
310
+ }
311
+ parsed.sessionId = sessionId;
312
+ }
285
313
  else if (arg === "--occupant") {
286
314
  const value = argv[i + 1];
287
- if (value === undefined) {
315
+ if (value === undefined || value.startsWith("--")) {
288
316
  return { ...parsed, error: "argument --occupant: expected one argument" };
289
317
  }
290
318
  parsed.occupant = value;
291
319
  i += 1;
292
320
  }
293
321
  else if (arg?.startsWith("--occupant=")) {
294
- parsed.occupant = arg.slice("--occupant=".length);
322
+ const value = arg.slice("--occupant=".length);
323
+ if (value.length === 0 || value.startsWith("--")) {
324
+ return { ...parsed, error: "argument --occupant: expected one argument" };
325
+ }
326
+ parsed.occupant = value;
295
327
  }
296
328
  else {
297
329
  return { ...parsed, error: `unrecognized argument: ${arg}` };
@@ -358,6 +390,7 @@ export function run(argv) {
358
390
  steal: args.steal ? true : undefined,
359
391
  confirm: args.confirm ? true : undefined,
360
392
  occupant: args.occupant ?? undefined,
393
+ sessionId: args.sessionId ?? undefined,
361
394
  ...(args.ceremonyDepthOverride !== null
362
395
  ? {
363
396
  ceremonyDial: selectCeremonyDepth({
@@ -2,6 +2,7 @@
2
2
  import { type CommandRunner } from "@deftai/directive-core/verify-env";
3
3
  export interface ToolchainCheckRunOptions {
4
4
  readonly runner?: CommandRunner;
5
+ readonly env?: NodeJS.ProcessEnv;
5
6
  }
6
7
  export declare function run(argv?: readonly string[], options?: ToolchainCheckRunOptions): number;
7
8
  //# sourceMappingURL=toolchain-check.d.ts.map
@@ -1,9 +1,48 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { runToolchainCheck } from "@deftai/directive-core/verify-env";
4
+ function parseArgs(argv) {
5
+ let consumer = false;
6
+ let projectRoot = process.cwd();
7
+ for (let index = 0; index < argv.length; index += 1) {
8
+ const arg = argv[index] ?? "";
9
+ if (arg === "--consumer") {
10
+ consumer = true;
11
+ continue;
12
+ }
13
+ if (arg === "--project-root") {
14
+ const value = argv[index + 1];
15
+ if (value === undefined || value.trim() === "" || value.startsWith("-")) {
16
+ return { consumer, projectRoot, error: "--project-root requires a path" };
17
+ }
18
+ projectRoot = value;
19
+ index += 1;
20
+ continue;
21
+ }
22
+ if (arg.startsWith("--project-root=")) {
23
+ const value = arg.slice("--project-root=".length);
24
+ if (value.trim() === "") {
25
+ return { consumer, projectRoot, error: "--project-root requires a path" };
26
+ }
27
+ projectRoot = value;
28
+ continue;
29
+ }
30
+ return { consumer, projectRoot, error: `unrecognized argument: ${arg}` };
31
+ }
32
+ return { consumer, projectRoot };
33
+ }
4
34
  export function run(argv = process.argv.slice(2), options = {}) {
5
- const consumer = argv.includes("--consumer");
6
- const result = runToolchainCheck(options.runner, { consumer });
35
+ const parsed = parseArgs(argv);
36
+ if (parsed.error !== undefined) {
37
+ process.stderr.write(`toolchain-check: ${parsed.error}\n`);
38
+ process.stderr.write("Usage: deft toolchain-check [--consumer] [--project-root <path>]\n");
39
+ return 2;
40
+ }
41
+ const result = runToolchainCheck(options.runner, {
42
+ consumer: parsed.consumer,
43
+ projectRoot: parsed.projectRoot,
44
+ env: options.env ?? process.env,
45
+ });
7
46
  for (const line of result.lines) {
8
47
  process.stdout.write(`${line}\n`);
9
48
  }
@@ -5,6 +5,8 @@ interface ParsedArgs {
5
5
  issue: number | null;
6
6
  quiet: boolean;
7
7
  skipGh: boolean;
8
+ changedOnly: boolean;
9
+ baseRef: string | null;
8
10
  error?: string;
9
11
  }
10
12
  /** Parse verify-orphan-active CLI args. */
@@ -18,6 +18,8 @@ export function parseArgs(argv) {
18
18
  issue: null,
19
19
  quiet: false,
20
20
  skipGh: false,
21
+ changedOnly: false,
22
+ baseRef: null,
21
23
  };
22
24
  for (let i = 0; i < argv.length; i += 1) {
23
25
  const arg = argv[i];
@@ -27,6 +29,20 @@ export function parseArgs(argv) {
27
29
  else if (arg === "--skip-gh") {
28
30
  parsed.skipGh = true;
29
31
  }
32
+ else if (arg === "--changed-only") {
33
+ parsed.changedOnly = true;
34
+ }
35
+ else if (arg === "--base-ref") {
36
+ const value = argv[i + 1];
37
+ if (value === undefined) {
38
+ return { ...parsed, error: "argument --base-ref: expected one argument" };
39
+ }
40
+ parsed.baseRef = value;
41
+ i += 1;
42
+ }
43
+ else if (arg?.startsWith("--base-ref=")) {
44
+ parsed.baseRef = arg.slice("--base-ref=".length);
45
+ }
30
46
  else if (arg === "--project-root") {
31
47
  const value = argv[i + 1];
32
48
  if (value === undefined) {
@@ -73,6 +89,18 @@ export function parseArgs(argv) {
73
89
  return { ...parsed, error: `unrecognized argument: ${arg}` };
74
90
  }
75
91
  }
92
+ // The two scopes answer different questions; silently preferring one would
93
+ // hide which residue the run actually covered (#3893).
94
+ if (parsed.changedOnly && parsed.issue !== null) {
95
+ return {
96
+ ...parsed,
97
+ error: "--changed-only and --issue are mutually exclusive: --issue is the after-merge " +
98
+ "one-origin scan, --changed-only is the merge-gate candidate scan",
99
+ };
100
+ }
101
+ if (parsed.baseRef !== null && !parsed.changedOnly) {
102
+ return { ...parsed, error: "--base-ref requires --changed-only" };
103
+ }
76
104
  return parsed;
77
105
  }
78
106
  /** Run the gate and return the process exit code. */
@@ -88,6 +116,8 @@ export function run(argv) {
88
116
  repo: args.repo,
89
117
  skipGh: args.skipGh,
90
118
  issue: args.issue,
119
+ changedOnly: args.changedOnly,
120
+ baseRef: args.baseRef,
91
121
  });
92
122
  if (result.message.length > 0) {
93
123
  if (result.stream === "stdout") {
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string;
4
+ repo: string | null;
5
+ pr: number | null;
6
+ quiet: boolean;
7
+ error?: string;
8
+ }
9
+ /** Parse verify-pr-closeout-attestable CLI args. */
10
+ export declare function parseArgs(argv: string[]): ParsedArgs;
11
+ /** Run the gate and return the process exit code. */
12
+ export declare function run(argv: string[]): number;
13
+ export {};
14
+ //# sourceMappingURL=verify-pr-closeout-attestable.d.ts.map
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { evaluate } from "@deftai/directive-core/pr-closeout-attestable";
5
+ function parsePrNumber(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-pr-closeout-attestable CLI args. */
14
+ export function parseArgs(argv) {
15
+ const parsed = { projectRoot: ".", repo: null, pr: null, quiet: false };
16
+ for (let i = 0; i < argv.length; i += 1) {
17
+ const arg = argv[i];
18
+ if (arg === "--quiet") {
19
+ parsed.quiet = true;
20
+ }
21
+ else if (arg === "--project-root") {
22
+ const value = argv[i + 1];
23
+ if (value === undefined) {
24
+ return { ...parsed, error: "argument --project-root: expected one argument" };
25
+ }
26
+ parsed.projectRoot = value;
27
+ i += 1;
28
+ }
29
+ else if (arg?.startsWith("--project-root=")) {
30
+ parsed.projectRoot = arg.slice("--project-root=".length);
31
+ }
32
+ else if (arg === "--repo") {
33
+ const value = argv[i + 1];
34
+ if (value === undefined) {
35
+ return { ...parsed, error: "argument --repo: expected one argument" };
36
+ }
37
+ parsed.repo = value;
38
+ i += 1;
39
+ }
40
+ else if (arg?.startsWith("--repo=")) {
41
+ parsed.repo = arg.slice("--repo=".length);
42
+ }
43
+ else if (arg === "--pr") {
44
+ const value = argv[i + 1];
45
+ if (value === undefined) {
46
+ return { ...parsed, error: "argument --pr: expected one argument" };
47
+ }
48
+ const pr = parsePrNumber(value);
49
+ if (pr === null) {
50
+ return { ...parsed, error: `argument --pr: expected a positive integer, got ${value}` };
51
+ }
52
+ parsed.pr = pr;
53
+ i += 1;
54
+ }
55
+ else if (arg?.startsWith("--pr=")) {
56
+ const value = arg.slice("--pr=".length);
57
+ const pr = parsePrNumber(value);
58
+ if (pr === null) {
59
+ return { ...parsed, error: `argument --pr: expected a positive integer, got ${value}` };
60
+ }
61
+ parsed.pr = pr;
62
+ }
63
+ else {
64
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
65
+ }
66
+ }
67
+ if (parsed.pr === null) {
68
+ return { ...parsed, error: "argument --pr is required" };
69
+ }
70
+ return parsed;
71
+ }
72
+ /** Run the gate and return the process exit code. */
73
+ export function run(argv) {
74
+ const args = parseArgs(argv);
75
+ if (args.error !== undefined) {
76
+ process.stderr.write(`verify_pr_closeout_attestable: ${args.error}\n`);
77
+ return 2;
78
+ }
79
+ const result = evaluate(resolve(args.projectRoot), args.pr, {
80
+ repo: args.repo,
81
+ quiet: args.quiet,
82
+ });
83
+ if (result.message.length > 0) {
84
+ if (result.stream === "stdout") {
85
+ process.stdout.write(`${result.message}\n`);
86
+ }
87
+ else if (result.stream === "stderr") {
88
+ process.stderr.write(`${result.message}\n`);
89
+ }
90
+ }
91
+ return result.code;
92
+ }
93
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
94
+ process.exit(run(process.argv.slice(2)));
95
+ }
96
+ //# sourceMappingURL=verify-pr-closeout-attestable.js.map
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ export type RunGitFn = (args: readonly string[]) => {
3
+ readonly returncode: number;
4
+ readonly stdout: string;
5
+ readonly stderr: string;
6
+ };
7
+ export type RangeResolution = {
8
+ readonly kind: "pr";
9
+ readonly pr: string;
10
+ } | {
11
+ readonly kind: "range";
12
+ readonly range: string;
13
+ } | {
14
+ readonly kind: "missing-base";
15
+ readonly reason: string;
16
+ } | {
17
+ readonly kind: "stale-base";
18
+ readonly reason: string;
19
+ };
20
+ export declare function defaultRunGit(args: readonly string[]): {
21
+ returncode: number;
22
+ stdout: string;
23
+ stderr: string;
24
+ };
25
+ /**
26
+ * Resolve the candidate commit range against a merge-base, not a hardcoded
27
+ * origin/master two-dot range (#3969 Greptile leftover).
28
+ */
29
+ export declare function resolveClosingKeywordsSource(env: NodeJS.ProcessEnv, runGit?: RunGitFn): RangeResolution;
30
+ export declare function buildClosingKeywordsCheckArgv(env: NodeJS.ProcessEnv, extra?: readonly string[], runGit?: RunGitFn): {
31
+ argv: string[];
32
+ error?: string;
33
+ };
34
+ export declare function run(argv: string[], env?: NodeJS.ProcessEnv, invoke?: (args: readonly string[]) => number, runGit?: RunGitFn): number;
35
+ //# sourceMappingURL=verify-closing-keywords.d.ts.map
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { cmdPrCheckClosingKeywords } from "@deftai/directive-core/dist/pr-closing-keywords/main.js";
5
+ const BASE_CANDIDATES = ["origin/master", "origin/main"];
6
+ export function defaultRunGit(args) {
7
+ try {
8
+ const stdout = execFileSync("git", [...args], {
9
+ encoding: "utf8",
10
+ timeout: 30_000,
11
+ stdio: ["ignore", "pipe", "pipe"],
12
+ });
13
+ return { returncode: 0, stdout: typeof stdout === "string" ? stdout : "", stderr: "" };
14
+ }
15
+ catch (err) {
16
+ const e = err;
17
+ return {
18
+ returncode: typeof e.status === "number" ? e.status : 1,
19
+ stdout: typeof e.stdout === "string" ? e.stdout : "",
20
+ stderr: typeof e.stderr === "string" ? e.stderr : String(e.message ?? ""),
21
+ };
22
+ }
23
+ }
24
+ function firstLine(text) {
25
+ return text.trim().split(/\r?\n/)[0] ?? "";
26
+ }
27
+ function shaOf(result) {
28
+ if (result.returncode !== 0)
29
+ return null;
30
+ const sha = firstLine(result.stdout);
31
+ return /^[0-9a-f]{7,40}$/i.test(sha) ? sha : null;
32
+ }
33
+ function localDefaultFor(originRef) {
34
+ if (originRef.endsWith("/master") || originRef === "master")
35
+ return "master";
36
+ if (originRef.endsWith("/main") || originRef === "main")
37
+ return "main";
38
+ const slash = originRef.lastIndexOf("/");
39
+ return slash >= 0 ? originRef.slice(slash + 1) : null;
40
+ }
41
+ /** Remote-tracking ref name for `git fetch origin <name> --`. */
42
+ function originFetchArg(originRef) {
43
+ if (!originRef.startsWith("origin/"))
44
+ return null;
45
+ const name = originRef.slice("origin/".length);
46
+ return name.length > 0 ? name : null;
47
+ }
48
+ /**
49
+ * Detect the locally observable stale-range enlargement without fetching:
50
+ * origin/<default> lags a descendant local default, and HEAD's merge-base
51
+ * against that local default is not the origin merge-base. Then
52
+ * origin/master..HEAD would include already-merged local-default commits.
53
+ */
54
+ function staleRelativeToLocalDefault(originRef, originMergeBase, runGit) {
55
+ const localDefault = localDefaultFor(originRef);
56
+ if (localDefault === null)
57
+ return null;
58
+ const ahead = runGit(["merge-base", "--is-ancestor", originRef, localDefault]);
59
+ if (ahead.returncode !== 0)
60
+ return null;
61
+ const localMb = shaOf(runGit(["merge-base", localDefault, "HEAD"]));
62
+ if (localMb === null || localMb === originMergeBase)
63
+ return null;
64
+ return {
65
+ kind: "stale-base",
66
+ reason: originRef +
67
+ " lags local " +
68
+ localDefault +
69
+ " relative to HEAD, so the scan range would include already-merged commits. Recovery: git fetch origin " +
70
+ localDefault +
71
+ ".",
72
+ };
73
+ }
74
+ /**
75
+ * Resolve the candidate commit range against a merge-base, not a hardcoded
76
+ * origin/master two-dot range (#3969 Greptile leftover).
77
+ */
78
+ export function resolveClosingKeywordsSource(env, runGit = defaultRunGit) {
79
+ const raw = env.GITHUB_PR_NUMBER ?? env.PR_NUMBER;
80
+ const pr = raw?.trim() ?? "";
81
+ if (/^\d+$/.test(pr)) {
82
+ return { kind: "pr", pr };
83
+ }
84
+ const bases = [
85
+ env.GITHUB_BASE_REF?.trim() ? `origin/${env.GITHUB_BASE_REF.trim()}` : null,
86
+ ...BASE_CANDIDATES,
87
+ ].filter((base) => base !== null && base.length > 0);
88
+ const seen = new Set();
89
+ for (const base of bases) {
90
+ if (seen.has(base))
91
+ continue;
92
+ seen.add(base);
93
+ const fetchArg = originFetchArg(base);
94
+ let fetched = false;
95
+ if (fetchArg !== null) {
96
+ // Scoped to this wrapper, not the whole check graph. CI uses --pr
97
+ // and never reaches this path.
98
+ fetched = runGit(["fetch", "origin", fetchArg, "--"]).returncode === 0;
99
+ }
100
+ const sha = shaOf(runGit(["merge-base", base, "HEAD"]));
101
+ if (sha === null)
102
+ continue;
103
+ if (!fetched) {
104
+ const stale = staleRelativeToLocalDefault(base, sha, runGit);
105
+ if (stale !== null)
106
+ return stale;
107
+ // Fetch failed and local evidence cannot prove freshness: do not
108
+ // scan a possibly stale origin merge-base.
109
+ continue;
110
+ }
111
+ return { kind: "range", range: `${sha}..HEAD` };
112
+ }
113
+ return {
114
+ kind: "missing-base",
115
+ reason: "no merge-base against origin/master or origin/main. Recovery: git fetch origin master.",
116
+ };
117
+ }
118
+ export function buildClosingKeywordsCheckArgv(env, extra = [], runGit = defaultRunGit) {
119
+ const source = resolveClosingKeywordsSource(env, runGit);
120
+ if (source.kind === "pr") {
121
+ return { argv: ["--mode", "fp", "--pr", source.pr, ...extra] };
122
+ }
123
+ if (source.kind === "missing-base" || source.kind === "stale-base") {
124
+ return { argv: [], error: source.reason };
125
+ }
126
+ return { argv: ["--mode", "fp", "--from-git-range", source.range, ...extra] };
127
+ }
128
+ export function run(argv, env = process.env, invoke = cmdPrCheckClosingKeywords, runGit = defaultRunGit) {
129
+ const built = buildClosingKeywordsCheckArgv(env, argv, runGit);
130
+ if (built.error !== undefined) {
131
+ process.stderr.write("verify:closing-keywords: fail -- " + built.error + "\n");
132
+ return 2;
133
+ }
134
+ return invoke(built.argv);
135
+ }
136
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
137
+ process.exit(run(process.argv.slice(2)));
138
+ }
139
+ //# sourceMappingURL=verify-closing-keywords.js.map
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ repo: string;
4
+ projectRoot: string;
5
+ changelog: string | null;
6
+ error?: string;
7
+ }
8
+ export interface HardStopCliSeams {
9
+ readonly inventory?: readonly Record<string, unknown>[];
10
+ readonly changelogText?: string;
11
+ }
12
+ /** Parse verify-consumer-hard-stops CLI args (#3900 / #3713). */
13
+ export declare function parseArgs(argv: string[]): ParsedArgs;
14
+ export declare function run(argv: string[], seams?: HardStopCliSeams): number;
15
+ export {};
16
+ //# sourceMappingURL=verify-consumer-hard-stops.d.ts.map