@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.
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +87 -51
- package/dist/doctor.js +6 -0
- package/dist/occupancy-grant.d.ts +18 -0
- package/dist/occupancy-grant.js +120 -0
- package/dist/occupancy-heartbeat.d.ts +8 -0
- package/dist/occupancy-heartbeat.js +62 -0
- package/dist/occupancy-release.d.ts +1 -0
- package/dist/occupancy-release.js +26 -2
- package/dist/occupancy-steal.d.ts +1 -0
- package/dist/occupancy-steal.js +32 -4
- package/dist/render-cli/prd-render-cli.js +6 -10
- package/dist/session-ready.d.ts +2 -0
- package/dist/session-ready.js +37 -5
- package/dist/session-start.d.ts +2 -0
- package/dist/session-start.js +39 -6
- package/dist/toolchain-check.d.ts +1 -0
- package/dist/toolchain-check.js +41 -2
- package/dist/verify-orphan-active.d.ts +2 -0
- package/dist/verify-orphan-active.js +30 -0
- package/dist/verify-pr-closeout-attestable.d.ts +14 -0
- package/dist/verify-pr-closeout-attestable.js +96 -0
- package/dist/verify-source-cli/verify-closing-keywords.d.ts +35 -0
- package/dist/verify-source-cli/verify-closing-keywords.js +139 -0
- package/dist/verify-source-cli/verify-consumer-hard-stops.d.ts +16 -0
- package/dist/verify-source-cli/verify-consumer-hard-stops.js +99 -0
- package/dist/verify-source-cli/verify-deposit-closure.d.ts +12 -0
- package/dist/verify-source-cli/verify-deposit-closure.js +52 -0
- package/dist/verify-source-cli/verify-semantic-single-source.d.ts +11 -0
- package/dist/verify-source-cli/verify-semantic-single-source.js +49 -0
- package/dist/verify-subagent-alive.js +5 -1
- 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
|
|
5
|
-
* resolver (#2132
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
: projectRoot
|
|
36
|
-
|
|
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
|
package/dist/session-ready.d.ts
CHANGED
|
@@ -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. */
|
package/dist/session-ready.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
package/dist/session-start.d.ts
CHANGED
|
@@ -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. */
|
package/dist/session-start.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/toolchain-check.js
CHANGED
|
@@ -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
|
|
6
|
-
|
|
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
|
}
|
|
@@ -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
|