@deftai/directive-core 0.55.2 → 0.56.1

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 (64) hide show
  1. package/dist/cache/errors.d.ts +4 -1
  2. package/dist/cache/errors.js +8 -2
  3. package/dist/cache/fetch.d.ts +50 -0
  4. package/dist/cache/fetch.js +224 -3
  5. package/dist/cache/main.js +10 -5
  6. package/dist/cache/operations.js +6 -4
  7. package/dist/deposit/copy-tree.d.ts +16 -0
  8. package/dist/deposit/copy-tree.js +58 -0
  9. package/dist/deposit/resolve-content.d.ts +28 -0
  10. package/dist/deposit/resolve-content.js +70 -0
  11. package/dist/doctor/checks.d.ts +6 -0
  12. package/dist/doctor/checks.js +37 -0
  13. package/dist/doctor/constants.d.ts +2 -0
  14. package/dist/doctor/constants.js +8 -0
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +1 -0
  17. package/dist/init-deposit/gitignore.d.ts +51 -0
  18. package/dist/init-deposit/gitignore.js +200 -0
  19. package/dist/init-deposit/index.d.ts +6 -0
  20. package/dist/init-deposit/index.js +6 -0
  21. package/dist/init-deposit/init-deposit.d.ts +44 -0
  22. package/dist/init-deposit/init-deposit.js +197 -0
  23. package/dist/init-deposit/legacy-detect.d.ts +61 -0
  24. package/dist/init-deposit/legacy-detect.js +223 -0
  25. package/dist/init-deposit/migrate.d.ts +88 -0
  26. package/dist/init-deposit/migrate.js +196 -0
  27. package/dist/init-deposit/refresh.d.ts +50 -0
  28. package/dist/init-deposit/refresh.js +292 -0
  29. package/dist/init-deposit/scaffold.d.ts +41 -0
  30. package/dist/init-deposit/scaffold.js +753 -0
  31. package/dist/legacy-bridge/bridge-drift.d.ts +70 -0
  32. package/dist/legacy-bridge/bridge-drift.js +171 -0
  33. package/dist/legacy-bridge/freeze-gate.d.ts +79 -0
  34. package/dist/legacy-bridge/freeze-gate.js +168 -0
  35. package/dist/legacy-bridge/index.d.ts +4 -0
  36. package/dist/legacy-bridge/index.js +4 -0
  37. package/dist/legacy-bridge/sot.d.ts +45 -0
  38. package/dist/legacy-bridge/sot.js +49 -0
  39. package/dist/preflight-cache/evaluate.d.ts +11 -0
  40. package/dist/preflight-cache/evaluate.js +63 -13
  41. package/dist/release-e2e/flags.js +5 -1
  42. package/dist/release-e2e/legacy-bridge-leg.d.ts +134 -0
  43. package/dist/release-e2e/legacy-bridge-leg.js +478 -0
  44. package/dist/release-e2e/main.d.ts +2 -1
  45. package/dist/release-e2e/main.js +17 -1
  46. package/dist/release-e2e/types.d.ts +9 -0
  47. package/dist/render/index.d.ts +1 -1
  48. package/dist/render/index.js +1 -1
  49. package/dist/render/project-render.js +4 -3
  50. package/dist/render/roadmap-render.d.ts +4 -2
  51. package/dist/render/roadmap-render.js +14 -7
  52. package/dist/triage/actions/candidates-log.d.ts +12 -1
  53. package/dist/triage/actions/candidates-log.js +44 -7
  54. package/dist/triage/actions/index.d.ts +25 -1
  55. package/dist/triage/actions/index.js +94 -3
  56. package/dist/triage/summary/index.d.ts +1 -3
  57. package/dist/triage/summary/index.js +6 -57
  58. package/dist/triage/summary/reconcilable.js +4 -33
  59. package/dist/triage/welcome/default-mode.d.ts +1 -0
  60. package/dist/triage/welcome/default-mode.js +6 -0
  61. package/dist/vbrief-validate/project-definition.js +4 -69
  62. package/dist/vbrief-validate/registry-status.d.ts +20 -0
  63. package/dist/vbrief-validate/registry-status.js +85 -0
  64. package/package.json +11 -2
@@ -0,0 +1,70 @@
1
+ /**
2
+ * bridge-drift.ts -- Tier-1 cross-surface drift gate for the legacy Go-installer
3
+ * bridge (#1912).
4
+ *
5
+ * Asserts that every surface which STATES the frozen bridge version reads the
6
+ * Tier-0 SoT (`lastGoInstaller`) instead of hardcoding a competing number.
7
+ * Modeled on the #1308/#1309 freshness/propagation gates: surfaces opt in to the
8
+ * contract via a stable SENTINEL marker, and the gate enforces that no marked
9
+ * line outside the SoT module carries a hardcoded Go-installer semver.
10
+ *
11
+ * The contract (sentinel-gated so it is zero-false-positive across the many
12
+ * unrelated docs/code files that legitimately mention "deft-install"):
13
+ *
14
+ * - Any surface that states the frozen bridge version puts the marker token
15
+ * `deft:last-go-installer` on the stating line and references the SoT
16
+ * (`lastGoInstaller()` / `LAST_GO_INSTALLER`) -- NOT an inline number.
17
+ * - A marked line that ALSO contains a hardcoded `vX.Y.Z` literal is drift:
18
+ * the version must come from the SoT, not be restated.
19
+ * - `sot.ts` is the sole exemption -- it is where the literal legitimately
20
+ * lives -- and is never scanned as a surface.
21
+ *
22
+ * Designed to pass whether or not story P's UPGRADING/doctor surfaces exist yet:
23
+ * a registered surface that is absent is simply skipped (the assertion is "no
24
+ * surface hardcodes", not "surface X must exist").
25
+ *
26
+ * Three-state exit (mirrors verify-source/content-manifest + scm-boundary):
27
+ * 0 -- clean: no marked surface hardcodes a Go-installer version.
28
+ * 1 -- drift: a marked surface line hardcodes a competing version.
29
+ * 2 -- config error: the SoT module is missing or no longer exposes the
30
+ * reader API + sentinel anchor (the contract has no anchor to enforce).
31
+ */
32
+ /** Stable marker a surface puts on the line that states the bridge version. */
33
+ export declare const BRIDGE_SENTINEL = "deft:last-go-installer";
34
+ /** Repo-relative path to the Tier-0 SoT module (the sole literal home). */
35
+ export declare const SOT_RELATIVE_PATH = "packages/core/src/legacy-bridge/sot.ts";
36
+ /**
37
+ * Default surface registry: files that document or consume the bridge version.
38
+ * Scanned only when present; absence is a pass. The SoT module and `*.test.ts`
39
+ * fixtures are never scanned (they may legitimately hold version literals).
40
+ * Story P's UPGRADING/doctor surfaces are listed so the gate covers them the
41
+ * moment they opt in via the sentinel -- but their absence today is a clean pass.
42
+ */
43
+ export declare const DEFAULT_DRIFT_SURFACES: readonly string[];
44
+ export interface DriftFinding {
45
+ readonly path: string;
46
+ readonly line: number;
47
+ readonly context: string;
48
+ }
49
+ /** Scan one surface's source for marked lines that hardcode a version literal. */
50
+ export declare function scanSurfaceForDrift(relPath: string, source: string): DriftFinding[];
51
+ export interface DriftResult {
52
+ readonly code: 0 | 1 | 2;
53
+ readonly message: string;
54
+ readonly stream: "stdout" | "stderr";
55
+ readonly findings: readonly DriftFinding[];
56
+ }
57
+ export interface DriftEvaluateOptions {
58
+ /** Test seam: override the surface registry. */
59
+ readonly surfaces?: readonly string[];
60
+ /** Test seam: override the SoT module path. */
61
+ readonly sotPath?: string;
62
+ /** Test seam: override the SoT value for the clean-state message. */
63
+ readonly pinned?: string | null;
64
+ }
65
+ /**
66
+ * Evaluate the cross-surface drift gate. Reads the SoT module (config anchor)
67
+ * plus each existing registered surface.
68
+ */
69
+ export declare function evaluateBridgeDrift(projectRoot: string, options?: DriftEvaluateOptions): DriftResult;
70
+ //# sourceMappingURL=bridge-drift.d.ts.map
@@ -0,0 +1,171 @@
1
+ /**
2
+ * bridge-drift.ts -- Tier-1 cross-surface drift gate for the legacy Go-installer
3
+ * bridge (#1912).
4
+ *
5
+ * Asserts that every surface which STATES the frozen bridge version reads the
6
+ * Tier-0 SoT (`lastGoInstaller`) instead of hardcoding a competing number.
7
+ * Modeled on the #1308/#1309 freshness/propagation gates: surfaces opt in to the
8
+ * contract via a stable SENTINEL marker, and the gate enforces that no marked
9
+ * line outside the SoT module carries a hardcoded Go-installer semver.
10
+ *
11
+ * The contract (sentinel-gated so it is zero-false-positive across the many
12
+ * unrelated docs/code files that legitimately mention "deft-install"):
13
+ *
14
+ * - Any surface that states the frozen bridge version puts the marker token
15
+ * `deft:last-go-installer` on the stating line and references the SoT
16
+ * (`lastGoInstaller()` / `LAST_GO_INSTALLER`) -- NOT an inline number.
17
+ * - A marked line that ALSO contains a hardcoded `vX.Y.Z` literal is drift:
18
+ * the version must come from the SoT, not be restated.
19
+ * - `sot.ts` is the sole exemption -- it is where the literal legitimately
20
+ * lives -- and is never scanned as a surface.
21
+ *
22
+ * Designed to pass whether or not story P's UPGRADING/doctor surfaces exist yet:
23
+ * a registered surface that is absent is simply skipped (the assertion is "no
24
+ * surface hardcodes", not "surface X must exist").
25
+ *
26
+ * Three-state exit (mirrors verify-source/content-manifest + scm-boundary):
27
+ * 0 -- clean: no marked surface hardcodes a Go-installer version.
28
+ * 1 -- drift: a marked surface line hardcodes a competing version.
29
+ * 2 -- config error: the SoT module is missing or no longer exposes the
30
+ * reader API + sentinel anchor (the contract has no anchor to enforce).
31
+ */
32
+ import { existsSync, readFileSync, statSync } from "node:fs";
33
+ import { resolve } from "node:path";
34
+ import { lastGoInstaller } from "./sot.js";
35
+ const EXIT_OK = 0;
36
+ const EXIT_DRIFT = 1;
37
+ const EXIT_CONFIG_ERROR = 2;
38
+ /** Stable marker a surface puts on the line that states the bridge version. */
39
+ export const BRIDGE_SENTINEL = "deft:last-go-installer";
40
+ /** Repo-relative path to the Tier-0 SoT module (the sole literal home). */
41
+ export const SOT_RELATIVE_PATH = "packages/core/src/legacy-bridge/sot.ts";
42
+ /**
43
+ * Default surface registry: files that document or consume the bridge version.
44
+ * Scanned only when present; absence is a pass. The SoT module and `*.test.ts`
45
+ * fixtures are never scanned (they may legitimately hold version literals).
46
+ * Story P's UPGRADING/doctor surfaces are listed so the gate covers them the
47
+ * moment they opt in via the sentinel -- but their absence today is a clean pass.
48
+ */
49
+ export const DEFAULT_DRIFT_SURFACES = [
50
+ "UPGRADING.md",
51
+ "content/UPGRADING.md",
52
+ "packages/core/src/doctor/constants.ts",
53
+ "packages/core/src/legacy-bridge/freeze-gate.ts",
54
+ "packages/core/src/legacy-bridge/bridge-drift.ts",
55
+ ];
56
+ /**
57
+ * Matches a hardcoded Go-installer-style semver literal (tolerates a leading `v`).
58
+ * The digit runs are length-bounded (`{1,9}`) rather than unbounded (`+`) on
59
+ * purpose: a real semver component is never 10+ digits, and the bound removes the
60
+ * quadratic-backtracking ReDoS class (`js/polynomial-redos`) that an unbounded
61
+ * `\d+\.\d+\.\d+` exhibits when `.test()` runs over uncontrolled file input.
62
+ */
63
+ const SEMVER_LITERAL = /v?\d{1,9}\.\d{1,9}\.\d{1,9}/;
64
+ /** Scan one surface's source for marked lines that hardcode a version literal. */
65
+ export function scanSurfaceForDrift(relPath, source) {
66
+ const findings = [];
67
+ const lines = source.split("\n");
68
+ for (let i = 0; i < lines.length; i += 1) {
69
+ const line = lines[i] ?? "";
70
+ if (line.includes(BRIDGE_SENTINEL) && SEMVER_LITERAL.test(line)) {
71
+ const ctx = line.trim();
72
+ findings.push({
73
+ path: relPath,
74
+ line: i + 1,
75
+ context: ctx.length <= 120 ? ctx : `${ctx.slice(0, 117)}...`,
76
+ });
77
+ }
78
+ }
79
+ return findings;
80
+ }
81
+ function isReadableFile(full) {
82
+ try {
83
+ return statSync(full).isFile();
84
+ }
85
+ catch {
86
+ return false;
87
+ }
88
+ }
89
+ /**
90
+ * Evaluate the cross-surface drift gate. Reads the SoT module (config anchor)
91
+ * plus each existing registered surface.
92
+ */
93
+ export function evaluateBridgeDrift(projectRoot, options = {}) {
94
+ const root = resolve(projectRoot);
95
+ const sotFull = resolve(root, options.sotPath ?? SOT_RELATIVE_PATH);
96
+ if (!existsSync(sotFull)) {
97
+ return {
98
+ code: EXIT_CONFIG_ERROR,
99
+ findings: [],
100
+ message: `Error: Tier-0 SoT module not found at ${options.sotPath ?? SOT_RELATIVE_PATH}; ` +
101
+ "the drift contract has no anchor to enforce.",
102
+ stream: "stderr",
103
+ };
104
+ }
105
+ let sotSource;
106
+ try {
107
+ sotSource = readFileSync(sotFull, { encoding: "utf8" });
108
+ }
109
+ catch (err) {
110
+ const msg = err instanceof Error ? err.message : String(err);
111
+ return {
112
+ code: EXIT_CONFIG_ERROR,
113
+ findings: [],
114
+ message: `Error: failed to read SoT module ${sotFull}: ${msg}`,
115
+ stream: "stderr",
116
+ };
117
+ }
118
+ const anchorOk = sotSource.includes("lastGoInstaller") &&
119
+ sotSource.includes("isFrozen") &&
120
+ sotSource.includes(BRIDGE_SENTINEL);
121
+ if (!anchorOk) {
122
+ return {
123
+ code: EXIT_CONFIG_ERROR,
124
+ findings: [],
125
+ message: `Error: SoT module ${options.sotPath ?? SOT_RELATIVE_PATH} no longer exposes the reader API ` +
126
+ `(lastGoInstaller / isFrozen) and sentinel anchor (${BRIDGE_SENTINEL}); the drift contract is broken.`,
127
+ stream: "stderr",
128
+ };
129
+ }
130
+ const surfaces = options.surfaces ?? DEFAULT_DRIFT_SURFACES;
131
+ const sotRelNormalized = (options.sotPath ?? SOT_RELATIVE_PATH).replace(/\\/g, "/");
132
+ const findings = [];
133
+ let scanned = 0;
134
+ for (const rel of surfaces) {
135
+ const relNorm = rel.replace(/\\/g, "/");
136
+ if (relNorm === sotRelNormalized || relNorm.endsWith(".test.ts")) {
137
+ continue;
138
+ }
139
+ const full = resolve(root, rel);
140
+ if (!isReadableFile(full)) {
141
+ continue;
142
+ }
143
+ let source;
144
+ try {
145
+ source = readFileSync(full, { encoding: "utf8" });
146
+ }
147
+ catch {
148
+ continue;
149
+ }
150
+ scanned += 1;
151
+ findings.push(...scanSurfaceForDrift(relNorm, source));
152
+ }
153
+ if (findings.length > 0) {
154
+ const header = `FAIL: ${findings.length} bridge-version drift finding(s) -- a marked surface hardcodes a ` +
155
+ "Go-installer version instead of reading the Tier-0 SoT (lastGoInstaller).\n" +
156
+ ` Fix: replace the inline version next to the \`${BRIDGE_SENTINEL}\` marker with a reference to ` +
157
+ "lastGoInstaller() / LAST_GO_INSTALLER (the SoT is the single source).";
158
+ const body = findings.map((f) => ` ${f.path}:${f.line} ${f.context}`).join("\n");
159
+ return { code: EXIT_DRIFT, findings, message: `${header}\n${body}`, stream: "stderr" };
160
+ }
161
+ const pinned = options.pinned !== undefined ? options.pinned : lastGoInstaller();
162
+ const state = pinned === null ? "null (unfrozen)" : pinned;
163
+ return {
164
+ code: EXIT_OK,
165
+ findings: [],
166
+ message: `OK: ${scanned} bridge-version surface(s) scanned; every reference reads the Tier-0 SoT ` +
167
+ `(lastGoInstaller = ${state}). No hardcoded competing version.`,
168
+ stream: "stdout",
169
+ };
170
+ }
171
+ //# sourceMappingURL=bridge-drift.js.map
@@ -0,0 +1,79 @@
1
+ /**
2
+ * freeze-gate.ts -- Tier-1 freeze gate for the legacy Go-installer bridge (#1912).
3
+ *
4
+ * Reads the Tier-0 SoT (`lastGoInstaller`) and weighs a candidate RELEASE TAG
5
+ * against the frozen line:
6
+ *
7
+ * - SoT null (NOT yet frozen): PASS (advisory). Go-installer development is
8
+ * still allowed up to the cut; the gate does not read the installer source
9
+ * in this state.
10
+ * - SoT pinned (frozen) + a release tag supplied: FAIL when the release tag is
11
+ * ABOVE the pinned tag -- i.e. someone is cutting a Go-installer release past
12
+ * the frozen line. At-or-below the line PASSES (re-publishing the pinned tag
13
+ * is allowed).
14
+ * - SoT pinned (frozen) + NO release tag supplied: PASS (advisory). There is no
15
+ * release being cut, so there is nothing to weigh; the enforcing teeth live
16
+ * in the release.yml tag guard, which runs only on a `v*.*.*` tag push.
17
+ *
18
+ * Why a release tag and not the installer source literal (#1972): the real
19
+ * installer version is injected via `-ldflags "-X main.version=<tag>"` at build
20
+ * time, so the `var version = "..."` literal in `cmd/deft-install/main.go` does
21
+ * NOT reflect the released version. Comparing the pinned SoT against that stale
22
+ * literal would false-fail the moment the SoT is pinned to a real 0.x tag. The
23
+ * gate therefore keys off the release tag (`github.ref_name` in CI). The source
24
+ * parsers below (`parseInstallerVersion` / `readInstallerVersion`) are retained
25
+ * as release-time utilities but are no longer the freeze-decision input.
26
+ *
27
+ * Three-state exit (mirrors verify-source/content-manifest + scm-boundary):
28
+ * 0 -- ok (advisory-when-null / advisory-when-no-tag, or at/below the line).
29
+ * 1 -- violation: frozen and the release tag is above the pinned tag.
30
+ * 2 -- config error: the SoT value or the release tag is unparseable.
31
+ *
32
+ * No version number is hardcoded here: the frozen tag comes from the SoT module
33
+ * and the candidate tag is supplied by the caller (the release pipeline) at
34
+ * runtime.
35
+ */
36
+ /** Repo-relative path to the Go installer source carrying the version constant. */
37
+ export declare const DEFAULT_INSTALLER_VERSION_PATH = "cmd/deft-install/main.go";
38
+ /** Per-shell emergency bypass env var (consistent with the other deft gates). */
39
+ export declare const FREEZE_BYPASS_ENV = "DEFT_ALLOW_GO_INSTALLER_BUMP";
40
+ /** Extract the `var version = "X"` literal from Go installer source, or null. */
41
+ export declare function parseInstallerVersion(source: string): string | null;
42
+ /** Read + parse the Go installer version constant. Throws (caller maps to 2). */
43
+ export declare function readInstallerVersion(projectRoot: string, relPath?: string): string;
44
+ /**
45
+ * Compare two installer version strings by numeric major.minor.patch core.
46
+ * Returns -1 (a < b), 0 (a == b), or 1 (a > b). Throws on an unparseable input
47
+ * (caller maps to config error). Pre-release / build metadata is ignored for the
48
+ * "above the line" determination.
49
+ */
50
+ export declare function compareInstallerVersions(a: string, b: string): -1 | 0 | 1;
51
+ export interface FreezeResult {
52
+ readonly code: 0 | 1 | 2;
53
+ readonly message: string;
54
+ readonly stream: "stdout" | "stderr";
55
+ }
56
+ export interface FreezeEvaluateOptions {
57
+ /** Test seam: override the SoT value (undefined = read the live SoT module). */
58
+ readonly pinned?: string | null;
59
+ /**
60
+ * The candidate release tag to weigh against the frozen line (CI:
61
+ * `github.ref_name`). When undefined / null the gate is advisory: there is no
62
+ * release being cut, so there is nothing to enforce -- the teeth live in the
63
+ * release.yml tag guard. This replaces the brittle source-literal comparison
64
+ * (#1972): the real installer version is ldflags-injected at build time, so
65
+ * the `var version` source literal does not reflect the released version.
66
+ */
67
+ readonly releaseTag?: string | null;
68
+ /** When true, downgrade a violation to an advisory pass (emergency bypass). */
69
+ readonly allowBump?: boolean;
70
+ }
71
+ /**
72
+ * Evaluate the freeze gate against a candidate release tag.
73
+ *
74
+ * The first parameter is retained for call-site signature compatibility (the
75
+ * `verify-go-freeze` CLI passes a project root positionally); the gate no longer
76
+ * reads the installer source, so the value is unused here (#1972).
77
+ */
78
+ export declare function evaluateGoFreeze(_projectRoot: string, options?: FreezeEvaluateOptions): FreezeResult;
79
+ //# sourceMappingURL=freeze-gate.d.ts.map
@@ -0,0 +1,168 @@
1
+ /**
2
+ * freeze-gate.ts -- Tier-1 freeze gate for the legacy Go-installer bridge (#1912).
3
+ *
4
+ * Reads the Tier-0 SoT (`lastGoInstaller`) and weighs a candidate RELEASE TAG
5
+ * against the frozen line:
6
+ *
7
+ * - SoT null (NOT yet frozen): PASS (advisory). Go-installer development is
8
+ * still allowed up to the cut; the gate does not read the installer source
9
+ * in this state.
10
+ * - SoT pinned (frozen) + a release tag supplied: FAIL when the release tag is
11
+ * ABOVE the pinned tag -- i.e. someone is cutting a Go-installer release past
12
+ * the frozen line. At-or-below the line PASSES (re-publishing the pinned tag
13
+ * is allowed).
14
+ * - SoT pinned (frozen) + NO release tag supplied: PASS (advisory). There is no
15
+ * release being cut, so there is nothing to weigh; the enforcing teeth live
16
+ * in the release.yml tag guard, which runs only on a `v*.*.*` tag push.
17
+ *
18
+ * Why a release tag and not the installer source literal (#1972): the real
19
+ * installer version is injected via `-ldflags "-X main.version=<tag>"` at build
20
+ * time, so the `var version = "..."` literal in `cmd/deft-install/main.go` does
21
+ * NOT reflect the released version. Comparing the pinned SoT against that stale
22
+ * literal would false-fail the moment the SoT is pinned to a real 0.x tag. The
23
+ * gate therefore keys off the release tag (`github.ref_name` in CI). The source
24
+ * parsers below (`parseInstallerVersion` / `readInstallerVersion`) are retained
25
+ * as release-time utilities but are no longer the freeze-decision input.
26
+ *
27
+ * Three-state exit (mirrors verify-source/content-manifest + scm-boundary):
28
+ * 0 -- ok (advisory-when-null / advisory-when-no-tag, or at/below the line).
29
+ * 1 -- violation: frozen and the release tag is above the pinned tag.
30
+ * 2 -- config error: the SoT value or the release tag is unparseable.
31
+ *
32
+ * No version number is hardcoded here: the frozen tag comes from the SoT module
33
+ * and the candidate tag is supplied by the caller (the release pipeline) at
34
+ * runtime.
35
+ */
36
+ import { existsSync, readFileSync } from "node:fs";
37
+ import { resolve } from "node:path";
38
+ import { lastGoInstaller } from "./sot.js";
39
+ const EXIT_OK = 0;
40
+ const EXIT_VIOLATION = 1;
41
+ const EXIT_CONFIG_ERROR = 2;
42
+ /** Repo-relative path to the Go installer source carrying the version constant. */
43
+ export const DEFAULT_INSTALLER_VERSION_PATH = "cmd/deft-install/main.go";
44
+ /** Per-shell emergency bypass env var (consistent with the other deft gates). */
45
+ export const FREEZE_BYPASS_ENV = "DEFT_ALLOW_GO_INSTALLER_BUMP";
46
+ /** Extract the `var version = "X"` literal from Go installer source, or null. */
47
+ export function parseInstallerVersion(source) {
48
+ const match = source.match(/var\s+version\s*=\s*"([^"]+)"/);
49
+ return match?.[1] ?? null;
50
+ }
51
+ /** Read + parse the Go installer version constant. Throws (caller maps to 2). */
52
+ export function readInstallerVersion(projectRoot, relPath = DEFAULT_INSTALLER_VERSION_PATH) {
53
+ const full = resolve(projectRoot, relPath);
54
+ if (!existsSync(full)) {
55
+ throw new Error(`Go installer source not found: ${full} -- expected the \`var version = "..."\` constant at ${relPath}.`);
56
+ }
57
+ let source;
58
+ try {
59
+ source = readFileSync(full, { encoding: "utf8" });
60
+ }
61
+ catch (err) {
62
+ const msg = err instanceof Error ? err.message : String(err);
63
+ throw new Error(`Failed to read Go installer source ${full}: ${msg}`);
64
+ }
65
+ const version = parseInstallerVersion(source);
66
+ if (version === null) {
67
+ throw new Error(`Could not parse \`var version = "..."\` from ${relPath}; the Go installer version constant is missing or malformed.`);
68
+ }
69
+ return version;
70
+ }
71
+ /** Parse the numeric major.minor.patch core, tolerating a leading `v` and a `-`/`+` suffix. */
72
+ function parseSemverCore(raw) {
73
+ const trimmed = raw.trim().replace(/^v/i, "");
74
+ const core = trimmed.split(/[-+]/, 1)[0] ?? "";
75
+ const parts = core.split(".");
76
+ if (parts.length === 0 || parts[0] === "") {
77
+ throw new Error(`unparseable version: '${raw}'`);
78
+ }
79
+ const nums = parts.map((p) => {
80
+ if (!/^\d+$/.test(p)) {
81
+ throw new Error(`unparseable version: '${raw}'`);
82
+ }
83
+ return Number.parseInt(p, 10);
84
+ });
85
+ return { major: nums[0] ?? 0, minor: nums[1] ?? 0, patch: nums[2] ?? 0 };
86
+ }
87
+ /**
88
+ * Compare two installer version strings by numeric major.minor.patch core.
89
+ * Returns -1 (a < b), 0 (a == b), or 1 (a > b). Throws on an unparseable input
90
+ * (caller maps to config error). Pre-release / build metadata is ignored for the
91
+ * "above the line" determination.
92
+ */
93
+ export function compareInstallerVersions(a, b) {
94
+ const av = parseSemverCore(a);
95
+ const bv = parseSemverCore(b);
96
+ for (const key of ["major", "minor", "patch"]) {
97
+ if (av[key] < bv[key])
98
+ return -1;
99
+ if (av[key] > bv[key])
100
+ return 1;
101
+ }
102
+ return 0;
103
+ }
104
+ /**
105
+ * Evaluate the freeze gate against a candidate release tag.
106
+ *
107
+ * The first parameter is retained for call-site signature compatibility (the
108
+ * `verify-go-freeze` CLI passes a project root positionally); the gate no longer
109
+ * reads the installer source, so the value is unused here (#1972).
110
+ */
111
+ export function evaluateGoFreeze(_projectRoot, options = {}) {
112
+ const pinned = options.pinned !== undefined ? options.pinned : lastGoInstaller();
113
+ if (pinned === null) {
114
+ return {
115
+ code: EXIT_OK,
116
+ message: "OK (advisory): the Go installer is not frozen (SoT lastGoInstaller is null). " +
117
+ "Go-installer development is still allowed up to the cut.",
118
+ stream: "stdout",
119
+ };
120
+ }
121
+ const releaseTag = options.releaseTag ?? null;
122
+ if (releaseTag === null) {
123
+ return {
124
+ code: EXIT_OK,
125
+ message: `OK (advisory): the Go installer is frozen at ${pinned}, but no release tag was supplied ` +
126
+ "to weigh against the frozen line. Freeze enforcement runs in the release.yml tag guard " +
127
+ "on a v*.*.* push; this local gate is advisory.",
128
+ stream: "stdout",
129
+ };
130
+ }
131
+ let cmp;
132
+ try {
133
+ cmp = compareInstallerVersions(releaseTag, pinned);
134
+ }
135
+ catch (err) {
136
+ const msg = err instanceof Error ? err.message : String(err);
137
+ return {
138
+ code: EXIT_CONFIG_ERROR,
139
+ message: `Error: cannot compare versions (${msg}). Check the SoT value and the release tag.`,
140
+ stream: "stderr",
141
+ };
142
+ }
143
+ if (cmp > 0) {
144
+ if (options.allowBump) {
145
+ return {
146
+ code: EXIT_OK,
147
+ message: `OK (bypass): release tag ${releaseTag} is ABOVE the frozen bridge tag ${pinned}, ` +
148
+ `but ${FREEZE_BYPASS_ENV}=1 downgraded the violation to advisory.`,
149
+ stream: "stdout",
150
+ };
151
+ }
152
+ return {
153
+ code: EXIT_VIOLATION,
154
+ message: `FAIL: release tag ${releaseTag} is ABOVE the frozen bridge tag ${pinned}.\n` +
155
+ " The last Go installer was frozen as the legacy stage-1 bridge (#1912); no Go-installer\n" +
156
+ " release past the pinned tag is allowed. Cut the release at or below the frozen tag\n" +
157
+ ` (${pinned}), or roll the freeze SoT forward. Emergency bypass: ${FREEZE_BYPASS_ENV}=1.`,
158
+ stream: "stderr",
159
+ };
160
+ }
161
+ return {
162
+ code: EXIT_OK,
163
+ message: `OK: the Go installer is frozen at ${pinned}; release tag ${releaseTag} ` +
164
+ "is at or below the frozen line.",
165
+ stream: "stdout",
166
+ };
167
+ }
168
+ //# sourceMappingURL=freeze-gate.js.map
@@ -0,0 +1,4 @@
1
+ export * from "./bridge-drift.js";
2
+ export * from "./freeze-gate.js";
3
+ export * from "./sot.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ export * from "./bridge-drift.js";
2
+ export * from "./freeze-gate.js";
3
+ export * from "./sot.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * sot.ts -- Tier-0 single source of truth for the frozen final Go-installer
3
+ * version, the legacy stage-1 bridge (#1912).
4
+ *
5
+ * SEMANTICS: null-until-frozen.
6
+ *
7
+ * - TODAY (and on every branch until the operator cuts the final Go
8
+ * installer) `LAST_GO_INSTALLER` is `null`. `isFrozen()` returns `false`.
9
+ * The freeze gate (`verify:go-freeze`) is advisory-only in this state --
10
+ * Go-installer development is still allowed up to the cut.
11
+ * - AT FREEZE TIME the OPERATOR pins the value here -- and ONLY here -- to the
12
+ * exact tag of the final published Go installer (e.g. `"v0.32.5"`). From
13
+ * that point `isFrozen()` is `true`, the freeze gate enforces the line, and
14
+ * every other surface that documents or consumes the bridge version reads
15
+ * this module instead of hardcoding a number.
16
+ *
17
+ * This module is the ONE place the bridge version is allowed to live as a
18
+ * literal. Every other surface (UPGRADING.md two-step wording, the `deft doctor`
19
+ * legacy-layout signpost, the pinned legacy-bridge e2e) MUST reference the SoT
20
+ * rather than restate the number -- the cross-surface drift gate
21
+ * (`verify:bridge-drift`) enforces that via the sentinel marker below.
22
+ *
23
+ * SENTINEL MARKER (cross-surface drift contract, #1912): any surface that
24
+ * STATES the frozen bridge version carries the marker token
25
+ * `deft:last-go-installer` on the stating line. `verify:bridge-drift` asserts no
26
+ * marked line outside this module hardcodes a Go-installer semver -- the value
27
+ * must come from `lastGoInstaller()` / `LAST_GO_INSTALLER`. This module is the
28
+ * sole exemption: it is where the literal legitimately lives.
29
+ *
30
+ * deft:last-go-installer -- anchor; the value is the constant below, never an
31
+ * inline number on this comment line.
32
+ */
33
+ /**
34
+ * The frozen final Go-installer tag, or `null` while unfrozen.
35
+ *
36
+ * OPERATOR FREEZE STEP: set this to the published tag string (e.g. `"v0.32.5"`)
37
+ * at freeze time. Leave it `null` otherwise. This is the only edit required to
38
+ * pin the bridge version; the freeze + drift gates pick it up automatically.
39
+ */
40
+ export declare const LAST_GO_INSTALLER: string | null;
41
+ /** Returns the frozen final Go-installer tag, or `null` while unfrozen. */
42
+ export declare function lastGoInstaller(): string | null;
43
+ /** Returns `true` once the final Go installer has been frozen (SoT pinned). */
44
+ export declare function isFrozen(): boolean;
45
+ //# sourceMappingURL=sot.d.ts.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * sot.ts -- Tier-0 single source of truth for the frozen final Go-installer
3
+ * version, the legacy stage-1 bridge (#1912).
4
+ *
5
+ * SEMANTICS: null-until-frozen.
6
+ *
7
+ * - TODAY (and on every branch until the operator cuts the final Go
8
+ * installer) `LAST_GO_INSTALLER` is `null`. `isFrozen()` returns `false`.
9
+ * The freeze gate (`verify:go-freeze`) is advisory-only in this state --
10
+ * Go-installer development is still allowed up to the cut.
11
+ * - AT FREEZE TIME the OPERATOR pins the value here -- and ONLY here -- to the
12
+ * exact tag of the final published Go installer (e.g. `"v0.32.5"`). From
13
+ * that point `isFrozen()` is `true`, the freeze gate enforces the line, and
14
+ * every other surface that documents or consumes the bridge version reads
15
+ * this module instead of hardcoding a number.
16
+ *
17
+ * This module is the ONE place the bridge version is allowed to live as a
18
+ * literal. Every other surface (UPGRADING.md two-step wording, the `deft doctor`
19
+ * legacy-layout signpost, the pinned legacy-bridge e2e) MUST reference the SoT
20
+ * rather than restate the number -- the cross-surface drift gate
21
+ * (`verify:bridge-drift`) enforces that via the sentinel marker below.
22
+ *
23
+ * SENTINEL MARKER (cross-surface drift contract, #1912): any surface that
24
+ * STATES the frozen bridge version carries the marker token
25
+ * `deft:last-go-installer` on the stating line. `verify:bridge-drift` asserts no
26
+ * marked line outside this module hardcodes a Go-installer semver -- the value
27
+ * must come from `lastGoInstaller()` / `LAST_GO_INSTALLER`. This module is the
28
+ * sole exemption: it is where the literal legitimately lives.
29
+ *
30
+ * deft:last-go-installer -- anchor; the value is the constant below, never an
31
+ * inline number on this comment line.
32
+ */
33
+ /**
34
+ * The frozen final Go-installer tag, or `null` while unfrozen.
35
+ *
36
+ * OPERATOR FREEZE STEP: set this to the published tag string (e.g. `"v0.32.5"`)
37
+ * at freeze time. Leave it `null` otherwise. This is the only edit required to
38
+ * pin the bridge version; the freeze + drift gates pick it up automatically.
39
+ */
40
+ export const LAST_GO_INSTALLER = "v0.56.0";
41
+ /** Returns the frozen final Go-installer tag, or `null` while unfrozen. */
42
+ export function lastGoInstaller() {
43
+ return LAST_GO_INSTALLER;
44
+ }
45
+ /** Returns `true` once the final Go installer has been frozen (SoT pinned). */
46
+ export function isFrozen() {
47
+ return LAST_GO_INSTALLER !== null;
48
+ }
49
+ //# sourceMappingURL=sot.js.map
@@ -10,6 +10,7 @@
10
10
  * 1 -- cache stale / blocking condition found
11
11
  * 2 -- config error (cache missing and --allow-missing-bootstrap not set)
12
12
  */
13
+ import { type CacheDriftProbeResult } from "../cache/fetch.js";
13
14
  export declare const CACHE_DIR_NAME = ".deft-cache";
14
15
  export declare const DEFAULT_SOURCE = "github-issue";
15
16
  export declare const CANDIDATES_RELPATH: string;
@@ -30,7 +31,17 @@ export interface EvaluateOptions {
30
31
  allowMissingBootstrap?: boolean;
31
32
  /** Injectable clock for tests. */
32
33
  nowFn?: () => Date;
34
+ /** Injectable drift probe for tests. */
35
+ probeDriftFn?: (repo: string, cacheRoot: string, source: string) => CacheDriftProbeResult | null;
33
36
  }
37
+ /**
38
+ * Branch-aware recovery hint (#1953 Option 3).
39
+ * Age-stale (or age+drift mixed) → --force bypasses TTL; drift-only → plain refetch.
40
+ */
41
+ export declare function recoveryHintForStaleFailure(causes: Readonly<{
42
+ ageStale: boolean;
43
+ driftDetected: boolean;
44
+ }>): string;
34
45
  /**
35
46
  * Evaluate cache freshness for the given project root.
36
47
  *