@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,49 @@
1
+ /** Default subprocess timeout for `git ls-remote` (seconds). */
2
+ export declare const REMOTE_PROBE_DEFAULT_TIMEOUT = 5;
3
+ /** Baked-in canonical upstream (#1320). Never probe consumer origin. */
4
+ export declare const DEFT_UPSTREAM_URL = "https://github.com/deftai/directive.git";
5
+ /** Reject git ls-remote targets that could be parsed as options (#CodeQL). */
6
+ export declare function isSafeGitLsRemoteTarget(url: string): boolean;
7
+ /** Return a trimmed upstream URL safe for `git ls-remote`, or null when rejected. */
8
+ export declare function sanitizeGitLsRemoteTarget(url: string): string | null;
9
+ export type RemoteProbeStatus = "ok" | "behind" | "skipped" | "no-upstream" | "no-tags" | "error";
10
+ export interface RemoteProbeResult {
11
+ readonly status: RemoteProbeStatus;
12
+ readonly current: string;
13
+ readonly remote?: string;
14
+ readonly upstream_url?: string;
15
+ readonly reason?: string;
16
+ readonly error?: string;
17
+ }
18
+ export type SemverKey = readonly [number, number, number, number, string];
19
+ export interface GitRunner {
20
+ lsRemoteTags(upstreamUrl: string, timeoutMs: number): string[] | "timeout" | "os-error";
21
+ }
22
+ export declare function parseSemverTag(tag: string): SemverKey | null;
23
+ export declare function maxSemverTag(tags: readonly string[]): string | null;
24
+ export declare function resolveProbeCurrentVersion(projectRoot: string, frameworkRoot?: string): string;
25
+ export declare function resolveUpstreamUrl(projectRoot: string): string;
26
+ export declare function resolveProbeTimeout(env?: NodeJS.ProcessEnv): number;
27
+ export declare function noNetworkActive(env?: NodeJS.ProcessEnv): boolean;
28
+ export declare function parseLsRemoteTags(stdout: string): string[];
29
+ export declare function defaultGitRunner(): GitRunner;
30
+ export interface RunRemoteProbeOptions {
31
+ readonly projectRoot: string;
32
+ readonly frameworkRoot?: string;
33
+ readonly env?: NodeJS.ProcessEnv;
34
+ readonly git?: GitRunner;
35
+ }
36
+ export declare function runRemoteProbe(options: RunRemoteProbeOptions): RemoteProbeResult;
37
+ export interface EmitCheckUpdatesOptions {
38
+ readonly jsonMode: boolean;
39
+ readonly writeOut: (text: string) => void;
40
+ }
41
+ export declare function emitCheckUpdates(result: RemoteProbeResult, options: EmitCheckUpdatesOptions): number;
42
+ export declare function runCheckUpdates(argv: readonly string[], options?: {
43
+ projectRoot?: string;
44
+ frameworkRoot?: string;
45
+ env?: NodeJS.ProcessEnv;
46
+ git?: GitRunner;
47
+ writeOut?: (text: string) => void;
48
+ }): number;
49
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,323 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
5
+ import { resolveVersion } from "../doctor/paths.js";
6
+ /** Default subprocess timeout for `git ls-remote` (seconds). */
7
+ export const REMOTE_PROBE_DEFAULT_TIMEOUT = 5.0;
8
+ /** Baked-in canonical upstream (#1320). Never probe consumer origin. */
9
+ export const DEFT_UPSTREAM_URL = "https://github.com/deftai/directive.git";
10
+ const MANIFEST_UPSTREAM_URL_KEYS = [
11
+ "source_url",
12
+ "url",
13
+ "upstream_url",
14
+ "upstream",
15
+ "origin",
16
+ ];
17
+ /** Reject git ls-remote targets that could be parsed as options (#CodeQL). */
18
+ export function isSafeGitLsRemoteTarget(url) {
19
+ return sanitizeGitLsRemoteTarget(url) !== null;
20
+ }
21
+ /** Return a trimmed upstream URL safe for `git ls-remote`, or null when rejected. */
22
+ export function sanitizeGitLsRemoteTarget(url) {
23
+ const trimmed = url.trim();
24
+ if (!trimmed || trimmed.startsWith("-")) {
25
+ return null;
26
+ }
27
+ if (/^https?:\/\/\S+$/i.test(trimmed)) {
28
+ return trimmed;
29
+ }
30
+ if (/^git@[\w.-]+:[\w./-]+\.git$/i.test(trimmed)) {
31
+ return trimmed;
32
+ }
33
+ return null;
34
+ }
35
+ function normalizePrereleaseForSort(pre) {
36
+ const dot = pre.indexOf(".");
37
+ if (dot <= 0) {
38
+ return pre;
39
+ }
40
+ const kind = pre.slice(0, dot).toLowerCase();
41
+ if (kind !== "alpha" && kind !== "beta" && kind !== "rc") {
42
+ return pre;
43
+ }
44
+ const afterKind = pre.slice(dot + 1);
45
+ let numEnd = 0;
46
+ while (numEnd < afterKind.length) {
47
+ const ch = afterKind[numEnd];
48
+ if (ch === undefined || ch < "0" || ch > "9") {
49
+ break;
50
+ }
51
+ numEnd += 1;
52
+ }
53
+ if (numEnd === 0) {
54
+ return pre;
55
+ }
56
+ const padded = afterKind.slice(0, numEnd).padStart(8, "0");
57
+ return `${kind}.${padded}${afterKind.slice(numEnd)}`;
58
+ }
59
+ const SEMVER_TAG_RE = /^v?(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?<pre>-[\w][\w.-]*)?$/;
60
+ export function parseSemverTag(tag) {
61
+ if (!tag) {
62
+ return null;
63
+ }
64
+ const match = SEMVER_TAG_RE.exec(tag.trim());
65
+ if (!match?.groups) {
66
+ return null;
67
+ }
68
+ const pre = match.groups.pre ?? "";
69
+ const preStripped = pre.replace(/^-/, "");
70
+ return [
71
+ Number(match.groups.major),
72
+ Number(match.groups.minor),
73
+ Number(match.groups.patch),
74
+ pre ? 0 : 1,
75
+ normalizePrereleaseForSort(preStripped),
76
+ ];
77
+ }
78
+ export function maxSemverTag(tags) {
79
+ const parsed = [];
80
+ for (const tag of tags) {
81
+ const key = parseSemverTag(tag);
82
+ if (key !== null) {
83
+ parsed.push({ key, tag });
84
+ }
85
+ }
86
+ if (parsed.length === 0) {
87
+ return null;
88
+ }
89
+ parsed.sort((a, b) => {
90
+ for (let i = 0; i < a.key.length; i += 1) {
91
+ const av = a.key[i];
92
+ const bv = b.key[i];
93
+ if (typeof av === "number" && typeof bv === "number" && av !== bv) {
94
+ return av - bv;
95
+ }
96
+ if (typeof av === "string" && typeof bv === "string" && av !== bv) {
97
+ return av < bv ? -1 : 1;
98
+ }
99
+ }
100
+ return 0;
101
+ });
102
+ return parsed[parsed.length - 1]?.tag ?? null;
103
+ }
104
+ function readVendoredManifest(projectRoot) {
105
+ for (const candidate of [
106
+ join(projectRoot, ".deft", "core", "VERSION"),
107
+ join(projectRoot, ".deft", "VERSION"),
108
+ join(projectRoot, "deft", "VERSION"),
109
+ ]) {
110
+ if (!existsSync(candidate)) {
111
+ continue;
112
+ }
113
+ try {
114
+ return parseInstallManifest(readFileSync(candidate, "utf8"));
115
+ }
116
+ catch { }
117
+ }
118
+ return null;
119
+ }
120
+ export function resolveProbeCurrentVersion(projectRoot, frameworkRoot) {
121
+ const manifest = readVendoredManifest(projectRoot);
122
+ if (manifest) {
123
+ const derived = manifestTagToVersion(manifest);
124
+ if (derived) {
125
+ return derived;
126
+ }
127
+ }
128
+ return resolveVersion(frameworkRoot).replace(/^v/, "");
129
+ }
130
+ export function resolveUpstreamUrl(projectRoot) {
131
+ const manifest = readVendoredManifest(projectRoot);
132
+ if (manifest) {
133
+ for (const key of MANIFEST_UPSTREAM_URL_KEYS) {
134
+ const value = manifest[key];
135
+ if (typeof value === "string" && value.trim()) {
136
+ const safe = sanitizeGitLsRemoteTarget(value);
137
+ if (safe) {
138
+ return safe;
139
+ }
140
+ }
141
+ }
142
+ }
143
+ return DEFT_UPSTREAM_URL;
144
+ }
145
+ export function resolveProbeTimeout(env = process.env) {
146
+ const raw = (env.DEFT_REMOTE_PROBE_TIMEOUT ?? "").trim();
147
+ if (!raw) {
148
+ return REMOTE_PROBE_DEFAULT_TIMEOUT;
149
+ }
150
+ const value = Number(raw);
151
+ if (!Number.isFinite(value) || value <= 0) {
152
+ return REMOTE_PROBE_DEFAULT_TIMEOUT;
153
+ }
154
+ return value;
155
+ }
156
+ export function noNetworkActive(env = process.env) {
157
+ return (env.DEFT_NO_NETWORK ?? "").trim() === "1";
158
+ }
159
+ export function parseLsRemoteTags(stdout) {
160
+ const tags = [];
161
+ for (const line of stdout.split("\n")) {
162
+ const marker = "\trefs/tags/";
163
+ const idx = line.indexOf(marker);
164
+ if (idx < 0) {
165
+ continue;
166
+ }
167
+ const tag = line.slice(idx + marker.length).trim();
168
+ if (tag) {
169
+ tags.push(tag);
170
+ }
171
+ }
172
+ return tags;
173
+ }
174
+ export function defaultGitRunner() {
175
+ return {
176
+ lsRemoteTags(upstreamUrl, timeoutMs) {
177
+ const safeUrl = sanitizeGitLsRemoteTarget(upstreamUrl);
178
+ if (!safeUrl) {
179
+ return "os-error";
180
+ }
181
+ try {
182
+ const stdout = execFileSync("git", ["ls-remote", "--tags", "--refs", "--", safeUrl], {
183
+ encoding: "utf8",
184
+ timeout: timeoutMs,
185
+ });
186
+ return parseLsRemoteTags(stdout);
187
+ }
188
+ catch (exc) {
189
+ if (exc instanceof Error &&
190
+ (exc.message.includes("ETIMEDOUT") || exc.message.includes("timed out"))) {
191
+ return "timeout";
192
+ }
193
+ return "os-error";
194
+ }
195
+ },
196
+ };
197
+ }
198
+ export function runRemoteProbe(options) {
199
+ const env = options.env ?? process.env;
200
+ const current = resolveProbeCurrentVersion(options.projectRoot, options.frameworkRoot);
201
+ if (noNetworkActive(env)) {
202
+ return {
203
+ status: "skipped",
204
+ reason: "DEFT_NO_NETWORK=1",
205
+ current,
206
+ };
207
+ }
208
+ const timeoutSec = resolveProbeTimeout(env);
209
+ const upstreamUrl = resolveUpstreamUrl(options.projectRoot);
210
+ const git = options.git ?? defaultGitRunner();
211
+ const tagsResult = git.lsRemoteTags(upstreamUrl, Math.ceil(timeoutSec * 1000));
212
+ if (tagsResult === "timeout") {
213
+ return {
214
+ status: "error",
215
+ current,
216
+ upstream_url: upstreamUrl,
217
+ error: "timeout",
218
+ };
219
+ }
220
+ if (tagsResult === "os-error") {
221
+ return {
222
+ status: "error",
223
+ current,
224
+ upstream_url: upstreamUrl,
225
+ error: "Error: git unavailable",
226
+ };
227
+ }
228
+ const remoteTag = maxSemverTag(tagsResult);
229
+ if (remoteTag === null) {
230
+ return {
231
+ status: "no-tags",
232
+ current,
233
+ upstream_url: upstreamUrl,
234
+ };
235
+ }
236
+ const remoteKey = parseSemverTag(remoteTag);
237
+ const currentKey = parseSemverTag(current.startsWith("v") ? current : `v${current}`);
238
+ if (currentKey !== null && remoteKey !== null && compareSemverKeys(remoteKey, currentKey) > 0) {
239
+ return {
240
+ status: "behind",
241
+ current,
242
+ remote: remoteTag,
243
+ upstream_url: upstreamUrl,
244
+ };
245
+ }
246
+ return {
247
+ status: "ok",
248
+ current,
249
+ remote: remoteTag,
250
+ upstream_url: upstreamUrl,
251
+ };
252
+ }
253
+ function compareSemverKeys(a, b) {
254
+ for (let i = 0; i < a.length; i += 1) {
255
+ const av = a[i];
256
+ const bv = b[i];
257
+ if (av === bv) {
258
+ continue;
259
+ }
260
+ if (typeof av === "number" && typeof bv === "number") {
261
+ return av - bv;
262
+ }
263
+ return String(av) < String(bv) ? -1 : 1;
264
+ }
265
+ return 0;
266
+ }
267
+ export function emitCheckUpdates(result, options) {
268
+ if (options.jsonMode) {
269
+ const payload = {
270
+ status: result.status,
271
+ current: result.current,
272
+ };
273
+ if (result.remote !== undefined) {
274
+ payload.remote = result.remote;
275
+ }
276
+ if (result.upstream_url !== undefined) {
277
+ payload.upstream_url = result.upstream_url;
278
+ }
279
+ if (result.reason !== undefined) {
280
+ payload.reason = result.reason;
281
+ }
282
+ if (result.error !== undefined) {
283
+ payload.error = result.error;
284
+ }
285
+ options.writeOut(`${JSON.stringify(payload, Object.keys(payload).sort())}\n`);
286
+ }
287
+ else {
288
+ const current = result.current;
289
+ switch (result.status) {
290
+ case "ok":
291
+ options.writeOut(`OK upstream=${result.remote}\n`);
292
+ break;
293
+ case "behind":
294
+ options.writeOut(`BEHIND upstream=${result.remote} current=v${current} commits-behind=unknown\n`);
295
+ break;
296
+ case "skipped":
297
+ options.writeOut(`SKIPPED reason=DEFT_NO_NETWORK current=v${current}\n`);
298
+ break;
299
+ case "no-upstream":
300
+ options.writeOut(`NO-UPSTREAM current=v${current}\n`);
301
+ break;
302
+ case "no-tags":
303
+ options.writeOut(`NO-TAGS current=v${current} upstream=${result.upstream_url ?? ""}\n`);
304
+ break;
305
+ default:
306
+ options.writeOut(`ERROR current=v${current} upstream=${result.upstream_url ?? ""} error=${result.error ?? "unknown"}\n`);
307
+ break;
308
+ }
309
+ }
310
+ return result.status === "behind" ? 1 : 0;
311
+ }
312
+ export function runCheckUpdates(argv, options = {}) {
313
+ const jsonMode = argv.includes("--json");
314
+ const result = runRemoteProbe({
315
+ projectRoot: options.projectRoot ?? process.cwd(),
316
+ frameworkRoot: options.frameworkRoot,
317
+ env: options.env,
318
+ git: options.git,
319
+ });
320
+ const writeOut = options.writeOut ?? ((text) => process.stdout.write(text));
321
+ return emitCheckUpdates(result, { jsonMode, writeOut });
322
+ }
323
+ //# sourceMappingURL=index.js.map
@@ -6,7 +6,7 @@ import { buildInstallManifestText, CANONICAL_INSTALL_ROOT, } from "../init-depos
6
6
  import { agentsRefreshPlan } from "../platform/agents-md.js";
7
7
  import { DEV_FALLBACK } from "../platform/constants.js";
8
8
  import { resolveVersion } from "../platform/resolve-version.js";
9
- import { detectPreCutoverLegacy } from "../vbrief-validate/precutover.js";
9
+ import { detectPreCutoverLegacy, frozenPreCutoverMigrationGuidance, } from "../vbrief-validate/precutover.js";
10
10
  const ENGINE_PACKAGE_FALLBACK = "0.0.0";
11
11
  /**
12
12
  * Resolve the framework version for an upgrade (#2053).
@@ -193,7 +193,7 @@ export function runInstallUpgrade(args, io) {
193
193
  }
194
194
  const legacy = detectPreCutoverLegacy(projectRoot);
195
195
  if (legacy.length > 0) {
196
- io.writeOut(`Pre-v0.20 document model detected (${legacy.join(", ")}). Run \`task migrate:vbrief\` first -- it migrates legacy artifacts and creates the lifecycle folder structure. This command only records the framework version.\n`);
196
+ io.writeOut(`Pre-v0.20 document model detected (${legacy.join(", ")}). ${frozenPreCutoverMigrationGuidance()}\n`);
197
197
  }
198
198
  const vbriefDir = join(projectRoot, "vbrief");
199
199
  const targetDir = existsSync(vbriefDir) && statSync(vbriefDir).isDirectory() ? vbriefDir : projectRoot;
@@ -216,7 +216,7 @@ export function runInstallUpgrade(args, io) {
216
216
  else {
217
217
  io.writeOut(`Updated .deft-version from ${recorded} to ${normalizedVersion}.\n`);
218
218
  }
219
- io.writeOut("If legacy SPECIFICATION.md or PROJECT.md content remains, run `task migrate:vbrief` to complete the upgrade.\n");
219
+ io.writeOut(`If legacy SPECIFICATION.md or PROJECT.md content remains, see UPGRADING.md § Frozen pre-v0.20 document-model migration (#2068).\n`);
220
220
  return runAgentsRefresh(projectRoot, agentsRoot, io);
221
221
  }
222
222
  //# sourceMappingURL=index.js.map
@@ -18,11 +18,10 @@ export type MigratePreflightOutcome = {
18
18
  readonly exitCode: 0 | 1;
19
19
  readonly results: readonly CheckResult[];
20
20
  } | MigratePreflightConfigError;
21
- export declare function checkUv(which?: (cmd: string) => string | null): CheckResult;
22
21
  export declare function checkLayout(deftRoot: string, projectRoot: string): CheckResult;
23
22
  export declare function checkGitClean(projectRoot: string): CheckResult;
24
23
  export declare function checkDocumentModel(projectRoot: string): CheckResult;
25
- export declare function evaluate(deftRoot: string, projectRoot: string, which?: (cmd: string) => string | null): {
24
+ export declare function evaluate(deftRoot: string, projectRoot: string): {
26
25
  exitCode: 0 | 1;
27
26
  results: CheckResult[];
28
27
  };
@@ -1,8 +1,7 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { existsSync, readFileSync, statSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
- import { defaultWhich } from "../scm/binary.js";
5
- import { detectPreCutoverLegacy, isCurrentGeneratedSpecification, isGeneratedSpecificationExport, missingLifecycleFolders, } from "../vbrief-validate/precutover.js";
4
+ import { detectPreCutoverLegacy, frozenPreCutoverMigrationGuidance, isCurrentGeneratedSpecification, isGeneratedSpecificationExport, missingLifecycleFolders, } from "../vbrief-validate/precutover.js";
6
5
  function resolveContentRoot(frameworkRoot) {
7
6
  const nested = join(frameworkRoot, "content");
8
7
  try {
@@ -14,25 +13,7 @@ function resolveContentRoot(frameworkRoot) {
14
13
  }
15
14
  return frameworkRoot;
16
15
  }
17
- export function checkUv(which = defaultWhich) {
18
- if (which("uv") !== null) {
19
- return { name: "uv", status: "PASS", message: "uv is on PATH." };
20
- }
21
- return {
22
- name: "uv",
23
- status: "FAIL",
24
- message: "uv is not on PATH. Install from https://docs.astral.sh/uv/ and re-run.",
25
- };
26
- }
27
16
  export function checkLayout(deftRoot, projectRoot) {
28
- const migrator = join(deftRoot, "scripts", "migrate_vbrief.py");
29
- if (!existsSync(migrator) || !statSync(migrator).isFile()) {
30
- return {
31
- name: "layout",
32
- status: "FAIL",
33
- message: `Migrator script missing at ${migrator}. The framework checkout appears incomplete or pre-v0.20; refresh per deft/QUICK-START.md.`,
34
- };
35
- }
36
17
  const schemasDir = join(resolveContentRoot(deftRoot), "vbrief", "schemas");
37
18
  if (!existsSync(schemasDir) || !statSync(schemasDir).isDirectory()) {
38
19
  return {
@@ -46,13 +27,13 @@ export function checkLayout(deftRoot, projectRoot) {
46
27
  return {
47
28
  name: "layout",
48
29
  status: "WARN",
49
- message: `Project vbrief/ not present at ${projectVbrief} -- migrator will create it on first run; this is expected for greenfield projects.`,
30
+ message: `Project vbrief/ not present at ${projectVbrief} -- expected for greenfield projects.`,
50
31
  };
51
32
  }
52
33
  return {
53
34
  name: "layout",
54
35
  status: "PASS",
55
- message: `Framework migrator + schemas present; project vbrief/ at ${projectVbrief}.`,
36
+ message: `Framework schemas present; project vbrief/ at ${projectVbrief}.`,
56
37
  };
57
38
  }
58
39
  export function checkGitClean(projectRoot) {
@@ -65,7 +46,7 @@ export function checkGitClean(projectRoot) {
65
46
  return {
66
47
  name: "git-clean",
67
48
  status: "WARN",
68
- message: "Working tree is dirty. The migrator will refuse to run without --force; preview with `task migrate:vbrief -- --dry-run` first.",
49
+ message: "Working tree is dirty. Commit or stash before running a frozen-release migration.",
69
50
  };
70
51
  }
71
52
  return { name: "git-clean", status: "PASS", message: "Working tree is clean." };
@@ -76,7 +57,7 @@ export function checkGitClean(projectRoot) {
76
57
  return {
77
58
  name: "git-clean",
78
59
  status: "WARN",
79
- message: "git executable not on PATH; skipping working-tree check. Migrator's dirty-tree guard will still fire if applicable.",
60
+ message: "git executable not on PATH; skipping working-tree check.",
80
61
  };
81
62
  }
82
63
  return {
@@ -91,8 +72,8 @@ export function checkDocumentModel(projectRoot) {
91
72
  if (legacy.length > 0) {
92
73
  return {
93
74
  name: "document-model",
94
- status: "PASS",
95
- message: `Legacy root artifact(s) detected: ${legacy.join(", ")}.`,
75
+ status: "FAIL",
76
+ message: `Pre-v0.20 document model detected (${legacy.join(", ")}). ${frozenPreCutoverMigrationGuidance()}`,
96
77
  };
97
78
  }
98
79
  const specPath = join(projectRoot, "SPECIFICATION.md");
@@ -117,8 +98,8 @@ export function checkDocumentModel(projectRoot) {
117
98
  if (isCurrentGeneratedSpecification(projectRoot, content)) {
118
99
  return {
119
100
  name: "document-model",
120
- status: "FAIL",
121
- message: "Current generated SPECIFICATION.md detected (source: vbrief/specification.vbrief.json); `task migrate:vbrief` is not needed.",
101
+ status: "PASS",
102
+ message: "Current generated SPECIFICATION.md detected (source: vbrief/specification.vbrief.json); pre-v0.20 migration is not needed.",
122
103
  };
123
104
  }
124
105
  }
@@ -128,20 +109,19 @@ export function checkDocumentModel(projectRoot) {
128
109
  if (missing.length > 0) {
129
110
  return {
130
111
  name: "document-model",
131
- status: "PASS",
132
- message: `Partial vBRIEF layout detected; missing lifecycle folder(s): ${missing.join(", ")}.`,
112
+ status: "FAIL",
113
+ message: `Partial vBRIEF layout detected; missing lifecycle folder(s): ${missing.join(", ")}. Create the folders or follow ${frozenPreCutoverMigrationGuidance()}`,
133
114
  };
134
115
  }
135
116
  }
136
117
  return {
137
118
  name: "document-model",
138
- status: "WARN",
139
- message: "No legacy root SPECIFICATION.md/PROJECT.md artifacts detected. Migration may have nothing to do.",
119
+ status: "PASS",
120
+ message: "No pre-v0.20 document-model artifacts detected.",
140
121
  };
141
122
  }
142
- export function evaluate(deftRoot, projectRoot, which = defaultWhich) {
123
+ export function evaluate(deftRoot, projectRoot) {
143
124
  const results = [
144
- checkUv(which),
145
125
  checkLayout(deftRoot, projectRoot),
146
126
  checkDocumentModel(projectRoot),
147
127
  checkGitClean(projectRoot),
@@ -183,10 +163,10 @@ export function emitMigratePreflight(outcome, io, quiet) {
183
163
  io.writeOut(line);
184
164
  }
185
165
  if (outcome.exitCode === 1) {
186
- io.writeErr("migrate:preflight FAILED -- resolve the FAIL line(s) above before running `task migrate:vbrief`.\n");
166
+ io.writeErr("migrate:preflight FAILED -- pre-v0.20 document model or incomplete vBRIEF layout. Resolve using UPGRADING.md § Frozen pre-v0.20 document-model migration (#2068).\n");
187
167
  }
188
168
  else {
189
- io.writeOut("migrate:preflight OK -- environment ready for `task migrate:vbrief`.\n");
169
+ io.writeOut("migrate:preflight OK -- no pre-v0.20 document-model migration required.\n");
190
170
  }
191
171
  return outcome.exitCode;
192
172
  }
@@ -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