@deftai/directive-core 0.61.0 → 0.61.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.
@@ -7,13 +7,14 @@
7
7
  *
8
8
  * Refs #1942, #1430, #1671.
9
9
  */
10
- import { existsSync, readFileSync } from "node:fs";
10
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
11
11
  import { join, resolve } from "node:path";
12
12
  import { copyTree } from "../deposit/copy-tree.js";
13
13
  import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
14
14
  import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
15
15
  import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
16
16
  import { readCorePackageVersion } from "../engine-version.js";
17
+ import { DEV_FALLBACK } from "../platform/constants.js";
17
18
  import { gitPorcelain } from "../story-ready/git.js";
18
19
  import { parseInitArgv } from "./init-deposit.js";
19
20
  import { buildLegacyRefusalJson, buildLegacyRefusalMessage, detectLegacyLayout, LEGACY_LAYOUT_REFUSED_EXIT_CODE, LegacyLayoutRefusedError, } from "./legacy-detect.js";
@@ -67,6 +68,46 @@ function readRecordedDepositVersion(deftDir) {
67
68
  return null;
68
69
  }
69
70
  }
71
+ /** Prior `managed_by` provenance sentinel from the deposit manifest, if any (#2056). */
72
+ function readRecordedManagedBy(deftDir) {
73
+ const manifestPath = join(deftDir, "VERSION");
74
+ if (!existsSync(manifestPath))
75
+ return null;
76
+ try {
77
+ const value = (parseInstallManifest(readFileSync(manifestPath, "utf8")).managed_by ?? "").trim();
78
+ return value || null;
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ }
84
+ /**
85
+ * Regenerate the bare `.deft-version` derivative from the deposited content
86
+ * version in the same transaction as the payload swap (#2055). The canonical
87
+ * marker lives at `vbrief/.deft-version`; fall back to the project root only
88
+ * when `vbrief/` is absent. Never persist the dev fallback.
89
+ */
90
+ function syncBareVersionMarker(projectDir, version) {
91
+ const normalized = normalizeVersion(version);
92
+ if (!normalized || normalized === DEV_FALLBACK)
93
+ return;
94
+ const vbriefDir = join(projectDir, "vbrief");
95
+ let targetDir = projectDir;
96
+ try {
97
+ if (statSync(vbriefDir).isDirectory())
98
+ targetDir = vbriefDir;
99
+ }
100
+ catch {
101
+ // vbrief/ absent — write the root-level derivative instead
102
+ }
103
+ try {
104
+ mkdirSync(targetDir, { recursive: true });
105
+ writeFileSync(join(targetDir, ".deft-version"), `${normalized}\n`, "utf8");
106
+ }
107
+ catch {
108
+ // best-effort, mirrors install-upgrade marker write
109
+ }
110
+ }
70
111
  function isInstallerManagedPath(path) {
71
112
  if (INSTALLER_MANAGED_EXACT.has(path))
72
113
  return true;
@@ -208,6 +249,7 @@ export async function runRefreshDeposit(args, io, seams = {}) {
208
249
  const readPackageVersion = seams.readPackageVersion ?? readCorePackageVersion;
209
250
  const contentRoot = await resolveContent();
210
251
  const previousDepositVersion = readRecordedDepositVersion(deftDir);
252
+ const previousManagedBy = readRecordedManagedBy(deftDir);
211
253
  const engineVersion = readEngine();
212
254
  const contentVersion = readContentPackageVersion(contentRoot, readPackageVersion);
213
255
  const versionSkewNotice = buildVersionSkewNotice(engineVersion, contentVersion, previousDepositVersion);
@@ -221,8 +263,13 @@ export async function runRefreshDeposit(args, io, seams = {}) {
221
263
  installRoot: CANONICAL_INSTALL_ROOT,
222
264
  fetchedAt: nowIso(),
223
265
  fetchedBy: "directive-update",
266
+ ...(previousManagedBy ? { managedBy: previousManagedBy } : {}),
224
267
  };
225
268
  writeInstallManifest(projectDir, deftDir, manifestFields);
269
+ // #2055: regenerate the bare .deft-version derivative so it agrees with the
270
+ // freshly written manifest tag (otherwise doctor's manifest-agreement check
271
+ // fails and the operator must hand-edit the marker).
272
+ syncBareVersionMarker(projectDir, contentVersion);
226
273
  const agentsMdUpdated = writeAgentsMd(projectDir, deftDir, io);
227
274
  await depositNeutralization(projectDir, io);
228
275
  const readPorcelain = seams.gitPorcelain ?? gitPorcelain;
@@ -18,6 +18,13 @@ export interface InstallManifestFields {
18
18
  installRoot: string;
19
19
  fetchedAt: string;
20
20
  fetchedBy: string;
21
+ /**
22
+ * Provenance sentinel (#2056). When the prior manifest was stamped
23
+ * `managed_by: 'npm'` by the npm-migration path, callers thread it through a
24
+ * manifest rebuild so a routine `directive update` / `install-upgrade` does not
25
+ * regress provenance and re-arm the doctor signpost.
26
+ */
27
+ managedBy?: string;
21
28
  }
22
29
  export declare function buildInstallManifestText(fields: InstallManifestFields): string;
23
30
  export declare function writeInstallManifest(projectDir: string, deftDir: string, fields: InstallManifestFields): string;
@@ -187,12 +187,17 @@ export function buildInstallManifestText(fields) {
187
187
  effectiveTag = `v${effectiveTag}`;
188
188
  }
189
189
  const effectiveRef = fields.ref || effectiveTag;
190
- return (`ref: '${effectiveRef}'\n` +
190
+ let body = `ref: '${effectiveRef}'\n` +
191
191
  `sha: '${fields.sha}'\n` +
192
192
  `tag: '${effectiveTag}'\n` +
193
193
  `install_root: '${fields.installRoot}'\n` +
194
194
  `fetched_at: '${fields.fetchedAt}'\n` +
195
- `fetched_by: '${fields.fetchedBy}'\n`);
195
+ `fetched_by: '${fields.fetchedBy}'\n`;
196
+ const managedBy = fields.managedBy?.trim();
197
+ if (managedBy) {
198
+ body += `managed_by: '${managedBy}'\n`;
199
+ }
200
+ return body;
196
201
  }
197
202
  export function writeInstallManifest(projectDir, deftDir, fields) {
198
203
  const installRoot = fields.installRoot ||
@@ -1,11 +1,75 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
2
2
  import { join, relative, resolve } from "node:path";
3
- import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
3
+ import { locateManifest, manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
4
+ import { readCorePackageVersion } from "../engine-version.js";
4
5
  import { buildInstallManifestText, CANONICAL_INSTALL_ROOT, } from "../init-deposit/scaffold.js";
5
6
  import { agentsRefreshPlan } from "../platform/agents-md.js";
6
7
  import { DEV_FALLBACK } from "../platform/constants.js";
7
8
  import { resolveVersion } from "../platform/resolve-version.js";
8
9
  import { detectPreCutoverLegacy } from "../vbrief-validate/precutover.js";
10
+ const ENGINE_PACKAGE_FALLBACK = "0.0.0";
11
+ /**
12
+ * Resolve the framework version for an upgrade (#2053).
13
+ *
14
+ * `resolveVersion({ frameworkRoot })` reads env / install-manifest / .deft-version
15
+ * / git against the *engine* framework root. When `deft install-upgrade` runs from
16
+ * a global npm install, that root is the npm package directory — which carries no
17
+ * install manifest, no bare marker, and is not its own git repo — so the chain
18
+ * dead-ends at `0.0.0-dev`. Recover by auto-detecting the *consumer project's*
19
+ * deposited manifest, then fall back to the engine package.json version, before
20
+ * surrendering to the dev fallback.
21
+ */
22
+ function resolveUpgradeVersion(frameworkRoot, projectRoot) {
23
+ const primary = resolveVersion({ frameworkRoot });
24
+ if (primary !== DEV_FALLBACK)
25
+ return primary;
26
+ const manifestPath = locateManifest(projectRoot, null);
27
+ if (manifestPath) {
28
+ try {
29
+ const tag = manifestTagToVersion(parseInstallManifest(readFileSync(manifestPath, "utf8")));
30
+ if (tag)
31
+ return tag;
32
+ }
33
+ catch {
34
+ // fall through to package.json fallback
35
+ }
36
+ }
37
+ const pkgVersion = readCorePackageVersion();
38
+ if (pkgVersion && pkgVersion !== ENGINE_PACKAGE_FALLBACK)
39
+ return pkgVersion;
40
+ return DEV_FALLBACK;
41
+ }
42
+ /**
43
+ * The consumer's deposited framework install root (`.deft/core`, then legacy
44
+ * `deft/`), if present. Used as the AGENTS.md render root and manifest target so
45
+ * the refresh renders from the *deposited* templates that match the installed
46
+ * payload -- not the (possibly newer) templates bundled in a global npm engine.
47
+ */
48
+ function resolveInstallRoot(projectRoot) {
49
+ for (const candidate of [join(projectRoot, ".deft", "core"), join(projectRoot, "deft")]) {
50
+ try {
51
+ if (statSync(candidate).isDirectory())
52
+ return candidate;
53
+ }
54
+ catch {
55
+ // try next candidate
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ /** Prior `managed_by` provenance sentinel from an install manifest, if any (#2056). */
61
+ function readManagedByAt(installRoot) {
62
+ const manifestPath = join(installRoot, "VERSION");
63
+ if (!existsSync(manifestPath) || !statSync(manifestPath).isFile())
64
+ return null;
65
+ try {
66
+ const value = (parseInstallManifest(readFileSync(manifestPath, "utf8")).managed_by ?? "").trim();
67
+ return value || null;
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
9
73
  function versionMarkerPaths(projectRoot) {
10
74
  return [join(projectRoot, "vbrief", ".deft-version"), join(projectRoot, ".deft-version")];
11
75
  }
@@ -46,6 +110,7 @@ function deriveInstallRootString(installRoot, projectRoot) {
46
110
  function writeInstallManifestAt(installRoot, projectRoot, version) {
47
111
  if (version.replace(/^v/, "") === DEV_FALLBACK)
48
112
  return null;
113
+ const priorManagedBy = readManagedByAt(installRoot);
49
114
  const fields = {
50
115
  ref: version.startsWith("v") ? version : `v${version}`,
51
116
  sha: "content-package",
@@ -53,6 +118,7 @@ function writeInstallManifestAt(installRoot, projectRoot, version) {
53
118
  installRoot: deriveInstallRootString(installRoot, projectRoot),
54
119
  fetchedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
55
120
  fetchedBy: "deft-upgrade",
121
+ ...(priorManagedBy ? { managedBy: priorManagedBy } : {}),
56
122
  };
57
123
  try {
58
124
  mkdirSync(installRoot, { recursive: true });
@@ -111,13 +177,19 @@ function runAgentsRefresh(projectRoot, frameworkRoot, io) {
111
177
  export function runInstallUpgrade(args, io) {
112
178
  const projectRoot = resolve(args.projectRoot);
113
179
  const frameworkRoot = resolve(args.frameworkRoot);
114
- const version = resolveVersion({ frameworkRoot });
180
+ const version = resolveUpgradeVersion(frameworkRoot, projectRoot);
115
181
  const normalizedVersion = version.startsWith("v") ? version.slice(1) : version;
182
+ // Render AGENTS.md from the deposited install root so the managed section
183
+ // matches the installed payload, not the engine's bundled templates (which a
184
+ // global npm `deft` may carry at a different version). Falls back to the engine
185
+ // framework root when no deposit is present.
186
+ const installRoot = resolveInstallRoot(projectRoot);
187
+ const agentsRoot = installRoot ?? frameworkRoot;
116
188
  io.writeOut(`Deft CLI v${normalizedVersion} - Upgrade\n\n`);
117
189
  const recorded = readVersionMarker(projectRoot);
118
190
  if (recorded === normalizedVersion) {
119
191
  io.writeOut(`Project already at ${normalizedVersion}. Nothing to do.\n`);
120
- return runAgentsRefresh(projectRoot, frameworkRoot, io);
192
+ return runAgentsRefresh(projectRoot, agentsRoot, io);
121
193
  }
122
194
  const legacy = detectPreCutoverLegacy(projectRoot);
123
195
  if (legacy.length > 0) {
@@ -126,21 +198,25 @@ export function runInstallUpgrade(args, io) {
126
198
  const vbriefDir = join(projectRoot, "vbrief");
127
199
  const targetDir = existsSync(vbriefDir) && statSync(vbriefDir).isDirectory() ? vbriefDir : projectRoot;
128
200
  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
- }
201
+ const writtenManifestPath = installRoot !== null
202
+ ? writeInstallManifestAt(installRoot, projectRoot, normalizedVersion)
203
+ : null;
136
204
  migrateLegacyInstallManifest(projectRoot, writtenManifestPath);
137
- if (recorded === null) {
205
+ if (normalizedVersion === DEV_FALLBACK) {
206
+ // #2053: the marker + manifest writers above no-op on the dev fallback, so do
207
+ // not claim a marker update that never happened.
208
+ io.writeOut("Could not resolve a published framework version (resolved 0.0.0-dev); " +
209
+ ".deft-version marker and install manifest were left unchanged. On a consumer " +
210
+ "install, ensure <project>/.deft/core/VERSION carries a real tag, or upgrade the " +
211
+ "engine with `npm i -g @deftai/directive@latest`.\n");
212
+ }
213
+ else if (recorded === null) {
138
214
  io.writeOut(`Recorded framework version ${normalizedVersion} in .deft-version.\n`);
139
215
  }
140
216
  else {
141
217
  io.writeOut(`Updated .deft-version from ${recorded} to ${normalizedVersion}.\n`);
142
218
  }
143
219
  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);
220
+ return runAgentsRefresh(projectRoot, agentsRoot, io);
145
221
  }
146
222
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.61.0",
3
+ "version": "0.61.1",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -221,8 +221,8 @@
221
221
  "provenance": true
222
222
  },
223
223
  "dependencies": {
224
- "@deftai/directive-content": "^0.61.0",
225
- "@deftai/directive-types": "^0.61.0"
224
+ "@deftai/directive-content": "^0.61.1",
225
+ "@deftai/directive-types": "^0.61.1"
226
226
  },
227
227
  "scripts": {
228
228
  "build": "tsc -b",