@deftai/directive-core 0.61.2 → 0.63.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/check-updates/index.d.ts +49 -0
- package/dist/check-updates/index.js +323 -0
- package/dist/install-upgrade/index.js +3 -3
- package/dist/migrate-preflight/index.d.ts +1 -2
- package/dist/migrate-preflight/index.js +16 -36
- package/dist/release/build-dist-runner.d.ts +2 -0
- package/dist/release/build-dist-runner.js +21 -0
- package/dist/release/build-dist.d.ts +14 -0
- package/dist/release/build-dist.js +171 -0
- package/dist/release/constants.d.ts +1 -1
- package/dist/release/constants.js +1 -6
- package/dist/release/index.d.ts +2 -3
- package/dist/release/index.js +2 -3
- package/dist/release/native-steps.d.ts +5 -0
- package/dist/release/native-steps.js +72 -0
- package/dist/release/pipeline.js +8 -27
- package/dist/release-e2e/rollback-bridge.d.ts +1 -1
- package/dist/release-e2e/rollback-bridge.js +3 -17
- package/dist/render/framework-commands.d.ts +2 -2
- package/dist/render/framework-commands.js +102 -84
- package/dist/umbrella-current-shape/index.d.ts +87 -0
- package/dist/umbrella-current-shape/index.js +329 -0
- package/dist/vbrief-validate/precutover.d.ts +3 -0
- package/dist/vbrief-validate/precutover.js +8 -1
- package/dist/verify-env/verify-hooks-installed.d.ts +2 -0
- package/dist/verify-env/verify-hooks-installed.js +20 -4
- package/package.json +15 -3
- package/dist/release/pyproject-sync.d.ts +0 -6
- package/dist/release/pyproject-sync.js +0 -52
- package/dist/release/pyproject.d.ts +0 -3
- package/dist/release/pyproject.js +0 -33
- package/dist/release/python-steps.d.ts +0 -6
- package/dist/release/python-steps.js +0 -143
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { cmdDoctor } from "../doctor/main.js";
|
|
5
6
|
function spec(name, entrypoint, opts = {}) {
|
|
6
7
|
return { name, entrypoint, ...opts };
|
|
@@ -12,8 +13,6 @@ export const COMMANDS = {
|
|
|
12
13
|
"core:validate": spec("core:validate", "framework_commands:_cmd_core_validate", {
|
|
13
14
|
cwd: "framework",
|
|
14
15
|
}),
|
|
15
|
-
"core:lint": spec("core:lint", "framework_commands:_cmd_core_lint", { cwd: "framework" }),
|
|
16
|
-
"core:test": spec("core:test", "framework_commands:_cmd_core_test", { cwd: "framework" }),
|
|
17
16
|
doctor: spec("doctor", "doctor:cmd_doctor"),
|
|
18
17
|
"session:start": spec("session:start", "session_start:main", {
|
|
19
18
|
projectRootArg: "--project-root",
|
|
@@ -42,7 +41,6 @@ export const COMMANDS = {
|
|
|
42
41
|
"triage:accept": spec("triage:accept", "triage_actions:main", { defaultArgs: ["accept"] }),
|
|
43
42
|
"triage:status": spec("triage:status", "triage_actions:main", { defaultArgs: ["status"] }),
|
|
44
43
|
"triage:scope": spec("triage:scope", "triage_scope:main"),
|
|
45
|
-
"migrate:vbrief": spec("migrate:vbrief", "framework_commands:_cmd_migrate_vbrief"),
|
|
46
44
|
"cache:fetch-all": spec("cache:fetch-all", "cache:main", { defaultArgs: ["fetch-all"] }),
|
|
47
45
|
"capacity:show": spec("capacity:show", "capacity_show:main", {
|
|
48
46
|
projectRootArg: "--project-root",
|
|
@@ -94,7 +92,7 @@ export const COMMANDS = {
|
|
|
94
92
|
vbriefDirArg: "--vbrief-dir",
|
|
95
93
|
}),
|
|
96
94
|
build: spec("build", "build_dist:main", {
|
|
97
|
-
defaultArgs: ["--version", "__DEFT_VERSION__"],
|
|
95
|
+
defaultArgs: ["--version", "__DEFT_VERSION__", "--root", "__DEFT_ROOT__"],
|
|
98
96
|
cwd: "framework",
|
|
99
97
|
}),
|
|
100
98
|
"check:consumer": aggregate("check:consumer", [
|
|
@@ -108,8 +106,6 @@ export const COMMANDS = {
|
|
|
108
106
|
]),
|
|
109
107
|
"check:framework-source": aggregate("check:framework-source", [
|
|
110
108
|
"core:validate",
|
|
111
|
-
"core:lint",
|
|
112
|
-
"core:test",
|
|
113
109
|
"toolchain:check",
|
|
114
110
|
"verify:stubs",
|
|
115
111
|
"verify:links",
|
|
@@ -198,49 +194,13 @@ export function cmdCoreValidate(argv) {
|
|
|
198
194
|
process.stdout.write(`✓ All ${files.length} markdown files validated\n`);
|
|
199
195
|
return 0;
|
|
200
196
|
}
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
encoding: "utf8",
|
|
205
|
-
stdio: "inherit",
|
|
206
|
-
});
|
|
207
|
-
return 0;
|
|
208
|
-
}
|
|
209
|
-
catch (err) {
|
|
210
|
-
const e = err;
|
|
211
|
-
return e.status ?? 1;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
export function cmdCoreLint(argv) {
|
|
215
|
-
if (argv.length > 0) {
|
|
216
|
-
process.stderr.write(`error: core:lint does not accept arguments: ${argv.join(" ")}\n`);
|
|
217
|
-
return 2;
|
|
218
|
-
}
|
|
219
|
-
const ruffCode = runUv(["ruff", "check", "."], resolveFrameworkRoot());
|
|
220
|
-
if (ruffCode !== 0)
|
|
221
|
-
return ruffCode;
|
|
222
|
-
const targets = ["run.py"];
|
|
223
|
-
if (existsSync("tests"))
|
|
224
|
-
targets.push("tests");
|
|
225
|
-
return runUv(["python", "-m", "mypy", ...targets], resolveFrameworkRoot());
|
|
197
|
+
export function cmdCoreLint(_argv) {
|
|
198
|
+
process.stderr.write("error: core:lint removed (#1860); use ts:check-lane\n");
|
|
199
|
+
return 2;
|
|
226
200
|
}
|
|
227
|
-
export function cmdCoreTest(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return 2;
|
|
231
|
-
}
|
|
232
|
-
if (!existsSync("tests")) {
|
|
233
|
-
process.stdout.write("no tests/ (vendored consumer) -- skipping\n");
|
|
234
|
-
return 0;
|
|
235
|
-
}
|
|
236
|
-
try {
|
|
237
|
-
execFileSync("python3", ["-m", "pytest", "tests"], { encoding: "utf8", stdio: "inherit" });
|
|
238
|
-
return 0;
|
|
239
|
-
}
|
|
240
|
-
catch (err) {
|
|
241
|
-
const e = err;
|
|
242
|
-
return e.status ?? 1;
|
|
243
|
-
}
|
|
201
|
+
export function cmdCoreTest(_argv) {
|
|
202
|
+
process.stderr.write("error: core:test removed (#1860); use ts:check-lane\n");
|
|
203
|
+
return 2;
|
|
244
204
|
}
|
|
245
205
|
function resolveVersion() {
|
|
246
206
|
try {
|
|
@@ -260,7 +220,12 @@ export function resolveFrameworkRoot() {
|
|
|
260
220
|
function argvForSpec(commandSpec, argv, projectRoot, frameworkRoot) {
|
|
261
221
|
const resolved = [];
|
|
262
222
|
for (const item of commandSpec.defaultArgs ?? []) {
|
|
263
|
-
|
|
223
|
+
if (item === "__DEFT_VERSION__")
|
|
224
|
+
resolved.push(resolveVersion());
|
|
225
|
+
else if (item === "__DEFT_ROOT__")
|
|
226
|
+
resolved.push(frameworkRoot);
|
|
227
|
+
else
|
|
228
|
+
resolved.push(item);
|
|
264
229
|
}
|
|
265
230
|
if (commandSpec.projectRootArg)
|
|
266
231
|
resolved.push(commandSpec.projectRootArg, projectRoot);
|
|
@@ -273,44 +238,81 @@ function argvForSpec(commandSpec, argv, projectRoot, frameworkRoot) {
|
|
|
273
238
|
resolved.push(...normalizeTaskSeparator(argv));
|
|
274
239
|
return resolved;
|
|
275
240
|
}
|
|
241
|
+
const BUILD_DIST_RUNNER = join(dirname(fileURLToPath(import.meta.url)), "..", "release", "build-dist-runner.js");
|
|
242
|
+
function runBuildDistArgv(argv) {
|
|
243
|
+
let version = null;
|
|
244
|
+
let root = null;
|
|
245
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
246
|
+
const arg = argv[i];
|
|
247
|
+
if (arg === "--version")
|
|
248
|
+
version = argv[++i];
|
|
249
|
+
else if (arg === "--root")
|
|
250
|
+
root = argv[++i];
|
|
251
|
+
}
|
|
252
|
+
if (!version || !root) {
|
|
253
|
+
process.stderr.write("build: --version and --root are required\n");
|
|
254
|
+
return 2;
|
|
255
|
+
}
|
|
256
|
+
const result = spawnSync(process.execPath, [BUILD_DIST_RUNNER, version, root], {
|
|
257
|
+
cwd: root,
|
|
258
|
+
encoding: "utf8",
|
|
259
|
+
env: { ...process.env, DEFT_RELEASE_VERSION: version },
|
|
260
|
+
});
|
|
261
|
+
if (result.stdout)
|
|
262
|
+
process.stdout.write(result.stdout);
|
|
263
|
+
if (result.stderr)
|
|
264
|
+
process.stderr.write(result.stderr);
|
|
265
|
+
return result.status ?? 1;
|
|
266
|
+
}
|
|
276
267
|
const TS_INLINE = {
|
|
277
268
|
"framework_commands:_cmd_core_validate": (argv) => cmdCoreValidate(argv),
|
|
278
|
-
"framework_commands:_cmd_core_lint": (argv) => cmdCoreLint(argv),
|
|
279
|
-
"framework_commands:_cmd_core_test": (argv) => cmdCoreTest(argv),
|
|
280
269
|
"doctor:cmd_doctor": (argv) => cmdDoctor(argv),
|
|
270
|
+
"build_dist:main": (argv) => runBuildDistArgv(argv),
|
|
281
271
|
};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
272
|
+
const ENTRYPOINT_VERB = {
|
|
273
|
+
"session_start:main": "session-start",
|
|
274
|
+
"triage_welcome:main": "triage-welcome",
|
|
275
|
+
"triage_bootstrap:main": "triage-bootstrap",
|
|
276
|
+
"triage_summary:main": "triage-summary",
|
|
277
|
+
"triage_queue:main": "triage-queue",
|
|
278
|
+
"triage_actions:main": "triage-actions",
|
|
279
|
+
"triage_scope:main": "triage-scope",
|
|
280
|
+
"cache:main": "cache",
|
|
281
|
+
"capacity_show:main": "capacity-show",
|
|
282
|
+
"scope_demote:main": "scope-demote",
|
|
283
|
+
"toolchain-check.py:main": "toolchain-check",
|
|
284
|
+
"verify-stubs.py:main": "verify-stubs",
|
|
285
|
+
"validate-links.py:main": "validate-links",
|
|
286
|
+
"rule_ownership_lint:main": "rule-ownership-lint",
|
|
287
|
+
"preflight_branch:main": "verify-branch",
|
|
288
|
+
"verify_encoding:main": "verify-encoding",
|
|
289
|
+
"verify_vbrief_conformance:main": "vbrief-validate",
|
|
290
|
+
"preflight_gh:main": "preflight-gh",
|
|
291
|
+
"verify_scm_boundary:main": "verify-scm-boundary",
|
|
292
|
+
"verify_no_task_runtime:main": "verify-no-task-runtime",
|
|
293
|
+
"preflight_cache:main": "preflight-cache",
|
|
294
|
+
"preflight_wip_cap:main": "verify-wip-cap",
|
|
295
|
+
"pack_render:main": "pack-render",
|
|
296
|
+
"validate_strategy_output:main": "validate-strategy-output",
|
|
297
|
+
"vbrief_validate:main": "vbrief-validate",
|
|
298
|
+
};
|
|
299
|
+
function deftCliBin(frameworkRoot) {
|
|
300
|
+
return join(frameworkRoot, "packages", "cli", "dist", "bin.js");
|
|
301
|
+
}
|
|
302
|
+
function spawnDeftVerb(verb, argv, cwd, frameworkRoot) {
|
|
303
|
+
const bin = deftCliBin(frameworkRoot);
|
|
304
|
+
if (!existsSync(bin)) {
|
|
305
|
+
return {
|
|
306
|
+
code: 2,
|
|
307
|
+
stdout: "",
|
|
308
|
+
stderr: `deft CLI not built at ${bin}; run pnpm run build first\n`,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
309
311
|
try {
|
|
310
|
-
const stdout = execFileSync(
|
|
311
|
-
cwd
|
|
312
|
+
const stdout = execFileSync(process.execPath, [bin, verb, ...argv], {
|
|
313
|
+
cwd,
|
|
312
314
|
encoding: "utf8",
|
|
313
|
-
env: { ...process.env,
|
|
315
|
+
env: { ...process.env, DEFT_CACHE_DISABLE: "1" },
|
|
314
316
|
});
|
|
315
317
|
return { code: 0, stdout: typeof stdout === "string" ? stdout : "", stderr: "" };
|
|
316
318
|
}
|
|
@@ -350,7 +352,23 @@ function invokeEntrypoint(entrypoint, argv, cwd, frameworkRoot, noArgv, capture)
|
|
|
350
352
|
}
|
|
351
353
|
return { code: inline(argv), stdout: "", stderr: "" };
|
|
352
354
|
}
|
|
353
|
-
|
|
355
|
+
const verb = ENTRYPOINT_VERB[entrypoint];
|
|
356
|
+
if (verb) {
|
|
357
|
+
const effectiveArgv = noArgv ? [] : argv;
|
|
358
|
+
const result = spawnDeftVerb(verb, effectiveArgv, cwd, frameworkRoot);
|
|
359
|
+
if (capture)
|
|
360
|
+
return result;
|
|
361
|
+
if (result.stdout)
|
|
362
|
+
process.stdout.write(result.stdout);
|
|
363
|
+
if (result.stderr)
|
|
364
|
+
process.stderr.write(result.stderr);
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
code: 2,
|
|
369
|
+
stdout: "",
|
|
370
|
+
stderr: `unknown entrypoint (Python scripts removed #1860): ${entrypoint}\n`,
|
|
371
|
+
};
|
|
354
372
|
}
|
|
355
373
|
export function runFrameworkCommand(name, argv = [], options = {}) {
|
|
356
374
|
const root = resolve(options.projectRoot ?? process.cwd());
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Matches `## Current shape (as of pass-N)` — same pattern as vbrief-reconcile/umbrellas.ts. */
|
|
2
|
+
export declare const CURRENT_SHAPE_HEADER_RE: RegExp;
|
|
3
|
+
export declare const SECTION_MARKERS: {
|
|
4
|
+
readonly lastUpdated: RegExp;
|
|
5
|
+
readonly lastPassType: RegExp;
|
|
6
|
+
readonly childCount: RegExp;
|
|
7
|
+
readonly childCountHistory: RegExp;
|
|
8
|
+
readonly openChildren: RegExp;
|
|
9
|
+
readonly closedChildren: RegExp;
|
|
10
|
+
readonly waveOrder: RegExp;
|
|
11
|
+
readonly openQuestions: RegExp;
|
|
12
|
+
readonly readingOrder: RegExp;
|
|
13
|
+
};
|
|
14
|
+
export type SectionKey = keyof typeof SECTION_MARKERS;
|
|
15
|
+
/** Required per AGENTS.md #1152; openQuestions is optional. */
|
|
16
|
+
export declare const REQUIRED_SECTIONS: readonly SectionKey[];
|
|
17
|
+
export declare const OPTIONAL_SECTIONS: readonly SectionKey[];
|
|
18
|
+
export interface IssueComment {
|
|
19
|
+
readonly id: number;
|
|
20
|
+
readonly body: string;
|
|
21
|
+
readonly htmlUrl: string;
|
|
22
|
+
readonly updatedAt: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CurrentShapeComment extends IssueComment {
|
|
25
|
+
readonly pass: number;
|
|
26
|
+
}
|
|
27
|
+
export interface SectionPresence {
|
|
28
|
+
readonly present: SectionKey[];
|
|
29
|
+
readonly missing: SectionKey[];
|
|
30
|
+
readonly optionalPresent: SectionKey[];
|
|
31
|
+
readonly optionalMissing: SectionKey[];
|
|
32
|
+
}
|
|
33
|
+
export interface CurrentShapeResult {
|
|
34
|
+
readonly issueNumber: number;
|
|
35
|
+
readonly repo: string;
|
|
36
|
+
readonly commentId: number;
|
|
37
|
+
readonly htmlUrl: string;
|
|
38
|
+
readonly pass: number;
|
|
39
|
+
readonly body: string;
|
|
40
|
+
readonly sections: SectionPresence;
|
|
41
|
+
}
|
|
42
|
+
export type ScmFetcher = (repo: string, issueNumber: number) => IssueComment[] | {
|
|
43
|
+
error: string;
|
|
44
|
+
};
|
|
45
|
+
export interface RunCurrentShapeOptions {
|
|
46
|
+
readonly issueNumber: number;
|
|
47
|
+
readonly projectRoot: string;
|
|
48
|
+
readonly repo?: string | null;
|
|
49
|
+
readonly jsonMode?: boolean;
|
|
50
|
+
readonly strict?: boolean;
|
|
51
|
+
readonly fetchComments?: ScmFetcher;
|
|
52
|
+
readonly writeOut?: (text: string) => void;
|
|
53
|
+
readonly writeErr?: (text: string) => void;
|
|
54
|
+
}
|
|
55
|
+
/** Merge `gh api --paginate` concatenated JSON array pages into comment rows. */
|
|
56
|
+
export declare function parseCommentsFromGhStdout(stdout: string): IssueComment[];
|
|
57
|
+
export declare function extractPassFromBody(body: string): number | null;
|
|
58
|
+
/** Pick the canonical comment — highest pass-N; tie-break by comment id (latest). */
|
|
59
|
+
export declare function selectCurrentShapeComment(comments: readonly IssueComment[]): CurrentShapeComment | null;
|
|
60
|
+
export declare function detectSections(body: string): SectionPresence;
|
|
61
|
+
export declare function sectionsRecord(presence: SectionPresence): Record<string, boolean>;
|
|
62
|
+
export declare const NO_CURRENT_SHAPE_MESSAGE: string;
|
|
63
|
+
export declare function fetchCurrentShape(options: {
|
|
64
|
+
repo: string;
|
|
65
|
+
issueNumber: number;
|
|
66
|
+
fetchComments?: ScmFetcher;
|
|
67
|
+
}): {
|
|
68
|
+
ok: true;
|
|
69
|
+
result: CurrentShapeResult;
|
|
70
|
+
} | {
|
|
71
|
+
ok: false;
|
|
72
|
+
error: string;
|
|
73
|
+
kind: "not-found" | "config";
|
|
74
|
+
};
|
|
75
|
+
export declare function emitCurrentShape(result: CurrentShapeResult, options: {
|
|
76
|
+
jsonMode: boolean;
|
|
77
|
+
writeOut: (text: string) => void;
|
|
78
|
+
}): number;
|
|
79
|
+
export declare function runCurrentShape(options: RunCurrentShapeOptions): number;
|
|
80
|
+
export declare function parseCurrentShapeArgv(argv: readonly string[]): {
|
|
81
|
+
issueNumber: number | null;
|
|
82
|
+
repo: string | null;
|
|
83
|
+
jsonMode: boolean;
|
|
84
|
+
strict: boolean;
|
|
85
|
+
passthroughError?: string;
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { resolveBinary } from "../scm/binary.js";
|
|
3
|
+
import { SUBPROCESS_MAX_BUFFER } from "../subprocess/max-buffer.js";
|
|
4
|
+
import { resolveRepo } from "../triage/queue/repo.js";
|
|
5
|
+
/** Matches `## Current shape (as of pass-N)` — same pattern as vbrief-reconcile/umbrellas.ts. */
|
|
6
|
+
export const CURRENT_SHAPE_HEADER_RE = /^## Current shape \(as of pass-(\d+)\)/m;
|
|
7
|
+
export const SECTION_MARKERS = {
|
|
8
|
+
lastUpdated: /^Last updated:\s/m,
|
|
9
|
+
lastPassType: /^Last pass type:\s/m,
|
|
10
|
+
childCount: /^Child count:\s/m,
|
|
11
|
+
childCountHistory: /^Child-count history:\s/m,
|
|
12
|
+
openChildren: /^### Open children\s*$/m,
|
|
13
|
+
closedChildren: /^### Closed children\s*$/m,
|
|
14
|
+
waveOrder: /^### Wave order\s*$/m,
|
|
15
|
+
openQuestions: /^### Open questions\s*$/m,
|
|
16
|
+
readingOrder: /^### Reading order for fresh contributors\s*$/m,
|
|
17
|
+
};
|
|
18
|
+
/** Required per AGENTS.md #1152; openQuestions is optional. */
|
|
19
|
+
export const REQUIRED_SECTIONS = [
|
|
20
|
+
"lastUpdated",
|
|
21
|
+
"lastPassType",
|
|
22
|
+
"childCount",
|
|
23
|
+
"childCountHistory",
|
|
24
|
+
"openChildren",
|
|
25
|
+
"closedChildren",
|
|
26
|
+
"waveOrder",
|
|
27
|
+
"readingOrder",
|
|
28
|
+
];
|
|
29
|
+
export const OPTIONAL_SECTIONS = ["openQuestions"];
|
|
30
|
+
function mapCommentEntry(entry) {
|
|
31
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const rec = entry;
|
|
35
|
+
if (typeof rec.id !== "number" || typeof rec.body !== "string") {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
id: rec.id,
|
|
40
|
+
body: rec.body,
|
|
41
|
+
htmlUrl: typeof rec.html_url === "string" ? rec.html_url : "",
|
|
42
|
+
updatedAt: typeof rec.updated_at === "string" ? rec.updated_at : "",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Merge `gh api --paginate` concatenated JSON array pages into comment rows. */
|
|
46
|
+
export function parseCommentsFromGhStdout(stdout) {
|
|
47
|
+
const comments = [];
|
|
48
|
+
const text = stdout.trim();
|
|
49
|
+
if (!text) {
|
|
50
|
+
return comments;
|
|
51
|
+
}
|
|
52
|
+
const pages = [];
|
|
53
|
+
try {
|
|
54
|
+
pages.push(JSON.parse(text));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
let idx = 0;
|
|
58
|
+
while (idx < text.length) {
|
|
59
|
+
while (idx < text.length && /\s/.test(text[idx] ?? "")) {
|
|
60
|
+
idx += 1;
|
|
61
|
+
}
|
|
62
|
+
if (idx >= text.length) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
const slice = text.slice(idx);
|
|
66
|
+
if (!slice.startsWith("[")) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
let depth = 0;
|
|
70
|
+
let end = -1;
|
|
71
|
+
for (let j = 0; j < slice.length; j += 1) {
|
|
72
|
+
const ch = slice[j];
|
|
73
|
+
if (ch === "[") {
|
|
74
|
+
depth += 1;
|
|
75
|
+
}
|
|
76
|
+
else if (ch === "]") {
|
|
77
|
+
depth -= 1;
|
|
78
|
+
if (depth === 0) {
|
|
79
|
+
end = j + 1;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (end < 0) {
|
|
85
|
+
throw new SyntaxError("invalid paginated JSON");
|
|
86
|
+
}
|
|
87
|
+
pages.push(JSON.parse(slice.slice(0, end)));
|
|
88
|
+
idx += end;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const page of pages) {
|
|
92
|
+
if (!Array.isArray(page)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
for (const entry of page) {
|
|
96
|
+
const mapped = mapCommentEntry(entry);
|
|
97
|
+
if (mapped !== null) {
|
|
98
|
+
comments.push(mapped);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return comments;
|
|
103
|
+
}
|
|
104
|
+
function defaultFetchComments(repo, issueNumber) {
|
|
105
|
+
const binary = resolveBinary();
|
|
106
|
+
const path = `repos/${repo}/issues/${issueNumber}/comments?per_page=100`;
|
|
107
|
+
const proc = spawnSync(binary, ["api", "--paginate", path], {
|
|
108
|
+
encoding: "utf8",
|
|
109
|
+
maxBuffer: SUBPROCESS_MAX_BUFFER,
|
|
110
|
+
});
|
|
111
|
+
if (proc.error !== undefined) {
|
|
112
|
+
return {
|
|
113
|
+
error: `fetch comments #${issueNumber} (${repo}) failed: ${proc.error.message}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (proc.status !== 0) {
|
|
117
|
+
return {
|
|
118
|
+
error: `fetch comments #${issueNumber} (${repo}) failed: ${(proc.stderr || proc.stdout || "").trim()}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return parseCommentsFromGhStdout(String(proc.stdout ?? ""));
|
|
123
|
+
}
|
|
124
|
+
catch (exc) {
|
|
125
|
+
return {
|
|
126
|
+
error: `fetch comments #${issueNumber} (${repo}) returned non-JSON: ${String(exc)}`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export function extractPassFromBody(body) {
|
|
131
|
+
const match = CURRENT_SHAPE_HEADER_RE.exec(body);
|
|
132
|
+
if (!match?.[1]) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const pass = Number(match[1]);
|
|
136
|
+
return Number.isFinite(pass) ? pass : null;
|
|
137
|
+
}
|
|
138
|
+
/** Pick the canonical comment — highest pass-N; tie-break by comment id (latest). */
|
|
139
|
+
export function selectCurrentShapeComment(comments) {
|
|
140
|
+
let best = null;
|
|
141
|
+
for (const comment of comments) {
|
|
142
|
+
const pass = extractPassFromBody(comment.body);
|
|
143
|
+
if (pass === null) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const candidate = { ...comment, pass };
|
|
147
|
+
if (best === null ||
|
|
148
|
+
candidate.pass > best.pass ||
|
|
149
|
+
(candidate.pass === best.pass && candidate.id > best.id)) {
|
|
150
|
+
best = candidate;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return best;
|
|
154
|
+
}
|
|
155
|
+
export function detectSections(body) {
|
|
156
|
+
const present = [];
|
|
157
|
+
const missing = [];
|
|
158
|
+
const optionalPresent = [];
|
|
159
|
+
const optionalMissing = [];
|
|
160
|
+
for (const key of REQUIRED_SECTIONS) {
|
|
161
|
+
if (SECTION_MARKERS[key].test(body)) {
|
|
162
|
+
present.push(key);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
missing.push(key);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const key of OPTIONAL_SECTIONS) {
|
|
169
|
+
if (SECTION_MARKERS[key].test(body)) {
|
|
170
|
+
optionalPresent.push(key);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
optionalMissing.push(key);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { present, missing, optionalPresent, optionalMissing };
|
|
177
|
+
}
|
|
178
|
+
export function sectionsRecord(presence) {
|
|
179
|
+
const out = {};
|
|
180
|
+
for (const key of REQUIRED_SECTIONS) {
|
|
181
|
+
out[key] = presence.present.includes(key);
|
|
182
|
+
}
|
|
183
|
+
for (const key of OPTIONAL_SECTIONS) {
|
|
184
|
+
out[key] = presence.optionalPresent.includes(key);
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
export const NO_CURRENT_SHAPE_MESSAGE = "No ## Current shape (as of pass-N) comment found on this issue. " +
|
|
189
|
+
"Create one per AGENTS.md ## Umbrella current-shape convention (#1152) — " +
|
|
190
|
+
"do not fall back to the issue body (stale by design).";
|
|
191
|
+
export function fetchCurrentShape(options) {
|
|
192
|
+
const fetcher = options.fetchComments ?? defaultFetchComments;
|
|
193
|
+
const fetched = fetcher(options.repo, options.issueNumber);
|
|
194
|
+
if (!Array.isArray(fetched)) {
|
|
195
|
+
return { ok: false, error: fetched.error, kind: "config" };
|
|
196
|
+
}
|
|
197
|
+
const selected = selectCurrentShapeComment(fetched);
|
|
198
|
+
if (selected === null) {
|
|
199
|
+
return { ok: false, error: NO_CURRENT_SHAPE_MESSAGE, kind: "not-found" };
|
|
200
|
+
}
|
|
201
|
+
const sections = detectSections(selected.body);
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
result: {
|
|
205
|
+
issueNumber: options.issueNumber,
|
|
206
|
+
repo: options.repo,
|
|
207
|
+
commentId: selected.id,
|
|
208
|
+
htmlUrl: selected.htmlUrl,
|
|
209
|
+
pass: selected.pass,
|
|
210
|
+
body: selected.body,
|
|
211
|
+
sections,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export function emitCurrentShape(result, options) {
|
|
216
|
+
if (options.jsonMode) {
|
|
217
|
+
const payload = {
|
|
218
|
+
issueNumber: result.issueNumber,
|
|
219
|
+
repo: result.repo,
|
|
220
|
+
commentId: result.commentId,
|
|
221
|
+
htmlUrl: result.htmlUrl,
|
|
222
|
+
pass: result.pass,
|
|
223
|
+
body: result.body,
|
|
224
|
+
sections: sectionsRecord(result.sections),
|
|
225
|
+
missingSections: result.sections.missing,
|
|
226
|
+
missingOptionalSections: result.sections.optionalMissing,
|
|
227
|
+
};
|
|
228
|
+
options.writeOut(`${JSON.stringify(payload)}\n`);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
options.writeOut(`${result.body}\n`);
|
|
232
|
+
}
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
export function runCurrentShape(options) {
|
|
236
|
+
const writeOut = options.writeOut ?? ((text) => process.stdout.write(text));
|
|
237
|
+
const writeErr = options.writeErr ?? ((text) => process.stderr.write(text));
|
|
238
|
+
if (!Number.isInteger(options.issueNumber) || options.issueNumber <= 0) {
|
|
239
|
+
writeErr("umbrella:current-shape: issue number must be a positive integer\n");
|
|
240
|
+
return 2;
|
|
241
|
+
}
|
|
242
|
+
const repo = resolveRepo(options.repo ?? null, options.projectRoot);
|
|
243
|
+
if (repo === null) {
|
|
244
|
+
writeErr("umbrella:current-shape: could not resolve owner/repo — pass --repo OWNER/REPO or run inside a git repo with origin\n");
|
|
245
|
+
return 2;
|
|
246
|
+
}
|
|
247
|
+
const fetched = fetchCurrentShape({
|
|
248
|
+
repo,
|
|
249
|
+
issueNumber: options.issueNumber,
|
|
250
|
+
fetchComments: options.fetchComments,
|
|
251
|
+
});
|
|
252
|
+
if (!fetched.ok) {
|
|
253
|
+
writeErr(`umbrella:current-shape: ${fetched.error}\n`);
|
|
254
|
+
return fetched.kind === "config" ? 2 : 1;
|
|
255
|
+
}
|
|
256
|
+
if (options.strict === true && fetched.result.sections.missing.length > 0) {
|
|
257
|
+
writeErr(`umbrella:current-shape: --strict: missing required section(s): ${fetched.result.sections.missing.join(", ")}\n`);
|
|
258
|
+
return 1;
|
|
259
|
+
}
|
|
260
|
+
return emitCurrentShape(fetched.result, {
|
|
261
|
+
jsonMode: options.jsonMode ?? false,
|
|
262
|
+
writeOut,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
export function parseCurrentShapeArgv(argv) {
|
|
266
|
+
let issueNumber = null;
|
|
267
|
+
let repo = null;
|
|
268
|
+
let jsonMode = false;
|
|
269
|
+
let strict = false;
|
|
270
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
271
|
+
const arg = argv[i] ?? "";
|
|
272
|
+
if (arg === "--json") {
|
|
273
|
+
jsonMode = true;
|
|
274
|
+
}
|
|
275
|
+
else if (arg === "--strict") {
|
|
276
|
+
strict = true;
|
|
277
|
+
}
|
|
278
|
+
else if (arg === "--repo") {
|
|
279
|
+
const value = argv[i + 1];
|
|
280
|
+
if (value === undefined) {
|
|
281
|
+
return {
|
|
282
|
+
issueNumber,
|
|
283
|
+
repo,
|
|
284
|
+
jsonMode,
|
|
285
|
+
strict,
|
|
286
|
+
passthroughError: "argument --repo: expected one argument",
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
repo = value;
|
|
290
|
+
i += 1;
|
|
291
|
+
}
|
|
292
|
+
else if (arg.startsWith("--repo=")) {
|
|
293
|
+
repo = arg.slice("--repo=".length);
|
|
294
|
+
}
|
|
295
|
+
else if (arg.startsWith("-")) {
|
|
296
|
+
return {
|
|
297
|
+
issueNumber,
|
|
298
|
+
repo,
|
|
299
|
+
jsonMode,
|
|
300
|
+
strict,
|
|
301
|
+
passthroughError: `unknown flag: ${arg}`,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
else if (issueNumber === null) {
|
|
305
|
+
const parsed = Number(arg);
|
|
306
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
307
|
+
return {
|
|
308
|
+
issueNumber,
|
|
309
|
+
repo,
|
|
310
|
+
jsonMode,
|
|
311
|
+
strict,
|
|
312
|
+
passthroughError: `invalid issue number: ${arg}`,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
issueNumber = parsed;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
return {
|
|
319
|
+
issueNumber,
|
|
320
|
+
repo,
|
|
321
|
+
jsonMode,
|
|
322
|
+
strict,
|
|
323
|
+
passthroughError: `unexpected positional argument: ${arg}`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return { issueNumber, repo, jsonMode, strict };
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -16,6 +16,9 @@ export interface PrecutoverDetection {
|
|
|
16
16
|
reasons: string[];
|
|
17
17
|
}
|
|
18
18
|
export declare function detectPreCutover(projectRoot: string): PrecutoverDetection;
|
|
19
|
+
/** Last release that ships `scripts/migrate_vbrief.py` on the consumer deposit path (#2068). */
|
|
20
|
+
export declare const FROZEN_PRECUTOVER_MIGRATION_TAG = "v0.59.0";
|
|
21
|
+
export declare function frozenPreCutoverMigrationGuidance(): string;
|
|
19
22
|
export declare function renderPrecutoverLine(projectRoot: string): string;
|
|
20
23
|
export { isFullSpecState, isGreenfieldSpecExport };
|
|
21
24
|
//# sourceMappingURL=precutover.d.ts.map
|