@deftai/directive-core 0.59.0 → 0.60.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/deposit/python-free.d.ts +21 -0
- package/dist/deposit/python-free.js +149 -0
- package/dist/doctor/constants.d.ts +2 -0
- package/dist/doctor/constants.js +2 -0
- package/dist/doctor/main.js +22 -8
- package/dist/init-deposit/init-deposit.js +2 -0
- package/dist/init-deposit/refresh.js +2 -0
- package/dist/install-upgrade/index.d.ts +11 -0
- package/dist/install-upgrade/index.js +146 -0
- package/dist/migrate-preflight/index.d.ts +36 -0
- package/dist/migrate-preflight/index.js +193 -0
- package/dist/release-e2e/greenfield-python-free-smoke-cli.d.ts +3 -0
- package/dist/release-e2e/greenfield-python-free-smoke-cli.js +10 -0
- package/dist/release-e2e/greenfield-python-free-smoke.d.ts +12 -0
- package/dist/release-e2e/greenfield-python-free-smoke.js +183 -0
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/project-render.d.ts +24 -0
- package/dist/render/project-render.js +111 -7
- package/dist/task-surface/index.d.ts +13 -0
- package/dist/task-surface/index.js +126 -0
- package/dist/vbrief-validate/precutover.d.ts +4 -0
- package/dist/vbrief-validate/precutover.js +24 -2
- package/dist/verify-env/toolchain-check.d.ts +9 -2
- package/dist/verify-env/toolchain-check.js +15 -4
- package/package.json +15 -3
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python-free deposit hygiene (#2022 Phase 3).
|
|
3
|
+
*
|
|
4
|
+
* After copying @deftai/directive-content into `.deft/core`, strip any
|
|
5
|
+
* Python-only artifacts that must not ship to npm consumers: the legacy
|
|
6
|
+
* `scripts/` tree, `.py` files, and Python `run` shims.
|
|
7
|
+
*/
|
|
8
|
+
export interface PythonArtifact {
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly kind: "py-file" | "scripts-tree" | "run-shim";
|
|
11
|
+
}
|
|
12
|
+
/** List Python-only artifacts under a deposit root (relative paths). */
|
|
13
|
+
export declare function collectPythonArtifacts(depositDir: string): PythonArtifact[];
|
|
14
|
+
/** Return true when a repo-root `run` file is a Python launcher shim. */
|
|
15
|
+
export declare function isRepoRootPythonRunShim(projectDir: string): boolean;
|
|
16
|
+
export interface PrunePythonArtifactsIo {
|
|
17
|
+
printf: (text: string) => void;
|
|
18
|
+
}
|
|
19
|
+
/** Remove Python-only trees/files from the consumer deposit and repo-root run shim. */
|
|
20
|
+
export declare function prunePythonArtifactsFromDeposit(depositDir: string, projectDir: string, io?: PrunePythonArtifactsIo): Promise<number>;
|
|
21
|
+
//# sourceMappingURL=python-free.d.ts.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python-free deposit hygiene (#2022 Phase 3).
|
|
3
|
+
*
|
|
4
|
+
* After copying @deftai/directive-content into `.deft/core`, strip any
|
|
5
|
+
* Python-only artifacts that must not ship to npm consumers: the legacy
|
|
6
|
+
* `scripts/` tree, `.py` files, and Python `run` shims.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
9
|
+
import { readdir, readFile, rm, stat } from "node:fs/promises";
|
|
10
|
+
import { join, relative } from "node:path";
|
|
11
|
+
const PY_SUFFIX = ".py";
|
|
12
|
+
const PYC_SUFFIX = ".pyc";
|
|
13
|
+
function isPythonRunShim(_path, head) {
|
|
14
|
+
if (!head.startsWith("#!"))
|
|
15
|
+
return false;
|
|
16
|
+
const bang = head.split("\n", 1)[0] ?? head;
|
|
17
|
+
return /python/i.test(bang);
|
|
18
|
+
}
|
|
19
|
+
function walkForPyFilesSync(root, base, found) {
|
|
20
|
+
let entries;
|
|
21
|
+
try {
|
|
22
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const full = join(root, entry.name);
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
if (entry.name === "__pycache__") {
|
|
31
|
+
found.push({ path: relative(base, full), kind: "py-file" });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
walkForPyFilesSync(full, base, found);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!entry.isFile())
|
|
38
|
+
continue;
|
|
39
|
+
if (entry.name.endsWith(PY_SUFFIX) || entry.name.endsWith(PYC_SUFFIX)) {
|
|
40
|
+
found.push({ path: relative(base, full), kind: "py-file" });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** List Python-only artifacts under a deposit root (relative paths). */
|
|
45
|
+
export function collectPythonArtifacts(depositDir) {
|
|
46
|
+
const found = [];
|
|
47
|
+
const scriptsDir = join(depositDir, "scripts");
|
|
48
|
+
try {
|
|
49
|
+
if (statSync(scriptsDir).isDirectory()) {
|
|
50
|
+
found.push({ path: "scripts", kind: "scripts-tree" });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// absent
|
|
55
|
+
}
|
|
56
|
+
const runPath = join(depositDir, "run");
|
|
57
|
+
if (existsSync(runPath)) {
|
|
58
|
+
try {
|
|
59
|
+
const head = readFileSync(runPath, "utf8");
|
|
60
|
+
if (isPythonRunShim(runPath, head.slice(0, 200))) {
|
|
61
|
+
found.push({ path: "run", kind: "run-shim" });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
found.push({ path: "run", kind: "run-shim" });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
walkForPyFilesSync(depositDir, depositDir, found);
|
|
69
|
+
return found;
|
|
70
|
+
}
|
|
71
|
+
/** Return true when a repo-root `run` file is a Python launcher shim. */
|
|
72
|
+
export function isRepoRootPythonRunShim(projectDir) {
|
|
73
|
+
const runPath = join(projectDir, "run");
|
|
74
|
+
if (!existsSync(runPath))
|
|
75
|
+
return false;
|
|
76
|
+
try {
|
|
77
|
+
const head = readFileSync(runPath, "utf8").slice(0, 200);
|
|
78
|
+
return isPythonRunShim(runPath, head);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Remove Python-only trees/files from the consumer deposit and repo-root run shim. */
|
|
85
|
+
export async function prunePythonArtifactsFromDeposit(depositDir, projectDir, io) {
|
|
86
|
+
let removed = 0;
|
|
87
|
+
const scriptsDir = join(depositDir, "scripts");
|
|
88
|
+
try {
|
|
89
|
+
if ((await stat(scriptsDir)).isDirectory()) {
|
|
90
|
+
await rm(scriptsDir, { recursive: true, force: true });
|
|
91
|
+
removed += 1;
|
|
92
|
+
io?.printf("Removed Python scripts/ tree from the consumer deposit (#2022 Phase 3).\n");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// absent
|
|
97
|
+
}
|
|
98
|
+
async function walkRemovePy(root) {
|
|
99
|
+
let entries;
|
|
100
|
+
try {
|
|
101
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
const full = join(root, entry.name);
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
if (entry.name === "__pycache__") {
|
|
110
|
+
await rm(full, { recursive: true, force: true });
|
|
111
|
+
removed += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
await walkRemovePy(full);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (!entry.isFile())
|
|
118
|
+
continue;
|
|
119
|
+
if (entry.name.endsWith(PY_SUFFIX) || entry.name.endsWith(PYC_SUFFIX)) {
|
|
120
|
+
await rm(full, { force: true });
|
|
121
|
+
removed += 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
await walkRemovePy(depositDir);
|
|
126
|
+
const depositRun = join(depositDir, "run");
|
|
127
|
+
if (existsSync(depositRun)) {
|
|
128
|
+
try {
|
|
129
|
+
const head = await readFile(depositRun, "utf8");
|
|
130
|
+
if (isPythonRunShim(depositRun, head.slice(0, 200))) {
|
|
131
|
+
await rm(depositRun, { force: true });
|
|
132
|
+
removed += 1;
|
|
133
|
+
io?.printf("Removed Python run shim from .deft/core (#2022 Phase 3).\n");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
await rm(depositRun, { force: true });
|
|
138
|
+
removed += 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const projectRun = join(projectDir, "run");
|
|
142
|
+
if (isRepoRootPythonRunShim(projectDir)) {
|
|
143
|
+
await rm(projectRun, { force: true });
|
|
144
|
+
removed += 1;
|
|
145
|
+
io?.printf("Removed repo-root Python run shim (#2022 Phase 3).\n");
|
|
146
|
+
}
|
|
147
|
+
return removed;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=python-free.js.map
|
|
@@ -8,6 +8,8 @@ export declare const REDIRECT_STUB_HEADER_LINES = 8;
|
|
|
8
8
|
export declare const TASKFILE_INCLUDE_SNIPPET = "version: '3'\n\nincludes:\n deft:\n taskfile: ./.deft/core/Taskfile.yml\n optional: true\n";
|
|
9
9
|
export declare const DOCTOR_ALLOWED_FLAGS: readonly ["--session", "--fix", "--repair", "--repair-taskfile", "--json", "--quiet", "--full", "--project-root", "-h", "--help"];
|
|
10
10
|
export declare const EXPECTED_FRAMEWORK_DIRS: readonly ["tasks", "scripts", "vbrief"];
|
|
11
|
+
/** npm consumer deposit after #2022 Phase 3 -- Python scripts/ tree is intentionally absent. */
|
|
12
|
+
export declare const CONSUMER_FRAMEWORK_DIRS: readonly ["tasks", "vbrief"];
|
|
11
13
|
export declare const DEFT_REPO_POSITIVE_MARKERS: readonly ["content/templates/agents-entry.md", "content/skills/deft-directive-build/SKILL.md"];
|
|
12
14
|
export declare const EXPECTED_CONTENT_DIRS: readonly ["languages", "strategies", "skills", "templates"];
|
|
13
15
|
/** Post-freeze canonical upgrade path (#1997 / #2003 / #1912). */
|
package/dist/doctor/constants.js
CHANGED
|
@@ -27,6 +27,8 @@ export const DOCTOR_ALLOWED_FLAGS = [
|
|
|
27
27
|
// Engine / lifecycle dirs that stay at the framework root (NOT relocated by
|
|
28
28
|
// #1875). Shippable-content dirs moved under content/ -- see EXPECTED_CONTENT_DIRS.
|
|
29
29
|
export const EXPECTED_FRAMEWORK_DIRS = ["tasks", "scripts", "vbrief"];
|
|
30
|
+
/** npm consumer deposit after #2022 Phase 3 -- Python scripts/ tree is intentionally absent. */
|
|
31
|
+
export const CONSUMER_FRAMEWORK_DIRS = ["tasks", "vbrief"];
|
|
30
32
|
// Post-#1875 content/ move: these framework-internal markers now live under
|
|
31
33
|
// content/ in the SOURCE repo. They identify a deft source checkout (a consumer
|
|
32
34
|
// would never reproduce them); the C1 flatten means a consumer deposit has no
|
package/dist/doctor/main.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
3
|
import { contentRoot } from "../content-root.js";
|
|
4
4
|
import { agentsRefreshPlan, hasV3ManagedMarker } from "./agents-md.js";
|
|
5
5
|
import { runChecks } from "./checks.js";
|
|
6
|
-
import { EXPECTED_CONTENT_DIRS, EXPECTED_FRAMEWORK_DIRS, TASKFILE_INCLUDE_SNIPPET, UV_INSTALL_URL, } from "./constants.js";
|
|
6
|
+
import { CONSUMER_FRAMEWORK_DIRS, EXPECTED_CONTENT_DIRS, EXPECTED_FRAMEWORK_DIRS, TASKFILE_INCLUDE_SNIPPET, UV_INSTALL_URL, } from "./constants.js";
|
|
7
7
|
import { decideThrottle, formatIsoZ, readState, renderDoctorStatusLine, writeState, } from "./doctor-state.js";
|
|
8
8
|
import { formatAllowedFlagsHint, formatUnknownFlagsError, parseDoctorFlags } from "./flags.js";
|
|
9
9
|
import { pythonJsonDump } from "./json.js";
|
|
10
|
+
import { parseInstallRootFromAgentsMd } from "./manifest.js";
|
|
10
11
|
import { createPlainSink } from "./output.js";
|
|
11
12
|
import { resolveDefaultFrameworkRoot, resolvePath, resolveVersion, runningInsideDeftRepo, } from "./paths.js";
|
|
12
13
|
import { runPayloadStalenessCheck } from "./payload-staleness.js";
|
|
@@ -28,6 +29,7 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
28
29
|
const fullMode = flags.full;
|
|
29
30
|
const projectRoot = resolvePath(flags.projectRoot ?? process.cwd());
|
|
30
31
|
const frameworkRoot = seams.frameworkRoot ?? resolveDefaultFrameworkRoot();
|
|
32
|
+
const consumerContext = resolve(projectRoot) !== resolve(frameworkRoot);
|
|
31
33
|
const whichFn = seams.whichFn ?? defaultWhich;
|
|
32
34
|
const nowFn = seams.now ?? (() => new Date());
|
|
33
35
|
if (!fullMode) {
|
|
@@ -108,11 +110,13 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
108
110
|
suggestion: installUrl || null,
|
|
109
111
|
});
|
|
110
112
|
};
|
|
111
|
-
|
|
113
|
+
if (!consumerContext) {
|
|
114
|
+
checkCommand("uv", "uv (Astral Python runner)", true, UV_INSTALL_URL);
|
|
115
|
+
checkCommand("python3", "python3");
|
|
116
|
+
checkCommand("go", "go");
|
|
117
|
+
}
|
|
112
118
|
checkCommand("git", "git", true);
|
|
113
|
-
checkCommand("
|
|
114
|
-
checkCommand("go", "go");
|
|
115
|
-
checkCommand("node", "node");
|
|
119
|
+
checkCommand("node", "node", consumerContext);
|
|
116
120
|
if (!jsonMode) {
|
|
117
121
|
sink.blank();
|
|
118
122
|
}
|
|
@@ -149,10 +153,20 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
149
153
|
// #1875: shippable-content dirs resolve under content/ in a source checkout
|
|
150
154
|
// and at the root in a flattened consumer deposit; engine/lifecycle dirs stay
|
|
151
155
|
// at the framework root in both layouts.
|
|
152
|
-
|
|
156
|
+
let agentsMdText = "";
|
|
157
|
+
try {
|
|
158
|
+
agentsMdText = readFileSync(join(projectRoot, "AGENTS.md"), "utf8");
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
agentsMdText = "";
|
|
162
|
+
}
|
|
163
|
+
const installRootRel = parseInstallRootFromAgentsMd(agentsMdText) ?? ".deft/core";
|
|
164
|
+
const depositRoot = consumerContext ? join(projectRoot, installRootRel) : frameworkRoot;
|
|
165
|
+
const contentBase = contentRoot(depositRoot);
|
|
166
|
+
const frameworkDirs = consumerContext ? CONSUMER_FRAMEWORK_DIRS : EXPECTED_FRAMEWORK_DIRS;
|
|
153
167
|
const layoutChecks = [
|
|
154
168
|
...EXPECTED_CONTENT_DIRS.map((d) => [d, contentBase]),
|
|
155
|
-
...
|
|
169
|
+
...frameworkDirs.map((d) => [d, depositRoot]),
|
|
156
170
|
];
|
|
157
171
|
for (const [dirName, base] of layoutChecks) {
|
|
158
172
|
const dirPath = join(base, dirName);
|
|
@@ -9,6 +9,7 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
|
9
9
|
import { homedir, platform } from "node:os";
|
|
10
10
|
import { join, resolve } from "node:path";
|
|
11
11
|
import { copyTree } from "../deposit/copy-tree.js";
|
|
12
|
+
import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
|
|
12
13
|
import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
|
|
13
14
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
14
15
|
import { ensureInitGitignoreLines, reconstituteDepositFromContent } from "./gitignore.js";
|
|
@@ -124,6 +125,7 @@ export async function runInitDeposit(args, io, seams = {}) {
|
|
|
124
125
|
const copyContent = seams.copyContent ?? copyTree;
|
|
125
126
|
const contentRoot = await resolveContent();
|
|
126
127
|
await reconstituteDepositFromContent(contentRoot, deftDir, copyContent);
|
|
128
|
+
await prunePythonArtifactsFromDeposit(deftDir, projectDir, io);
|
|
127
129
|
ensureInitGitignoreLines(projectDir, io);
|
|
128
130
|
const nowIso = seams.nowIso ?? (() => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"));
|
|
129
131
|
const version = readContentVersion(contentRoot, seams.readPackageVersion ?? readCorePackageVersion);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { existsSync, readFileSync } from "node:fs";
|
|
11
11
|
import { join, resolve } from "node:path";
|
|
12
12
|
import { copyTree } from "../deposit/copy-tree.js";
|
|
13
|
+
import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
|
|
13
14
|
import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
|
|
14
15
|
import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
|
|
15
16
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
@@ -211,6 +212,7 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
211
212
|
const contentVersion = readContentPackageVersion(contentRoot, readPackageVersion);
|
|
212
213
|
const versionSkewNotice = buildVersionSkewNotice(engineVersion, contentVersion, previousDepositVersion);
|
|
213
214
|
await copyContent(contentRoot, deftDir);
|
|
215
|
+
await prunePythonArtifactsFromDeposit(deftDir, projectDir, io);
|
|
214
216
|
const nowIso = seams.nowIso ?? (() => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"));
|
|
215
217
|
const manifestFields = {
|
|
216
218
|
ref: contentVersion.startsWith("v") ? contentVersion : `v${contentVersion}`,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface InstallUpgradeArgs {
|
|
2
|
+
readonly projectRoot: string;
|
|
3
|
+
readonly frameworkRoot: string;
|
|
4
|
+
}
|
|
5
|
+
export interface InstallUpgradeIo {
|
|
6
|
+
writeOut: (text: string) => void;
|
|
7
|
+
writeErr: (text: string) => void;
|
|
8
|
+
}
|
|
9
|
+
/** Port of ``run upgrade`` / ``task install:upgrade`` for the consumer task surface (#1061 / #2022). */
|
|
10
|
+
export declare function runInstallUpgrade(args: InstallUpgradeArgs, io: InstallUpgradeIo): number;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, relative, resolve } from "node:path";
|
|
3
|
+
import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
|
|
4
|
+
import { buildInstallManifestText, CANONICAL_INSTALL_ROOT, } from "../init-deposit/scaffold.js";
|
|
5
|
+
import { agentsRefreshPlan } from "../platform/agents-md.js";
|
|
6
|
+
import { DEV_FALLBACK } from "../platform/constants.js";
|
|
7
|
+
import { resolveVersion } from "../platform/resolve-version.js";
|
|
8
|
+
import { detectPreCutoverLegacy } from "../vbrief-validate/precutover.js";
|
|
9
|
+
function versionMarkerPaths(projectRoot) {
|
|
10
|
+
return [join(projectRoot, "vbrief", ".deft-version"), join(projectRoot, ".deft-version")];
|
|
11
|
+
}
|
|
12
|
+
function readVersionMarker(projectRoot) {
|
|
13
|
+
for (const candidate of versionMarkerPaths(projectRoot)) {
|
|
14
|
+
if (!existsSync(candidate) || !statSync(candidate).isFile())
|
|
15
|
+
continue;
|
|
16
|
+
try {
|
|
17
|
+
const value = readFileSync(candidate, "utf8").trim();
|
|
18
|
+
if (value)
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// try next candidate
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function writeVersionMarker(targetDir, version) {
|
|
28
|
+
if (version === DEV_FALLBACK)
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
mkdirSync(targetDir, { recursive: true });
|
|
32
|
+
writeFileSync(join(targetDir, ".deft-version"), `${version}\n`, "utf8");
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// best-effort, mirrors Python
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function deriveInstallRootString(installRoot, projectRoot) {
|
|
39
|
+
try {
|
|
40
|
+
return relative(projectRoot, installRoot).split("\\").join("/") || CANONICAL_INSTALL_ROOT;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return resolve(installRoot).split("\\").join("/");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function writeInstallManifestAt(installRoot, projectRoot, version) {
|
|
47
|
+
if (version.replace(/^v/, "") === DEV_FALLBACK)
|
|
48
|
+
return null;
|
|
49
|
+
const fields = {
|
|
50
|
+
ref: version.startsWith("v") ? version : `v${version}`,
|
|
51
|
+
sha: "content-package",
|
|
52
|
+
tag: version.startsWith("v") ? version : `v${version}`,
|
|
53
|
+
installRoot: deriveInstallRootString(installRoot, projectRoot),
|
|
54
|
+
fetchedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
55
|
+
fetchedBy: "deft-upgrade",
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
mkdirSync(installRoot, { recursive: true });
|
|
59
|
+
const body = buildInstallManifestText(fields);
|
|
60
|
+
const path = join(installRoot, "VERSION");
|
|
61
|
+
writeFileSync(path, body, "utf8");
|
|
62
|
+
return path;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function migrateLegacyInstallManifest(projectRoot, canonicalManifestPath) {
|
|
69
|
+
if (canonicalManifestPath === null)
|
|
70
|
+
return;
|
|
71
|
+
const canonical = resolve(canonicalManifestPath);
|
|
72
|
+
const expectedParent = resolve(projectRoot, ".deft", "core");
|
|
73
|
+
if (resolve(canonical, "..") !== expectedParent)
|
|
74
|
+
return;
|
|
75
|
+
const legacy = join(projectRoot, ".deft", "VERSION");
|
|
76
|
+
if (!existsSync(legacy) || !statSync(legacy).isFile())
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
const legacyVersion = manifestTagToVersion(parseInstallManifest(readFileSync(legacy, "utf8")));
|
|
80
|
+
const canonicalVersion = manifestTagToVersion(parseInstallManifest(readFileSync(canonical, "utf8")));
|
|
81
|
+
if (legacyVersion !== null && legacyVersion === canonicalVersion)
|
|
82
|
+
return;
|
|
83
|
+
renameSync(legacy, join(projectRoot, ".deft", "VERSION.premigrate"));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// best-effort
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function runAgentsRefresh(projectRoot, frameworkRoot, io) {
|
|
90
|
+
const plan = agentsRefreshPlan(projectRoot, { frameworkRoot });
|
|
91
|
+
const state = String(plan.state ?? "unknown");
|
|
92
|
+
if (state === "current") {
|
|
93
|
+
io.writeOut("AGENTS.md managed section is current — no changes.\n");
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
if (state === "template-missing" || state === "template-malformed" || state === "unreadable") {
|
|
97
|
+
io.writeErr(`agents:refresh failed: ${state}\n`);
|
|
98
|
+
return 2;
|
|
99
|
+
}
|
|
100
|
+
const newContent = plan.new_content;
|
|
101
|
+
if (typeof newContent !== "string") {
|
|
102
|
+
io.writeErr("agents:refresh failed: plan produced no new_content\n");
|
|
103
|
+
return 2;
|
|
104
|
+
}
|
|
105
|
+
const path = String(plan.path ?? join(projectRoot, "AGENTS.md"));
|
|
106
|
+
writeFileSync(path, newContent, "utf8");
|
|
107
|
+
io.writeOut(`AGENTS.md updated (state=${state}).\n`);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
/** Port of ``run upgrade`` / ``task install:upgrade`` for the consumer task surface (#1061 / #2022). */
|
|
111
|
+
export function runInstallUpgrade(args, io) {
|
|
112
|
+
const projectRoot = resolve(args.projectRoot);
|
|
113
|
+
const frameworkRoot = resolve(args.frameworkRoot);
|
|
114
|
+
const version = resolveVersion({ frameworkRoot });
|
|
115
|
+
const normalizedVersion = version.startsWith("v") ? version.slice(1) : version;
|
|
116
|
+
io.writeOut(`Deft CLI v${normalizedVersion} - Upgrade\n\n`);
|
|
117
|
+
const recorded = readVersionMarker(projectRoot);
|
|
118
|
+
if (recorded === normalizedVersion) {
|
|
119
|
+
io.writeOut(`Project already at ${normalizedVersion}. Nothing to do.\n`);
|
|
120
|
+
return runAgentsRefresh(projectRoot, frameworkRoot, io);
|
|
121
|
+
}
|
|
122
|
+
const legacy = detectPreCutoverLegacy(projectRoot);
|
|
123
|
+
if (legacy.length > 0) {
|
|
124
|
+
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`);
|
|
125
|
+
}
|
|
126
|
+
const vbriefDir = join(projectRoot, "vbrief");
|
|
127
|
+
const targetDir = existsSync(vbriefDir) && statSync(vbriefDir).isDirectory() ? vbriefDir : projectRoot;
|
|
128
|
+
writeVersionMarker(targetDir, normalizedVersion);
|
|
129
|
+
let writtenManifestPath = null;
|
|
130
|
+
for (const installCandidate of [join(projectRoot, ".deft", "core"), join(projectRoot, "deft")]) {
|
|
131
|
+
if (existsSync(installCandidate) && statSync(installCandidate).isDirectory()) {
|
|
132
|
+
writtenManifestPath = writeInstallManifestAt(installCandidate, projectRoot, normalizedVersion);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
migrateLegacyInstallManifest(projectRoot, writtenManifestPath);
|
|
137
|
+
if (recorded === null) {
|
|
138
|
+
io.writeOut(`Recorded framework version ${normalizedVersion} in .deft-version.\n`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
io.writeOut(`Updated .deft-version from ${recorded} to ${normalizedVersion}.\n`);
|
|
142
|
+
}
|
|
143
|
+
io.writeOut("If legacy SPECIFICATION.md or PROJECT.md content remains, run `task migrate:vbrief` to complete the upgrade.\n");
|
|
144
|
+
return runAgentsRefresh(projectRoot, frameworkRoot, io);
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type CheckStatus = "PASS" | "WARN" | "FAIL";
|
|
2
|
+
export interface CheckResult {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly status: CheckStatus;
|
|
5
|
+
readonly message: string;
|
|
6
|
+
}
|
|
7
|
+
export interface MigratePreflightArgs {
|
|
8
|
+
readonly projectRoot: string;
|
|
9
|
+
readonly deftRoot: string;
|
|
10
|
+
readonly quiet: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface MigratePreflightConfigError {
|
|
13
|
+
readonly kind: "config";
|
|
14
|
+
readonly message: string;
|
|
15
|
+
}
|
|
16
|
+
export type MigratePreflightOutcome = {
|
|
17
|
+
readonly kind: "ready";
|
|
18
|
+
readonly exitCode: 0 | 1;
|
|
19
|
+
readonly results: readonly CheckResult[];
|
|
20
|
+
} | MigratePreflightConfigError;
|
|
21
|
+
export declare function checkUv(which?: (cmd: string) => string | null): CheckResult;
|
|
22
|
+
export declare function checkLayout(deftRoot: string, projectRoot: string): CheckResult;
|
|
23
|
+
export declare function checkGitClean(projectRoot: string): CheckResult;
|
|
24
|
+
export declare function checkDocumentModel(projectRoot: string): CheckResult;
|
|
25
|
+
export declare function evaluate(deftRoot: string, projectRoot: string, which?: (cmd: string) => string | null): {
|
|
26
|
+
exitCode: 0 | 1;
|
|
27
|
+
results: CheckResult[];
|
|
28
|
+
};
|
|
29
|
+
export declare function formatCheckLine(result: CheckResult): string;
|
|
30
|
+
export declare function runMigratePreflight(args: MigratePreflightArgs): MigratePreflightOutcome;
|
|
31
|
+
export interface MigratePreflightIo {
|
|
32
|
+
writeOut: (text: string) => void;
|
|
33
|
+
writeErr: (text: string) => void;
|
|
34
|
+
}
|
|
35
|
+
export declare function emitMigratePreflight(outcome: Exclude<MigratePreflightOutcome, MigratePreflightConfigError>, io: MigratePreflightIo, quiet: boolean): number;
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|