@deftai/directive-core 0.62.0 → 0.64.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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=build-dist-runner.d.ts.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Subprocess entry for sync callers (release pipeline, task build).
3
+ * Top-level await keeps the process alive until the archive is written.
4
+ */
5
+ import { buildArchive, selectFormat } from "./build-dist.js";
6
+ const version = process.argv[2];
7
+ const root = process.argv[3];
8
+ if (!version || !root) {
9
+ process.stderr.write("usage: build-dist-runner <version> <root>\n");
10
+ process.exit(2);
11
+ }
12
+ const fmt = selectFormat(process.env.DEFT_BUILD_FORMAT);
13
+ try {
14
+ const out = await buildArchive(root, version, fmt);
15
+ process.stdout.write(`${out}\n`);
16
+ }
17
+ catch (err) {
18
+ process.stderr.write(`${String(err)}\n`);
19
+ process.exit(1);
20
+ }
21
+ //# sourceMappingURL=build-dist-runner.js.map
@@ -0,0 +1,14 @@
1
+ export declare const DEFAULT_EXCLUDES: Set<string>;
2
+ export declare const DEFAULT_EXCLUDED_PATH_PREFIXES: readonly ["history/archive", "vbrief/completed", "vbrief/cancelled"];
3
+ export declare const ARCHIVE_ROOT = "deft";
4
+ export declare const CONTENT_PREFIX = "content/";
5
+ export declare function iterSourceFiles(root: string, excludes?: ReadonlySet<string>, excludedPrefixes?: readonly string[]): Array<{
6
+ absPath: string;
7
+ archiveRel: string;
8
+ }>;
9
+ export declare function selectFormat(arg: string | null | undefined): "tar" | "zip";
10
+ export declare function outputPath(root: string, version: string, fmt: "tar" | "zip"): string;
11
+ export declare function buildArchive(root: string, version: string, fmt: "tar" | "zip", extraExcludes?: readonly string[]): Promise<string>;
12
+ export declare function parseExtraExcludes(raw: string): string[];
13
+ export declare function main(argv: readonly string[]): Promise<number>;
14
+ //# sourceMappingURL=build-dist.d.ts.map
@@ -0,0 +1,171 @@
1
+ import { createWriteStream, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { platform } from "node:os";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
+ const { ZipArchive, TarArchive } = createRequire(import.meta.url)("archiver");
6
+ export const DEFAULT_EXCLUDES = new Set([
7
+ ".git",
8
+ "dist",
9
+ "backup",
10
+ "node_modules",
11
+ "__pycache__",
12
+ ".venv",
13
+ "htmlcov",
14
+ ".pytest_cache",
15
+ ".mypy_cache",
16
+ ".ruff_cache",
17
+ ".coverage",
18
+ ]);
19
+ export const DEFAULT_EXCLUDED_PATH_PREFIXES = [
20
+ "history/archive",
21
+ "vbrief/completed",
22
+ "vbrief/cancelled",
23
+ ];
24
+ export const ARCHIVE_ROOT = "deft";
25
+ export const CONTENT_PREFIX = "content/";
26
+ const VENDORED_TS_TEST_RE = /\.(test|spec)\.(c|m)?[jt]sx?$/i;
27
+ function flattenContentPrefix(relPosix) {
28
+ if (relPosix === "content")
29
+ return relPosix;
30
+ if (relPosix.startsWith(CONTENT_PREFIX)) {
31
+ return relPosix.slice(CONTENT_PREFIX.length);
32
+ }
33
+ return relPosix;
34
+ }
35
+ function matchesExcludedPrefix(relPosix, prefixes) {
36
+ return prefixes.some((prefix) => relPosix === prefix || relPosix.startsWith(`${prefix}/`));
37
+ }
38
+ function isVendoredTsTest(relPosix) {
39
+ if (relPosix !== "packages" && !relPosix.startsWith("packages/")) {
40
+ return false;
41
+ }
42
+ const basename = relPosix.split("/").pop() ?? "";
43
+ return VENDORED_TS_TEST_RE.test(basename);
44
+ }
45
+ export function iterSourceFiles(root, excludes = DEFAULT_EXCLUDES, excludedPrefixes = DEFAULT_EXCLUDED_PATH_PREFIXES) {
46
+ const entries = [];
47
+ const walk = (dir) => {
48
+ let names;
49
+ try {
50
+ names = readdirSync(dir).sort();
51
+ }
52
+ catch {
53
+ return;
54
+ }
55
+ for (const name of names) {
56
+ if (excludes.has(name))
57
+ continue;
58
+ const absPath = join(dir, name);
59
+ let relPosix;
60
+ try {
61
+ relPosix = relative(root, absPath).split("\\").join("/");
62
+ }
63
+ catch {
64
+ continue;
65
+ }
66
+ if (matchesExcludedPrefix(relPosix, excludedPrefixes))
67
+ continue;
68
+ let st;
69
+ try {
70
+ st = statSync(absPath);
71
+ }
72
+ catch {
73
+ continue;
74
+ }
75
+ if (st.isDirectory()) {
76
+ walk(absPath);
77
+ continue;
78
+ }
79
+ if (!st.isFile())
80
+ continue;
81
+ if (isVendoredTsTest(relPosix))
82
+ continue;
83
+ entries.push({ absPath, archiveRel: flattenContentPrefix(relPosix) });
84
+ }
85
+ };
86
+ walk(root);
87
+ entries.sort((a, b) => a.archiveRel.localeCompare(b.archiveRel));
88
+ return entries;
89
+ }
90
+ export function selectFormat(arg) {
91
+ if (arg)
92
+ return arg.toLowerCase() === "zip" ? "zip" : "tar";
93
+ return platform().startsWith("win") ? "zip" : "tar";
94
+ }
95
+ export function outputPath(root, version, fmt) {
96
+ const suffix = fmt === "zip" ? "zip" : "tar.gz";
97
+ return join(root, "dist", `deft-${version}.${suffix}`);
98
+ }
99
+ export async function buildArchive(root, version, fmt, extraExcludes = []) {
100
+ const excludes = new Set([...DEFAULT_EXCLUDES, ...extraExcludes]);
101
+ const output = outputPath(root, version, fmt);
102
+ mkdirSync(dirname(output), { recursive: true });
103
+ if (existsSync(output)) {
104
+ unlinkSync(output);
105
+ }
106
+ const entries = iterSourceFiles(root, excludes);
107
+ await new Promise((resolvePromise, reject) => {
108
+ const out = createWriteStream(output);
109
+ const archive = fmt === "zip"
110
+ ? new ZipArchive({ zlib: { level: 9 } })
111
+ : new TarArchive({ gzip: true, gzipOptions: { level: 9 } });
112
+ out.on("close", () => resolvePromise());
113
+ out.on("error", (err) => reject(err));
114
+ archive.on("error", (err) => reject(err));
115
+ archive.pipe(out);
116
+ for (const { absPath, archiveRel } of entries) {
117
+ archive.file(absPath, { name: `${ARCHIVE_ROOT}/${archiveRel}` });
118
+ }
119
+ void archive.finalize();
120
+ });
121
+ return output;
122
+ }
123
+ export function parseExtraExcludes(raw) {
124
+ return raw
125
+ .split(",")
126
+ .map((p) => p.trim())
127
+ .filter(Boolean);
128
+ }
129
+ export async function main(argv) {
130
+ const args = [...argv];
131
+ let version = null;
132
+ let fmtArg = null;
133
+ let root = null;
134
+ let extra = "";
135
+ for (let i = 0; i < args.length; i += 1) {
136
+ const arg = args[i];
137
+ if (arg === "--version")
138
+ version = args[++i];
139
+ else if (arg === "--format")
140
+ fmtArg = args[++i];
141
+ else if (arg === "--root")
142
+ root = args[++i];
143
+ else if (arg === "--exclude-extra")
144
+ extra = args[++i];
145
+ else if (arg === "-h" || arg === "--help") {
146
+ process.stderr.write("usage: build-dist --version X.Y.Z [--format tar|zip] [--root PATH] [--exclude-extra a,b]\n");
147
+ return 2;
148
+ }
149
+ }
150
+ if (!version) {
151
+ process.stderr.write("error: --version is required\n");
152
+ return 2;
153
+ }
154
+ const projectRoot = resolve(root ?? process.cwd());
155
+ if (!existsSync(projectRoot)) {
156
+ process.stderr.write(`error: root not found: ${projectRoot}\n`);
157
+ return 2;
158
+ }
159
+ const fmt = selectFormat(fmtArg);
160
+ try {
161
+ const out = await buildArchive(projectRoot, version, fmt, parseExtraExcludes(extra));
162
+ const printable = relative(projectRoot, out) || out;
163
+ process.stdout.write(`Created ${printable}\n`);
164
+ return 0;
165
+ }
166
+ catch (err) {
167
+ process.stderr.write(`error: ${String(err)}\n`);
168
+ return 1;
169
+ }
170
+ }
171
+ //# sourceMappingURL=build-dist.js.map
@@ -13,7 +13,7 @@ export declare const FRESH_UNRELEASED_BLOCK: string;
13
13
  export declare const TOTAL_STEPS = 13;
14
14
  export declare const VERIFY_DRAFT_MAX_ATTEMPTS = 5;
15
15
  export declare const VERIFY_DRAFT_INTERVAL_SECONDS = 1;
16
- export declare const RELEASE_ARTIFACTS: readonly ["CHANGELOG.md", "ROADMAP.md", "pyproject.toml", "uv.lock"];
16
+ export declare const RELEASE_ARTIFACTS: readonly ["CHANGELOG.md", "ROADMAP.md"];
17
17
  export declare const BRANCH_GATE_BYPASS_ENV = "DEFT_ALLOW_DEFAULT_BRANCH_COMMIT";
18
18
  export declare const DESTRUCTIVE_GH_GATE_BYPASS_ENV = "DEFT_ALLOW_DESTRUCTIVE_GH_VERBS";
19
19
  export declare const PYPROJECT_VERSION_LINE_RE: RegExp;
@@ -21,12 +21,7 @@ export const FRESH_UNRELEASED_BLOCK = "## [Unreleased]\n" +
21
21
  export const TOTAL_STEPS = 13;
22
22
  export const VERIFY_DRAFT_MAX_ATTEMPTS = 5;
23
23
  export const VERIFY_DRAFT_INTERVAL_SECONDS = 1.0;
24
- export const RELEASE_ARTIFACTS = [
25
- "CHANGELOG.md",
26
- "ROADMAP.md",
27
- "pyproject.toml",
28
- "uv.lock",
29
- ];
24
+ export const RELEASE_ARTIFACTS = ["CHANGELOG.md", "ROADMAP.md"];
30
25
  export const BRANCH_GATE_BYPASS_ENV = "DEFT_ALLOW_DEFAULT_BRANCH_COMMIT";
31
26
  export const DESTRUCTIVE_GH_GATE_BYPASS_ENV = "DEFT_ALLOW_DESTRUCTIVE_GH_VERBS";
32
27
  export const PYPROJECT_VERSION_LINE_RE = /version\s*=\s*"[^"]*"/;
@@ -1,14 +1,13 @@
1
+ export * from "./build-dist.js";
1
2
  export * from "./constants.js";
2
3
  export * from "./flags.js";
3
4
  export * from "./gh.js";
4
5
  export * from "./git.js";
5
6
  export * from "./main.js";
7
+ export * from "./native-steps.js";
6
8
  export * from "./paths.js";
7
9
  export * from "./pipeline.js";
8
10
  export * from "./preflight.js";
9
- export * from "./pyproject.js";
10
- export * from "./pyproject-sync.js";
11
- export * from "./python-steps.js";
12
11
  export * from "./spawn.js";
13
12
  export * from "./types.js";
14
13
  export * from "./version.js";
@@ -1,15 +1,14 @@
1
1
  /* v8 ignore file -- re-export barrel; covered via main.ts */
2
+ export * from "./build-dist.js";
2
3
  export * from "./constants.js";
3
4
  export * from "./flags.js";
4
5
  export * from "./gh.js";
5
6
  export * from "./git.js";
6
7
  export * from "./main.js";
8
+ export * from "./native-steps.js";
7
9
  export * from "./paths.js";
8
10
  export * from "./pipeline.js";
9
11
  export * from "./preflight.js";
10
- export * from "./pyproject.js";
11
- export * from "./pyproject-sync.js";
12
- export * from "./python-steps.js";
13
12
  export * from "./spawn.js";
14
13
  export * from "./types.js";
15
14
  export * from "./version.js";
@@ -0,0 +1,5 @@
1
+ import type { ReleaseSeams } from "./types.js";
2
+ export declare function refreshRoadmapNative(projectRoot: string): [boolean, string];
3
+ export declare function checkVbriefLifecycleSyncNative(projectRoot: string, repo: string): [boolean, number, string];
4
+ export declare function runBuildNative(projectRoot: string, version: string | null, _seams?: ReleaseSeams): [boolean, string];
5
+ //# sourceMappingURL=native-steps.d.ts.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Native TypeScript release pipeline steps (#1860).
3
+ * Replaces python-steps.ts (scripts/ roadmap_render, reconcile_issues, build_dist).
4
+ */
5
+ import { spawnSync } from "node:child_process";
6
+ import { dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { fetchIssueStates, isTerminalLifecyclePath, reconcile, scanVbriefDir, } from "../intake/reconcile-issues.js";
9
+ import { renderRoadmap } from "../render/roadmap-render.js";
10
+ const BUILD_DIST_RUNNER = join(dirname(fileURLToPath(import.meta.url)), "build-dist-runner.js");
11
+ export function refreshRoadmapNative(projectRoot) {
12
+ const pending = join(projectRoot, "vbrief", "pending");
13
+ const completed = join(projectRoot, "vbrief", "completed");
14
+ const roadmap = join(projectRoot, "ROADMAP.md");
15
+ const [ok, msg] = renderRoadmap(pending, roadmap, completed);
16
+ if (!ok) {
17
+ return [false, `roadmap:render failed: ${msg}`];
18
+ }
19
+ return [true, "ROADMAP.md re-rendered"];
20
+ }
21
+ export function checkVbriefLifecycleSyncNative(projectRoot, repo) {
22
+ const vbriefDir = join(projectRoot, "vbrief");
23
+ try {
24
+ const issueToVbriefs = scanVbriefDir(vbriefDir);
25
+ const issueStateMap = fetchIssueStates(repo, new Set(issueToVbriefs.keys()), {
26
+ cwd: projectRoot,
27
+ });
28
+ if (issueStateMap === null) {
29
+ return [false, -1, "failed to fetch issue states from gh"];
30
+ }
31
+ const report = reconcile(issueToVbriefs, issueStateMap);
32
+ const mismatches = [];
33
+ for (const entry of report.no_open_issue) {
34
+ const files = (entry.vbrief_files ?? []);
35
+ for (const rel of files) {
36
+ if (!isTerminalLifecyclePath(rel)) {
37
+ mismatches.push(rel);
38
+ }
39
+ }
40
+ }
41
+ const count = mismatches.length;
42
+ if (count === 0) {
43
+ return [true, 0, "no mismatches"];
44
+ }
45
+ const suffix = count > 5 ? " ..." : "";
46
+ const preview = mismatches.slice(0, 5).join(", ");
47
+ const reason = `${count} closed-issue vBRIEF(s) not in completed/ or cancelled/: ${preview}${suffix}`;
48
+ return [false, count, reason];
49
+ }
50
+ catch (err) {
51
+ const msg = err instanceof Error ? err.message : String(err);
52
+ return [false, -1, msg];
53
+ }
54
+ }
55
+ export function runBuildNative(projectRoot, version, _seams = {}) {
56
+ if (!version) {
57
+ return [false, "build requires a release version"];
58
+ }
59
+ const env = { ...process.env, DEFT_RELEASE_VERSION: version };
60
+ const result = spawnSync(process.execPath, [BUILD_DIST_RUNNER, version, projectRoot], {
61
+ cwd: projectRoot,
62
+ encoding: "utf8",
63
+ env,
64
+ });
65
+ if (result.status !== 0) {
66
+ const msg = (result.stderr ?? result.stdout ?? "").trim() || `exit ${result.status ?? 1}`;
67
+ return [false, `build failed: ${msg}`];
68
+ }
69
+ const out = (result.stdout ?? "").trim();
70
+ return [true, `build ran clean (DEFT_RELEASE_VERSION=${version}) -> ${out}`];
71
+ }
72
+ //# sourceMappingURL=native-steps.js.map
@@ -4,10 +4,9 @@ import { prependUpgradeBanner, promoteChangelog, sectionForVersion } from "./cha
4
4
  import { EXIT_CONFIG_ERROR, EXIT_OK, EXIT_VIOLATION, RELEASE_ARTIFACTS, TOTAL_STEPS, VERIFY_DRAFT_INTERVAL_SECONDS, VERIFY_DRAFT_MAX_ATTEMPTS, } from "./constants.js";
5
5
  import { checkTagAvailable, createGithubRelease, readTextFile, verifyReleaseDraft } from "./gh.js";
6
6
  import { checkGitClean, commitReleaseArtifacts, createTag, currentBranch, pushRelease, releaseCommitSubject, } from "./git.js";
7
- import { resolveScriptsDir, todayIso } from "./paths.js";
7
+ import { checkVbriefLifecycleSyncNative, refreshRoadmapNative, runBuildNative, } from "./native-steps.js";
8
+ import { todayIso } from "./paths.js";
8
9
  import { runReleaseCheck } from "./preflight.js";
9
- import { pyprojectPathFor, syncPyprojectForRelease } from "./pyproject-sync.js";
10
- import { checkVbriefLifecycleSync, refreshRoadmap, runBuild, runUvLock } from "./python-steps.js";
11
10
  import { isPrereleaseTag } from "./version.js";
12
11
  export function emit(step, label, status, target = process.stderr) {
13
12
  target.write(`[${step}/${TOTAL_STEPS}] ${label}... ${status}\n`);
@@ -17,16 +16,14 @@ export function runPipeline(config, seams = {}) {
17
16
  const version = config.version;
18
17
  const today = (seams.todayIso ?? todayIso)();
19
18
  const changelogPath = join(projectRoot, "CHANGELOG.md");
20
- const scriptsDir = resolveScriptsDir();
21
19
  const readFile = seams.readFile ?? ((p) => readFileSync(p, "utf8"));
22
20
  const writeFile = seams.writeFile ?? ((p, c) => writeFileSync(p, c, "utf8"));
23
21
  const fileExists = seams.fileExists ?? ((p) => existsSync(p));
24
22
  const runCiFn = seams.runCi ?? ((root) => runReleaseCheck(root));
25
- const refreshRoadmapFn = seams.refreshRoadmap ?? ((root) => refreshRoadmap(root, scriptsDir, seams));
23
+ const refreshRoadmapFn = seams.refreshRoadmap ?? ((root) => refreshRoadmapNative(root));
26
24
  const checkVbriefFn = seams.checkVbriefLifecycleSync ??
27
- ((root, repo) => checkVbriefLifecycleSync(root, repo, scriptsDir, seams));
28
- const runBuildFn = seams.runBuild ?? ((root, v) => runBuild(root, scriptsDir, v, seams));
29
- const runUvLockFn = seams.runUvLock ?? ((root) => runUvLock(root, seams));
25
+ ((root, repo) => checkVbriefLifecycleSyncNative(root, repo));
26
+ const runBuildFn = seams.runBuild ?? ((root, v) => runBuildNative(root, v, seams));
30
27
  // Step 1: dirty-tree guard.
31
28
  let label = "Pre-flight git status";
32
29
  if (config.dryRun) {
@@ -123,7 +120,7 @@ export function runPipeline(config, seams = {}) {
123
120
  return EXIT_VIOLATION;
124
121
  }
125
122
  }
126
- // Step 6: CHANGELOG promotion + pyproject sync.
123
+ // Step 6: CHANGELOG promotion.
127
124
  label = "CHANGELOG promotion";
128
125
  if (!fileExists(changelogPath)) {
129
126
  emit(6, label, `FAIL (CHANGELOG.md not found at ${changelogPath})`);
@@ -148,28 +145,12 @@ export function runPipeline(config, seams = {}) {
148
145
  else {
149
146
  summaryNote = " no summary";
150
147
  }
151
- const pyprojectPath = pyprojectPathFor(projectRoot);
152
- const [pyprojectNote, promotedPyproject] = syncPyprojectForRelease(pyprojectPath, version, { dryRun: config.dryRun }, seams);
153
- if (pyprojectNote.startsWith("FAIL")) {
154
- emit(6, label, pyprojectNote);
155
- return EXIT_CONFIG_ERROR;
156
- }
157
148
  if (config.dryRun) {
158
- emit(6, label, `DRYRUN (would rewrite CHANGELOG.md: ## [Unreleased] -> ## [${version}] - ${today}; new compare link added;${summaryNote}; ${pyprojectNote}; would run \`uv lock\` to refresh uv.lock to ${version})`);
149
+ emit(6, label, `DRYRUN (would rewrite CHANGELOG.md: ## [Unreleased] -> ## [${version}] - ${today}; new compare link added;${summaryNote})`);
159
150
  }
160
151
  else {
161
152
  writeFile(changelogPath, promotedChangelog);
162
- let uvLockNote = "uv.lock unchanged (pyproject not modified)";
163
- if (promotedPyproject !== null) {
164
- writeFile(pyprojectPath, promotedPyproject);
165
- const [uvOk, uvNote] = runUvLockFn(projectRoot);
166
- uvLockNote = uvNote;
167
- if (!uvOk) {
168
- emit(6, label, `FAIL (${uvLockNote})`);
169
- return EXIT_VIOLATION;
170
- }
171
- }
172
- emit(6, label, `OK (## [${version}] - ${today};${summaryNote}; ${pyprojectNote}; ${uvLockNote})`);
153
+ emit(6, label, `OK (## [${version}] - ${today};${summaryNote})`);
173
154
  }
174
155
  // Step 7: ROADMAP refresh.
175
156
  label = "ROADMAP refresh (task roadmap:render)";
@@ -1,3 +1,3 @@
1
- /** Invoke release_rollback.main via Python until the TS rollback module lands (#1729). */
1
+ /** Native TypeScript release rollback (#1860). */
2
2
  export declare function rollbackMain(argv: string[]): number;
3
3
  //# sourceMappingURL=rollback-bridge.d.ts.map
@@ -1,20 +1,6 @@
1
- import { resolveScriptsDir } from "../release/paths.js";
2
- import { spawnText } from "../release/spawn.js";
3
- /** Invoke release_rollback.main via Python until the TS rollback module lands (#1729). */
1
+ import { cmdRollback } from "../release-rollback/main.js";
2
+ /** Native TypeScript release rollback (#1860). */
4
3
  export function rollbackMain(argv) {
5
- const scriptsDir = resolveScriptsDir();
6
- const code = [
7
- "import sys",
8
- `sys.path.insert(0, ${JSON.stringify(scriptsDir)})`,
9
- "import release_rollback",
10
- `sys.exit(release_rollback.main(${JSON.stringify(argv)}))`,
11
- ].join("\n");
12
- const frameworkRoot = scriptsDir.replace(/[/\\]scripts$/, "") || process.cwd();
13
- const result = spawnText("uv", ["run", "python", "-c", code], {
14
- cwd: frameworkRoot,
15
- env: { ...process.env, PYTHONUTF8: "1" },
16
- timeoutMs: 300_000,
17
- });
18
- return result.status ?? 1;
4
+ return cmdRollback(argv);
19
5
  }
20
6
  //# sourceMappingURL=rollback-bridge.js.map
@@ -26,8 +26,8 @@ export declare function formatFrameworkCommand(args: readonly string[], options?
26
26
  taskPrefix?: string | null;
27
27
  }): string;
28
28
  export declare function cmdCoreValidate(argv: readonly string[]): number;
29
- export declare function cmdCoreLint(argv: readonly string[]): number;
30
- export declare function cmdCoreTest(argv: readonly string[]): number;
29
+ export declare function cmdCoreLint(_argv: readonly string[]): number;
30
+ export declare function cmdCoreTest(_argv: readonly string[]): number;
31
31
  export declare function resolveFrameworkRoot(): string;
32
32
  export interface RunFrameworkCommandOptions {
33
33
  readonly projectRoot?: string;
@@ -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",
@@ -93,7 +92,7 @@ export const COMMANDS = {
93
92
  vbriefDirArg: "--vbrief-dir",
94
93
  }),
95
94
  build: spec("build", "build_dist:main", {
96
- defaultArgs: ["--version", "__DEFT_VERSION__"],
95
+ defaultArgs: ["--version", "__DEFT_VERSION__", "--root", "__DEFT_ROOT__"],
97
96
  cwd: "framework",
98
97
  }),
99
98
  "check:consumer": aggregate("check:consumer", [
@@ -107,8 +106,6 @@ export const COMMANDS = {
107
106
  ]),
108
107
  "check:framework-source": aggregate("check:framework-source", [
109
108
  "core:validate",
110
- "core:lint",
111
- "core:test",
112
109
  "toolchain:check",
113
110
  "verify:stubs",
114
111
  "verify:links",
@@ -197,49 +194,13 @@ export function cmdCoreValidate(argv) {
197
194
  process.stdout.write(`✓ All ${files.length} markdown files validated\n`);
198
195
  return 0;
199
196
  }
200
- function runUv(args, frameworkRoot) {
201
- try {
202
- execFileSync("uv", ["--project", frameworkRoot, "run", ...args], {
203
- encoding: "utf8",
204
- stdio: "inherit",
205
- });
206
- return 0;
207
- }
208
- catch (err) {
209
- const e = err;
210
- return e.status ?? 1;
211
- }
212
- }
213
- export function cmdCoreLint(argv) {
214
- if (argv.length > 0) {
215
- process.stderr.write(`error: core:lint does not accept arguments: ${argv.join(" ")}\n`);
216
- return 2;
217
- }
218
- const ruffCode = runUv(["ruff", "check", "."], resolveFrameworkRoot());
219
- if (ruffCode !== 0)
220
- return ruffCode;
221
- const targets = ["run.py"];
222
- if (existsSync("tests"))
223
- targets.push("tests");
224
- 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;
225
200
  }
226
- export function cmdCoreTest(argv) {
227
- if (argv.length > 0) {
228
- process.stderr.write(`error: core:test does not accept arguments: ${argv.join(" ")}\n`);
229
- return 2;
230
- }
231
- if (!existsSync("tests")) {
232
- process.stdout.write("no tests/ (vendored consumer) -- skipping\n");
233
- return 0;
234
- }
235
- try {
236
- execFileSync("python3", ["-m", "pytest", "tests"], { encoding: "utf8", stdio: "inherit" });
237
- return 0;
238
- }
239
- catch (err) {
240
- const e = err;
241
- return e.status ?? 1;
242
- }
201
+ export function cmdCoreTest(_argv) {
202
+ process.stderr.write("error: core:test removed (#1860); use ts:check-lane\n");
203
+ return 2;
243
204
  }
244
205
  function resolveVersion() {
245
206
  try {
@@ -259,7 +220,12 @@ export function resolveFrameworkRoot() {
259
220
  function argvForSpec(commandSpec, argv, projectRoot, frameworkRoot) {
260
221
  const resolved = [];
261
222
  for (const item of commandSpec.defaultArgs ?? []) {
262
- resolved.push(item === "__DEFT_VERSION__" ? resolveVersion() : item);
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);
263
229
  }
264
230
  if (commandSpec.projectRootArg)
265
231
  resolved.push(commandSpec.projectRootArg, projectRoot);
@@ -272,44 +238,81 @@ function argvForSpec(commandSpec, argv, projectRoot, frameworkRoot) {
272
238
  resolved.push(...normalizeTaskSeparator(argv));
273
239
  return resolved;
274
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
+ }
275
267
  const TS_INLINE = {
276
268
  "framework_commands:_cmd_core_validate": (argv) => cmdCoreValidate(argv),
277
- "framework_commands:_cmd_core_lint": (argv) => cmdCoreLint(argv),
278
- "framework_commands:_cmd_core_test": (argv) => cmdCoreTest(argv),
279
269
  "doctor:cmd_doctor": (argv) => cmdDoctor(argv),
270
+ "build_dist:main": (argv) => runBuildDistArgv(argv),
280
271
  };
281
- function spawnPythonEntrypoint(entrypoint, argv, cwd, frameworkRoot, noArgv) {
282
- const scriptsDir = join(frameworkRoot, "scripts").replace(/\\/g, "/");
283
- const code = [
284
- "import sys, importlib, importlib.util, inspect, os",
285
- `sys.path.insert(0, ${JSON.stringify(scriptsDir)})`,
286
- `os.chdir(${JSON.stringify(cwd.replace(/\\/g, "/"))})`,
287
- `entrypoint = ${JSON.stringify(entrypoint)}`,
288
- "module_ref, _, func_name = entrypoint.partition(':')",
289
- "if module_ref.endswith('.py'):",
290
- " path = __import__('pathlib').Path(module_ref)",
291
- " if not path.is_absolute():",
292
- ` path = __import__('pathlib').Path(${JSON.stringify(scriptsDir)}) / module_ref`,
293
- " module_name = '_deft_cmd_' + path.stem.replace('-', '_')",
294
- " spec = importlib.util.spec_from_file_location(module_name, path)",
295
- " mod = importlib.util.module_from_spec(spec)",
296
- " spec.loader.exec_module(mod)",
297
- "else:",
298
- " mod = importlib.import_module(module_ref)",
299
- "func = getattr(mod, func_name)",
300
- `argv = ${JSON.stringify(argv)}`,
301
- "if not callable(func):",
302
- " raise TypeError('not callable')",
303
- noArgv
304
- ? "code = func() if len(inspect.signature(func).parameters) == 0 else func([])"
305
- : "code = func() if len(inspect.signature(func).parameters) == 0 else func(argv)",
306
- "sys.exit(int(code or 0))",
307
- ].join("\n");
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
+ }
308
311
  try {
309
- const stdout = execFileSync("uv", ["--project", frameworkRoot, "run", "python", "-c", code], {
310
- cwd: frameworkRoot,
312
+ const stdout = execFileSync(process.execPath, [bin, verb, ...argv], {
313
+ cwd,
311
314
  encoding: "utf8",
312
- env: { ...process.env, PYTHONUTF8: "1", DEFT_CACHE_DISABLE: "1" },
315
+ env: { ...process.env, DEFT_CACHE_DISABLE: "1" },
313
316
  });
314
317
  return { code: 0, stdout: typeof stdout === "string" ? stdout : "", stderr: "" };
315
318
  }
@@ -349,7 +352,23 @@ function invokeEntrypoint(entrypoint, argv, cwd, frameworkRoot, noArgv, capture)
349
352
  }
350
353
  return { code: inline(argv), stdout: "", stderr: "" };
351
354
  }
352
- return spawnPythonEntrypoint(entrypoint, argv, cwd, frameworkRoot, noArgv);
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
+ };
353
372
  }
354
373
  export function runFrameworkCommand(name, argv = [], options = {}) {
355
374
  const root = resolve(options.projectRoot ?? process.cwd());
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Deterministic gate: assert that Cursor is enumerated as a Tier-1 descriptor in
3
+ * both the swarm Phase 3 capability matrix and the review-cycle monitoring
4
+ * tier-selection table (#1877). Without this gate the "Cursor -> Tier 1" mapping
5
+ * is prose-trusted; a doc edit that drops the Cursor descriptor would silently
6
+ * re-open the misclassification (Cursor falling through to a blocking poll).
7
+ */
8
+ export interface CursorTier1Target {
9
+ /** Project-root-relative path to the skill file. */
10
+ readonly path: string;
11
+ /** Human label for the surface, used in failure output. */
12
+ readonly label: string;
13
+ /** Whitespace-normalized substrings that MUST all be present. */
14
+ readonly markers: readonly string[];
15
+ }
16
+ export declare const CURSOR_TIER1_TARGETS: readonly CursorTier1Target[];
17
+ /** Collapse all runs of whitespace to a single space (substring-containment normalization). */
18
+ export declare function normalizeWhitespace(text: string): string;
19
+ export interface CursorTier1Finding {
20
+ readonly path: string;
21
+ readonly label: string;
22
+ readonly missingMarkers: readonly string[];
23
+ }
24
+ export interface CursorTier1Result {
25
+ readonly code: 0 | 1 | 2;
26
+ readonly findings: readonly CursorTier1Finding[];
27
+ readonly message: string;
28
+ readonly stream: "stdout" | "stderr";
29
+ }
30
+ export interface CursorTier1Options {
31
+ readonly targets?: readonly CursorTier1Target[];
32
+ readonly quiet?: boolean;
33
+ }
34
+ export declare function evaluateCursorTier1(projectRoot: string, options?: CursorTier1Options): CursorTier1Result;
35
+ //# sourceMappingURL=cursor-tier1.d.ts.map
@@ -0,0 +1,94 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ export const CURSOR_TIER1_TARGETS = [
4
+ {
5
+ path: "content/skills/deft-directive-swarm/SKILL.md",
6
+ label: "swarm Phase 3 capability matrix",
7
+ markers: [
8
+ "Probe for the Cursor `Task` tool",
9
+ "cursor-composer",
10
+ "cursor-cloud-agent",
11
+ "Step 2e: Cursor Launch",
12
+ ],
13
+ },
14
+ {
15
+ path: "content/skills/deft-directive-review-cycle/SKILL.md",
16
+ label: "review-cycle monitoring tier selection",
17
+ markers: [
18
+ "cursor-composer",
19
+ "cursor-cloud-agent",
20
+ "Tier 1 with the backgrounded Cursor `Task` poller path",
21
+ "Heartbeat contract for Cursor pollers",
22
+ ],
23
+ },
24
+ ];
25
+ /** Collapse all runs of whitespace to a single space (substring-containment normalization). */
26
+ export function normalizeWhitespace(text) {
27
+ return text.replace(/\s+/g, " ");
28
+ }
29
+ export function evaluateCursorTier1(projectRoot, options = {}) {
30
+ const root = resolve(projectRoot);
31
+ let isDir = false;
32
+ try {
33
+ isDir = statSync(root).isDirectory();
34
+ }
35
+ catch {
36
+ isDir = false;
37
+ }
38
+ if (!isDir) {
39
+ return {
40
+ code: 2,
41
+ findings: [],
42
+ message: `verify_cursor_tier1: --project-root is not a directory: ${root}\n` +
43
+ " Recovery: pass an existing directory path.",
44
+ stream: "stderr",
45
+ };
46
+ }
47
+ const targets = options.targets ?? CURSOR_TIER1_TARGETS;
48
+ const findings = [];
49
+ for (const target of targets) {
50
+ const full = join(root, target.path);
51
+ if (!existsSync(full)) {
52
+ return {
53
+ code: 2,
54
+ findings: [...findings],
55
+ message: `verify_cursor_tier1: required skill file not found: ${target.path}\n` +
56
+ " Recovery: run from the framework source root, or update CURSOR_TIER1_TARGETS if the skill moved.",
57
+ stream: "stderr",
58
+ };
59
+ }
60
+ let normalized;
61
+ try {
62
+ normalized = normalizeWhitespace(readFileSync(full, { encoding: "utf8" }));
63
+ }
64
+ catch (err) {
65
+ const msg = err instanceof Error ? err.message : String(err);
66
+ return {
67
+ code: 2,
68
+ findings: [...findings],
69
+ message: `verify_cursor_tier1: could not read ${target.path}: ${msg}`,
70
+ stream: "stderr",
71
+ };
72
+ }
73
+ const missing = target.markers.filter((marker) => !normalized.includes(normalizeWhitespace(marker)));
74
+ if (missing.length > 0) {
75
+ findings.push({ path: target.path, label: target.label, missingMarkers: missing });
76
+ }
77
+ }
78
+ if (findings.length > 0) {
79
+ const header = "verify_cursor_tier1: Cursor is not fully enumerated as a Tier-1 descriptor (#1877).\n" +
80
+ " Root cause: a Cursor agent has a first-class backgroundable sub-agent primitive (the Task tool) and\n" +
81
+ " is therefore Tier 1 / Approach 1. If the matrices below drop the Cursor descriptor, a Cursor session\n" +
82
+ " silently degrades to the Approach-3 blocking poll. Re-add the missing marker(s):";
83
+ const body = findings
84
+ .map((f) => ` ${f.path} (${f.label}) missing: ${f.missingMarkers.map((m) => `"${m}"`).join(", ")}`)
85
+ .join("\n");
86
+ return { code: 1, findings, message: `${header}\n${body}`, stream: "stderr" };
87
+ }
88
+ const msg = `verify_cursor_tier1: Cursor enumerated as a Tier-1 descriptor in ${targets.length} matrix surface(s) (#1877).`;
89
+ if (options.quiet) {
90
+ return { code: 0, findings: [], message: "", stream: "stdout" };
91
+ }
92
+ return { code: 0, findings: [], message: msg, stream: "stdout" };
93
+ }
94
+ //# sourceMappingURL=cursor-tier1.js.map
@@ -1,5 +1,6 @@
1
1
  export * from "./code-structure-validate.js";
2
2
  export * from "./content-manifest.js";
3
+ export * from "./cursor-tier1.js";
3
4
  export * from "./python-call-scan.js";
4
5
  export * from "./rule-ownership-lint.js";
5
6
  export * from "./scm-boundary.js";
@@ -1,5 +1,6 @@
1
1
  export * from "./code-structure-validate.js";
2
2
  export * from "./content-manifest.js";
3
+ export * from "./cursor-tier1.js";
3
4
  export * from "./python-call-scan.js";
4
5
  export * from "./rule-ownership-lint.js";
5
6
  export * from "./scm-boundary.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -229,11 +229,15 @@
229
229
  "provenance": true
230
230
  },
231
231
  "dependencies": {
232
- "@deftai/directive-content": "^0.62.0",
233
- "@deftai/directive-types": "^0.62.0"
232
+ "@deftai/directive-content": "^0.64.0",
233
+ "@deftai/directive-types": "^0.64.0",
234
+ "archiver": "^8.0.0"
234
235
  },
235
236
  "scripts": {
236
237
  "build": "tsc -b",
237
238
  "test": "pnpm -w exec vitest run --coverage packages/core/src"
239
+ },
240
+ "devDependencies": {
241
+ "@types/archiver": "^8.0.0"
238
242
  }
239
243
  }
@@ -1,6 +0,0 @@
1
- import type { ReleaseSeams } from "./types.js";
2
- export declare function syncPyprojectForRelease(pyprojectPath: string, version: string, options: {
3
- dryRun: boolean;
4
- }, seams?: ReleaseSeams): [string, string | null];
5
- export declare function pyprojectPathFor(projectRoot: string): string;
6
- //# sourceMappingURL=pyproject-sync.d.ts.map
@@ -1,52 +0,0 @@
1
- import { existsSync, readFileSync, statSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { updatePyprojectVersion } from "./pyproject.js";
4
- import { NonPublishableVersionError, toPep440 } from "./version.js";
5
- export function syncPyprojectForRelease(pyprojectPath, version, options, seams = {}) {
6
- const exists = seams.fileExists ??
7
- ((p) => {
8
- try {
9
- return existsSync(p) && statSync(p).isFile();
10
- }
11
- catch {
12
- return false;
13
- }
14
- });
15
- const read = seams.readFile ?? ((p) => readFileSync(p, "utf8"));
16
- if (!exists(pyprojectPath)) {
17
- return ["no pyproject.toml; skipping sync", null];
18
- }
19
- let pepVersion;
20
- try {
21
- pepVersion = toPep440(version);
22
- }
23
- catch (err) {
24
- if (err instanceof NonPublishableVersionError) {
25
- return [`non-publishable tag (${err.message}); skipping pyproject sync`, null];
26
- }
27
- if (err instanceof Error) {
28
- return [`FAIL (cannot normalize version to PEP 440: ${err.message})`, null];
29
- }
30
- return [`FAIL (cannot normalize version to PEP 440: ${String(err)})`, null];
31
- }
32
- const original = read(pyprojectPath);
33
- let newText;
34
- try {
35
- newText = updatePyprojectVersion(original, pepVersion);
36
- }
37
- catch (err) {
38
- const msg = err instanceof Error ? err.message : String(err);
39
- return [`FAIL (pyproject.toml: ${msg})`, null];
40
- }
41
- if (newText === original) {
42
- return [`pyproject already at ${pepVersion}`, null];
43
- }
44
- if (options.dryRun) {
45
- return [`pyproject [project].version -> ${pepVersion}`, null];
46
- }
47
- return [`pyproject [project].version -> ${pepVersion}`, newText];
48
- }
49
- export function pyprojectPathFor(projectRoot) {
50
- return join(projectRoot, "pyproject.toml");
51
- }
52
- //# sourceMappingURL=pyproject-sync.js.map
@@ -1,3 +0,0 @@
1
- /** Rewrite [project].version in pyproject.toml content (#771). */
2
- export declare function updatePyprojectVersion(text: string, version: string): string;
3
- //# sourceMappingURL=pyproject.d.ts.map
@@ -1,33 +0,0 @@
1
- import { PYPROJECT_VERSION_LINE_RE } from "./constants.js";
2
- /** Rewrite [project].version in pyproject.toml content (#771). */
3
- export function updatePyprojectVersion(text, version) {
4
- if (typeof text !== "string") {
5
- throw new Error(`text must be a string, got ${typeof text}`);
6
- }
7
- if (typeof version !== "string" || !version.trim()) {
8
- throw new Error("version must be a non-empty string");
9
- }
10
- const lines = text.split(/(?<=\n)/);
11
- let inProjectSection = false;
12
- for (let idx = 0; idx < lines.length; idx += 1) {
13
- const line = lines[idx] ?? "";
14
- const stripped = line.trim();
15
- if (!stripped || stripped.startsWith("#")) {
16
- continue;
17
- }
18
- if (stripped.startsWith("[") && stripped.endsWith("]")) {
19
- inProjectSection = stripped === "[project]";
20
- continue;
21
- }
22
- if (inProjectSection && PYPROJECT_VERSION_LINE_RE.test(stripped)) {
23
- const newLine = line.replace(PYPROJECT_VERSION_LINE_RE, `version = "${version}"`);
24
- if (newLine === line) {
25
- return text;
26
- }
27
- lines[idx] = newLine;
28
- return lines.join("");
29
- }
30
- }
31
- throw new Error("pyproject.toml has no [project] section with a version key");
32
- }
33
- //# sourceMappingURL=pyproject.js.map
@@ -1,6 +0,0 @@
1
- import type { ReleaseSeams } from "./types.js";
2
- export declare function refreshRoadmap(projectRoot: string, scriptsDir: string, seams?: ReleaseSeams): [boolean, string];
3
- export declare function checkVbriefLifecycleSync(projectRoot: string, repo: string, scriptsDir: string, seams?: ReleaseSeams): [boolean, number, string];
4
- export declare function runBuild(projectRoot: string, scriptsDir: string, version: string | null, seams?: ReleaseSeams): [boolean, string];
5
- export declare function runUvLock(projectRoot: string, seams?: ReleaseSeams): [boolean, string];
6
- //# sourceMappingURL=python-steps.d.ts.map
@@ -1,143 +0,0 @@
1
- /**
2
- * python-steps.ts -- Residual Python-backed release pipeline steps.
3
- *
4
- * #2022 Phase 1 removed the `ci_local.py` Step-5 pre-flight bridge (now native
5
- * TypeScript via release/preflight.ts), retiring the former `python-bridge.ts`
6
- * module. These remaining steps still shell into bundled Python and stay here
7
- * until their own purge phases land:
8
- * - refreshRoadmap (Step 7) -> scripts/roadmap_render.py
9
- * - checkVbriefLifecycleSync (Step 3) -> scripts/reconcile_issues.py
10
- * - runBuild (Step 8) -> scripts/build_dist.py (Bucket B, deleted by #1860)
11
- * - runUvLock (Step 6) -> `uv lock`
12
- */
13
- import { existsSync, statSync } from "node:fs";
14
- import { join } from "node:path";
15
- import { defaultWhich, spawnText } from "./spawn.js";
16
- function runUvPython(_scriptsDir, code, cwd, env = process.env, seams = {}) {
17
- const spawn = seams.spawnText ?? spawnText;
18
- return spawn("uv", ["run", "python", "-c", code], {
19
- cwd,
20
- env: { ...env, PYTHONUTF8: "1" },
21
- timeoutMs: 300_000,
22
- });
23
- }
24
- export function refreshRoadmap(projectRoot, scriptsDir, seams = {}) {
25
- const pending = join(projectRoot, "vbrief", "pending");
26
- const roadmap = join(projectRoot, "ROADMAP.md");
27
- const completed = join(projectRoot, "vbrief", "completed");
28
- const code = [
29
- "import sys",
30
- `sys.path.insert(0, ${JSON.stringify(scriptsDir)})`,
31
- "import roadmap_render",
32
- `ok, msg = roadmap_render.render_roadmap(${JSON.stringify(pending)}, ${JSON.stringify(roadmap)}, completed_dir=${JSON.stringify(completed)})`,
33
- "if ok:",
34
- " sys.exit(0)",
35
- "print(msg, file=sys.stderr)",
36
- "sys.exit(1)",
37
- ].join("\n");
38
- const result = runUvPython(scriptsDir, code, projectRoot, process.env, seams);
39
- if (result.status !== 0) {
40
- const msg = result.stderr.trim() || result.stdout.trim();
41
- return [false, `roadmap:render failed: ${msg}`];
42
- }
43
- return [true, "ROADMAP.md re-rendered"];
44
- }
45
- export function checkVbriefLifecycleSync(projectRoot, repo, scriptsDir, seams = {}) {
46
- const code = [
47
- "import json, sys",
48
- "from pathlib import Path",
49
- `scripts_dir = Path(${JSON.stringify(scriptsDir)})`,
50
- "sys.path.insert(0, str(scripts_dir))",
51
- "try:",
52
- " import reconcile_issues",
53
- "except ImportError as exc:",
54
- " print(json.dumps({'ok': False, 'mismatch_count': -1, 'reason': f'reconcile_issues import failed: {exc}'}))",
55
- " sys.exit(0)",
56
- `project_root = Path(${JSON.stringify(projectRoot)})`,
57
- `repo = ${JSON.stringify(repo)}`,
58
- "vbrief_dir = project_root / 'vbrief'",
59
- "if not vbrief_dir.is_dir():",
60
- " print(json.dumps({'ok': False, 'mismatch_count': -1, 'reason': f'vbrief directory not found at {vbrief_dir}'}))",
61
- " sys.exit(0)",
62
- "issue_to_vbriefs = reconcile_issues.scan_vbrief_dir(vbrief_dir)",
63
- "issue_state_map = reconcile_issues.fetch_issue_states(repo, set(issue_to_vbriefs.keys()), cwd=project_root)",
64
- "if issue_state_map is None:",
65
- " print(json.dumps({'ok': False, 'mismatch_count': -1, 'reason': 'failed to fetch issue states from gh'}))",
66
- " sys.exit(0)",
67
- "report = reconcile_issues.reconcile(issue_to_vbriefs, issue_state_map)",
68
- "mismatches = [rel for entry in report.get('no_open_issue', []) for rel in entry.get('vbrief_files', []) if not reconcile_issues.is_terminal_lifecycle_path(rel)]",
69
- "count = len(mismatches)",
70
- "if count == 0:",
71
- " print(json.dumps({'ok': True, 'mismatch_count': 0, 'reason': 'no mismatches'}))",
72
- "else:",
73
- " suffix = ' ...' if count > 5 else ''",
74
- " preview = ', '.join(mismatches[:5])",
75
- " reason = f'{count} closed-issue vBRIEF(s) not in completed/ or cancelled/: {preview}{suffix}'",
76
- " print(json.dumps({'ok': False, 'mismatch_count': count, 'reason': reason}))",
77
- ].join("\n");
78
- const result = runUvPython(scriptsDir, code, projectRoot, process.env, seams);
79
- try {
80
- const payload = JSON.parse(result.stdout.trim());
81
- return [payload.ok, payload.mismatch_count, payload.reason];
82
- }
83
- catch {
84
- return [
85
- false,
86
- -1,
87
- `reconcile_issues bridge failed: ${result.stderr.trim() || result.stdout.trim()}`,
88
- ];
89
- }
90
- }
91
- export function runBuild(projectRoot, scriptsDir, version, seams = {}) {
92
- const env = { ...process.env };
93
- if (version) {
94
- env.DEFT_RELEASE_VERSION = version;
95
- }
96
- else {
97
- delete env.DEFT_RELEASE_VERSION;
98
- }
99
- const code = [
100
- "import sys",
101
- "from pathlib import Path",
102
- `sys.path.insert(0, ${JSON.stringify(scriptsDir)})`,
103
- "from framework_commands import run_framework_command",
104
- `root = Path(${JSON.stringify(projectRoot)})`,
105
- "result = run_framework_command('build', project_root=root, framework_root=root)",
106
- "sys.exit(result.code)",
107
- ].join("\n");
108
- const result = runUvPython(scriptsDir, code, projectRoot, env, seams);
109
- if (result.status !== 0) {
110
- return [false, `build failed (exit ${result.status})`];
111
- }
112
- const suffix = version ? ` (DEFT_RELEASE_VERSION=${version})` : "";
113
- return [true, `build ran clean${suffix}`];
114
- }
115
- export function runUvLock(projectRoot, seams = {}) {
116
- const exists = seams.fileExists ??
117
- ((p) => {
118
- try {
119
- return existsSync(p) && statSync(p).isFile();
120
- }
121
- catch {
122
- return false;
123
- }
124
- });
125
- if (!exists(join(projectRoot, "pyproject.toml"))) {
126
- return [true, "no pyproject.toml; skipping uv lock"];
127
- }
128
- const whichUv = seams.whichUv ?? defaultWhich;
129
- const uvPath = whichUv("uv");
130
- if (uvPath === null) {
131
- process.stderr.write("WARNING: uv binary not on PATH; skipping uv.lock regeneration " +
132
- "(see #774). Run `uv lock` manually before pushing the release tag.\n");
133
- return [true, "uv binary not on PATH; skipping uv lock"];
134
- }
135
- const spawn = seams.spawnText ?? spawnText;
136
- const result = spawn(uvPath, ["lock"], { cwd: projectRoot, timeoutMs: 300_000 });
137
- if (result.status !== 0) {
138
- const stderr = result.stderr.trim();
139
- return [false, `uv lock failed (exit ${result.status}): ${stderr}`];
140
- }
141
- return [true, "uv.lock regenerated"];
142
- }
143
- //# sourceMappingURL=python-steps.js.map