@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.
- 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 -83
- package/dist/verify-source/cursor-tier1.d.ts +35 -0
- package/dist/verify-source/cursor-tier1.js +94 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/package.json +7 -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
|
@@ -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"
|
|
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*"[^"]*"/;
|
package/dist/release/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/release/index.js
CHANGED
|
@@ -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
|
package/dist/release/pipeline.js
CHANGED
|
@@ -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 {
|
|
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) =>
|
|
23
|
+
const refreshRoadmapFn = seams.refreshRoadmap ?? ((root) => refreshRoadmapNative(root));
|
|
26
24
|
const checkVbriefFn = seams.checkVbriefLifecycleSync ??
|
|
27
|
-
((root, repo) =>
|
|
28
|
-
const runBuildFn = seams.runBuild ?? ((root, v) =>
|
|
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
|
|
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}
|
|
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
|
-
|
|
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,20 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
|
|
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(
|
|
30
|
-
export declare function cmdCoreTest(
|
|
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
|
|
201
|
-
|
|
202
|
-
|
|
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(
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
310
|
-
cwd
|
|
312
|
+
const stdout = execFileSync(process.execPath, [bin, verb, ...argv], {
|
|
313
|
+
cwd,
|
|
311
314
|
encoding: "utf8",
|
|
312
|
-
env: { ...process.env,
|
|
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
|
-
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive-core",
|
|
3
|
-
"version": "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.
|
|
233
|
-
"@deftai/directive-types": "^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,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
|