@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.
Files changed (33) hide show
  1. package/dist/check-updates/index.d.ts +49 -0
  2. package/dist/check-updates/index.js +323 -0
  3. package/dist/install-upgrade/index.js +3 -3
  4. package/dist/migrate-preflight/index.d.ts +1 -2
  5. package/dist/migrate-preflight/index.js +16 -36
  6. package/dist/release/build-dist-runner.d.ts +2 -0
  7. package/dist/release/build-dist-runner.js +21 -0
  8. package/dist/release/build-dist.d.ts +14 -0
  9. package/dist/release/build-dist.js +171 -0
  10. package/dist/release/constants.d.ts +1 -1
  11. package/dist/release/constants.js +1 -6
  12. package/dist/release/index.d.ts +2 -3
  13. package/dist/release/index.js +2 -3
  14. package/dist/release/native-steps.d.ts +5 -0
  15. package/dist/release/native-steps.js +72 -0
  16. package/dist/release/pipeline.js +8 -27
  17. package/dist/release-e2e/rollback-bridge.d.ts +1 -1
  18. package/dist/release-e2e/rollback-bridge.js +3 -17
  19. package/dist/render/framework-commands.d.ts +2 -2
  20. package/dist/render/framework-commands.js +102 -84
  21. package/dist/umbrella-current-shape/index.d.ts +87 -0
  22. package/dist/umbrella-current-shape/index.js +329 -0
  23. package/dist/vbrief-validate/precutover.d.ts +3 -0
  24. package/dist/vbrief-validate/precutover.js +8 -1
  25. package/dist/verify-env/verify-hooks-installed.d.ts +2 -0
  26. package/dist/verify-env/verify-hooks-installed.js +20 -4
  27. package/package.json +15 -3
  28. package/dist/release/pyproject-sync.d.ts +0 -6
  29. package/dist/release/pyproject-sync.js +0 -52
  30. package/dist/release/pyproject.d.ts +0 -3
  31. package/dist/release/pyproject.js +0 -33
  32. package/dist/release/python-steps.d.ts +0 -6
  33. package/dist/release/python-steps.js +0 -143
@@ -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;