@deftai/directive-core 0.67.0 → 0.68.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 (90) hide show
  1. package/dist/check/index.d.ts +1 -1
  2. package/dist/check/index.js +1 -1
  3. package/dist/check/orchestrator.d.ts +14 -4
  4. package/dist/check/orchestrator.js +30 -6
  5. package/dist/doctor/checks.js +11 -11
  6. package/dist/doctor/constants.d.ts +2 -0
  7. package/dist/doctor/constants.js +2 -0
  8. package/dist/doctor/payload-staleness.js +15 -6
  9. package/dist/forward-coverage/evaluate.d.ts +55 -0
  10. package/dist/forward-coverage/evaluate.js +254 -0
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.js +1 -0
  13. package/dist/init-deposit/constants.d.ts +14 -0
  14. package/dist/init-deposit/constants.js +10 -0
  15. package/dist/init-deposit/hygiene.d.ts +50 -0
  16. package/dist/init-deposit/hygiene.js +176 -0
  17. package/dist/init-deposit/init-deposit.d.ts +1 -0
  18. package/dist/init-deposit/init-deposit.js +7 -1
  19. package/dist/init-deposit/refresh.d.ts +2 -0
  20. package/dist/init-deposit/refresh.js +48 -30
  21. package/dist/init-deposit/scaffold.d.ts +3 -4
  22. package/dist/init-deposit/scaffold.js +3 -24
  23. package/dist/integration-e2e/helpers.d.ts +0 -1
  24. package/dist/integration-e2e/helpers.js +3 -7
  25. package/dist/layout/lifecycle-ref.d.ts +15 -0
  26. package/dist/layout/lifecycle-ref.js +55 -0
  27. package/dist/platform/agents-md.d.ts +28 -0
  28. package/dist/platform/agents-md.js +34 -0
  29. package/dist/policy/resolve.js +56 -46
  30. package/dist/pr-merge-readiness/compute.d.ts +10 -0
  31. package/dist/pr-merge-readiness/compute.js +53 -17
  32. package/dist/pr-merge-readiness/index.d.ts +2 -1
  33. package/dist/pr-merge-readiness/index.js +1 -0
  34. package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
  35. package/dist/pr-merge-readiness/mergeability.js +105 -0
  36. package/dist/pr-monitor/index.d.ts +1 -1
  37. package/dist/pr-monitor/index.js +1 -1
  38. package/dist/pr-monitor/monitor.d.ts +8 -2
  39. package/dist/pr-monitor/monitor.js +35 -6
  40. package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
  41. package/dist/pr-wait-mergeable/wrappers.js +13 -5
  42. package/dist/pr-watch/constants.d.ts +33 -0
  43. package/dist/pr-watch/constants.js +33 -0
  44. package/dist/pr-watch/index.d.ts +6 -0
  45. package/dist/pr-watch/index.js +6 -0
  46. package/dist/pr-watch/main.d.ts +21 -0
  47. package/dist/pr-watch/main.js +206 -0
  48. package/dist/pr-watch/probe.d.ts +13 -0
  49. package/dist/pr-watch/probe.js +100 -0
  50. package/dist/pr-watch/types.d.ts +47 -0
  51. package/dist/pr-watch/types.js +2 -0
  52. package/dist/pr-watch/watch.d.ts +12 -0
  53. package/dist/pr-watch/watch.js +122 -0
  54. package/dist/render/project-render.js +114 -89
  55. package/dist/scope/capacity-stamp.d.ts +24 -2
  56. package/dist/scope/capacity-stamp.js +80 -28
  57. package/dist/scope/decomposed-refs.js +2 -2
  58. package/dist/scope/project-definition-sync.js +3 -3
  59. package/dist/scope/vbrief-ref.d.ts +2 -1
  60. package/dist/scope/vbrief-ref.js +3 -2
  61. package/dist/spec-authority/constants.d.ts +6 -0
  62. package/dist/spec-authority/constants.js +11 -0
  63. package/dist/spec-authority/resolver.js +3 -3
  64. package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
  65. package/dist/swarm/finalize-cohort-cli.js +100 -0
  66. package/dist/swarm/finalize-cohort.d.ts +39 -0
  67. package/dist/swarm/finalize-cohort.js +589 -0
  68. package/dist/swarm/index.d.ts +2 -0
  69. package/dist/swarm/index.js +2 -0
  70. package/dist/swarm/launch.d.ts +7 -0
  71. package/dist/swarm/launch.js +17 -5
  72. package/dist/triage/queue/index.d.ts +1 -0
  73. package/dist/triage/queue/index.js +1 -0
  74. package/dist/triage/queue/project.d.ts +6 -1
  75. package/dist/triage/queue/project.js +9 -4
  76. package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
  77. package/dist/triage/queue/reconcile-live-state.js +54 -0
  78. package/dist/triage/scope/mutations-core.js +91 -81
  79. package/dist/triage/scope/resolve.js +4 -3
  80. package/dist/triage/scope-drift/add-ignore.js +33 -32
  81. package/dist/triage/scope-drift/scope-rules.js +3 -3
  82. package/dist/triage/subscribe/index.js +51 -45
  83. package/dist/triage/welcome/writers.js +79 -64
  84. package/dist/vbrief-build/project-definition-io.js +8 -3
  85. package/dist/vbrief-validate/paths.js +8 -4
  86. package/dist/vbrief-validate/precutover.d.ts +1 -1
  87. package/dist/vbrief-validate/precutover.js +12 -10
  88. package/package.json +7 -7
  89. package/dist/install-upgrade/index.d.ts +0 -13
  90. package/dist/install-upgrade/index.js +0 -251
@@ -2,6 +2,7 @@ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renam
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { hasArtifactSuffix, resolveLifecycleFolder, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
4
4
  import { migrateLegacyPolicyKey, PLAN_POLICY_KEY } from "../../policy/plan-extensions.js";
5
+ import { projectDefinitionMutationLock } from "../../vbrief-build/project-definition-io.js";
5
6
  import { AUDIT_LOG_REL_PATH, DEFAULT_RELIEF_AGE_DAYS, DEFAULT_WIP_CAP, SUBSCRIPTION_PRESETS, WELCOME_AUDIT_TAG, } from "./constants.js";
6
7
  function projectDefinitionPath(projectRoot) {
7
8
  return resolveProjectDefinitionPath(projectRoot);
@@ -27,77 +28,91 @@ function atomicWrite(path, data) {
27
28
  renameSync(tmp, path);
28
29
  }
29
30
  export function writeTriageScope(projectRoot, rules, options = { presetLabel: "custom" }) {
30
- const path = projectDefinitionPath(projectRoot);
31
- if (!existsSync(path))
32
- throw new Error(`PROJECT-DEFINITION not found at ${path}`);
33
- const data = JSON.parse(readFileSync(path, "utf8"));
34
- const plan = data.plan;
35
- if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
36
- throw new Error("PROJECT-DEFINITION 'plan' is not an object");
37
- }
38
- const planRec = plan;
39
- migrateLegacyPolicyKey(planRec);
40
- let policy = planRec[PLAN_POLICY_KEY];
41
- if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
42
- policy = {};
43
- planRec[PLAN_POLICY_KEY] = policy;
44
- }
45
- const policyRec = policy;
46
- const previous = policyRec.triageScope;
47
- policyRec.triageScope = rules;
48
- atomicWrite(path, data);
49
- const changed = JSON.stringify(previous) !== JSON.stringify(rules);
50
- const actor = options.actor ?? WELCOME_AUDIT_TAG;
51
- const auditEntry = [
52
- `actor=${actor}`,
53
- "field=plan.policy.triageScope",
54
- `preset=${options.presetLabel}`,
55
- `rule_count=${rules.length}`,
56
- `changed=${changed ? "true" : "false"}`,
57
- ].join(" ");
58
- appendAuditEntry(projectRoot, auditEntry);
59
- return [changed, auditEntry];
31
+ // Serialise read-modify-write + audit append under the shared lock (#1260).
32
+ return projectDefinitionMutationLock(projectRoot, () => {
33
+ const path = projectDefinitionPath(projectRoot);
34
+ if (!existsSync(path))
35
+ throw new Error(`PROJECT-DEFINITION not found at ${path}`);
36
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
37
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
38
+ throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
39
+ }
40
+ const data = parsed;
41
+ const plan = data.plan;
42
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
43
+ throw new Error("PROJECT-DEFINITION 'plan' is not an object");
44
+ }
45
+ const planRec = plan;
46
+ migrateLegacyPolicyKey(planRec);
47
+ let policy = planRec[PLAN_POLICY_KEY];
48
+ if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
49
+ policy = {};
50
+ planRec[PLAN_POLICY_KEY] = policy;
51
+ }
52
+ const policyRec = policy;
53
+ const previous = policyRec.triageScope;
54
+ policyRec.triageScope = rules;
55
+ atomicWrite(path, data);
56
+ const changed = JSON.stringify(previous) !== JSON.stringify(rules);
57
+ const actor = options.actor ?? WELCOME_AUDIT_TAG;
58
+ const auditEntry = [
59
+ `actor=${actor}`,
60
+ "field=plan.policy.triageScope",
61
+ `preset=${options.presetLabel}`,
62
+ `rule_count=${rules.length}`,
63
+ `changed=${changed ? "true" : "false"}`,
64
+ ].join(" ");
65
+ appendAuditEntry(projectRoot, auditEntry);
66
+ return [changed, auditEntry];
67
+ });
60
68
  }
61
69
  export function writeWipCap(projectRoot, wipCap, options = {}) {
62
70
  if (!Number.isInteger(wipCap) || wipCap < 1) {
63
71
  throw new Error(`wipCap must be a positive int, got ${JSON.stringify(wipCap)}`);
64
72
  }
65
- const path = projectDefinitionPath(projectRoot);
66
- if (!existsSync(path))
67
- throw new Error(`PROJECT-DEFINITION not found at ${path}`);
68
- const data = JSON.parse(readFileSync(path, "utf8"));
69
- const plan = data.plan;
70
- if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
71
- throw new Error("PROJECT-DEFINITION 'plan' is not an object");
72
- }
73
- const planRec = plan;
74
- migrateLegacyPolicyKey(planRec);
75
- let policy = planRec[PLAN_POLICY_KEY];
76
- if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
77
- policy = {};
78
- planRec[PLAN_POLICY_KEY] = policy;
79
- }
80
- const policyRec = policy;
81
- const previous = policyRec.wipCap;
82
- const actor = options.actor ?? WELCOME_AUDIT_TAG;
83
- if (previous === undefined && wipCap === DEFAULT_WIP_CAP) {
84
- return [false, ""];
85
- }
86
- if (previous !== undefined && wipCap === DEFAULT_WIP_CAP) {
87
- delete policyRec.wipCap;
73
+ // Serialise read-modify-write + audit append under the shared lock (#1260).
74
+ return projectDefinitionMutationLock(projectRoot, () => {
75
+ const path = projectDefinitionPath(projectRoot);
76
+ if (!existsSync(path))
77
+ throw new Error(`PROJECT-DEFINITION not found at ${path}`);
78
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
79
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
80
+ throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
81
+ }
82
+ const data = parsed;
83
+ const plan = data.plan;
84
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
85
+ throw new Error("PROJECT-DEFINITION 'plan' is not an object");
86
+ }
87
+ const planRec = plan;
88
+ migrateLegacyPolicyKey(planRec);
89
+ let policy = planRec[PLAN_POLICY_KEY];
90
+ if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
91
+ policy = {};
92
+ planRec[PLAN_POLICY_KEY] = policy;
93
+ }
94
+ const policyRec = policy;
95
+ const previous = policyRec.wipCap;
96
+ const actor = options.actor ?? WELCOME_AUDIT_TAG;
97
+ if (previous === undefined && wipCap === DEFAULT_WIP_CAP) {
98
+ return [false, ""];
99
+ }
100
+ if (previous !== undefined && wipCap === DEFAULT_WIP_CAP) {
101
+ delete policyRec.wipCap;
102
+ atomicWrite(path, data);
103
+ const auditEntry = `actor=${actor} field=plan.policy.wipCap action=cleared-to-default value=${wipCap} ` +
104
+ `previous=${JSON.stringify(previous)} changed=true`;
105
+ appendAuditEntry(projectRoot, auditEntry);
106
+ return [true, auditEntry];
107
+ }
108
+ policyRec.wipCap = wipCap;
88
109
  atomicWrite(path, data);
89
- const auditEntry = `actor=${actor} field=plan.policy.wipCap action=cleared-to-default value=${wipCap} ` +
90
- `previous=${JSON.stringify(previous)} changed=true`;
110
+ const changed = previous !== wipCap;
111
+ const auditEntry = `actor=${actor} field=plan.policy.wipCap value=${wipCap} previous=${JSON.stringify(previous)} ` +
112
+ `changed=${changed ? "true" : "false"}`;
91
113
  appendAuditEntry(projectRoot, auditEntry);
92
- return [true, auditEntry];
93
- }
94
- policyRec.wipCap = wipCap;
95
- atomicWrite(path, data);
96
- const changed = previous !== wipCap;
97
- const auditEntry = `actor=${actor} field=plan.policy.wipCap value=${wipCap} previous=${JSON.stringify(previous)} ` +
98
- `changed=${changed ? "true" : "false"}`;
99
- appendAuditEntry(projectRoot, auditEntry);
100
- return [changed, auditEntry];
114
+ return [changed, auditEntry];
115
+ });
101
116
  }
102
117
  function daysInPending(path, now) {
103
118
  try {
@@ -1,6 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, writeSync, } from "node:fs";
3
- import { dirname, join } from "node:path";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { resolveProjectDefinitionPath } from "../layout/resolve.js";
4
5
  import { PROJECT_DEFINITION_REL_PATH } from "./constants.js";
5
6
  import { pythonJsonPretty } from "./json.js";
6
7
  import { ProjectDefinitionIOError } from "./types.js";
@@ -18,8 +19,12 @@ function defaultSleep(ms) {
18
19
  export function projectDefinitionMutationLock(projectRoot, fn, deps = {}) {
19
20
  const sleepMs = deps.sleepMs ?? defaultSleep;
20
21
  const now = deps.now ?? Date.now;
21
- const path = projectDefinitionPath(projectRoot);
22
- const lockPath = join(dirname(path), `${path.split(/[/\\]/).pop()}.lock`);
22
+ // Derive the sidecar lock path from the layout-aware resolved PROJECT-DEFINITION
23
+ // path (xbrief/ when migrated, else vbrief/) so the lock lives next to the real
24
+ // artifact and every mutator sharing a project root contends on the same lock,
25
+ // instead of the constant vbrief/ path which would strand a stray lock (#1260).
26
+ const path = resolveProjectDefinitionPath(resolve(projectRoot));
27
+ const lockPath = `${path}.lock`;
23
28
  mkdirSync(dirname(lockPath), { recursive: true });
24
29
  if (mutationThreadLock.held) {
25
30
  throw new Error("project definition mutation lock is not reentrant");
@@ -1,4 +1,5 @@
1
1
  import { basename, relative, resolve, sep } from "node:path";
2
+ import { resolveLifecycleArtifactRef } from "../layout/lifecycle-ref.js";
2
3
  import { hasArtifactSuffix, stripArtifactSuffix } from "../layout/resolve.js";
3
4
  /** Return true when ``child`` resolves under ``parent``. */
4
5
  export function isRelativeTo(child, parent) {
@@ -15,14 +16,17 @@ export function resolveRefPath(uri, vbriefDir) {
15
16
  if (typeof uri !== "string") {
16
17
  return null;
17
18
  }
19
+ let rel;
18
20
  if (uri.startsWith("file://")) {
19
- const rel = uri.slice("file://".length);
20
- return resolve(vbriefDir, rel);
21
+ rel = uri.slice("file://".length);
21
22
  }
22
- if (uri.startsWith("http://") || uri.startsWith("https://") || uri.startsWith("#")) {
23
+ else if (uri.startsWith("http://") || uri.startsWith("https://") || uri.startsWith("#")) {
23
24
  return null;
24
25
  }
25
- return resolve(vbriefDir, uri);
26
+ else {
27
+ rel = uri;
28
+ }
29
+ return resolveLifecycleArtifactRef(rel, vbriefDir);
26
30
  }
27
31
  /** Split like Python ``str.split(sep, maxsplit)`` (maxsplit splits, not result length). */
28
32
  function splitMax(value, sep, maxsplit) {
@@ -4,7 +4,7 @@ export { DEPRECATION_SENTINEL as DEPRECATED_REDIRECT_SENTINEL };
4
4
  export declare function missingLifecycleFolders(projectRoot: string): string[];
5
5
  /** Return true when markdown content is a migration redirect stub. */
6
6
  export declare function isDeprecationRedirect(content: string): boolean;
7
- /** Full-spec generated export (specification.vbrief.json source line). */
7
+ /** Full-spec generated export (layout-resolved specification artifact source line). */
8
8
  export declare function isGeneratedSpecificationExport(projectRoot: string, content: string): boolean;
9
9
  /** Return true for a fully current generated spec export (full-spec or greenfield). */
10
10
  export declare function isCurrentGeneratedSpecification(projectRoot: string, content: string): boolean;
@@ -1,15 +1,15 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { GENERATED_SPEC_PURPOSE, GENERATED_SPEC_SOURCE_SPEC } from "../spec-authority/constants.js";
3
+ import { resolveLifecycleLayout, resolveLifecycleRoot, resolveSpecArtifactPath, } from "../layout/resolve.js";
4
+ import { contentHasGeneratedSpecSource, GENERATED_SPEC_PURPOSE, } from "../spec-authority/constants.js";
4
5
  import { isFullSpecState, isGreenfieldSpecExport } from "../spec-authority/resolver.js";
5
6
  import { DEPRECATION_SENTINEL } from "../vbrief-build/constants.js";
6
7
  export { DEPRECATION_SENTINEL as DEPRECATED_REDIRECT_SENTINEL };
7
8
  const DEPRECATION_REDIRECT_PURPOSE = "<!-- Purpose: deprecation redirect -->";
8
- const SPEC_SOURCE_RELPATH = join("vbrief", "specification.vbrief.json");
9
9
  const LIFECYCLE_FOLDERS = ["proposed", "pending", "active", "completed", "cancelled"];
10
10
  export function missingLifecycleFolders(projectRoot) {
11
- const vbriefRoot = join(projectRoot, "vbrief");
12
- return LIFECYCLE_FOLDERS.filter((folder) => !existsSync(join(vbriefRoot, folder)));
11
+ const lifecycleRoot = resolveLifecycleRoot(projectRoot);
12
+ return LIFECYCLE_FOLDERS.filter((folder) => !existsSync(join(lifecycleRoot, folder)));
13
13
  }
14
14
  function hasCompleteLifecycle(projectRoot) {
15
15
  return missingLifecycleFolders(projectRoot).length === 0;
@@ -18,11 +18,11 @@ function hasCompleteLifecycle(projectRoot) {
18
18
  export function isDeprecationRedirect(content) {
19
19
  return content.includes(DEPRECATION_SENTINEL) || content.includes(DEPRECATION_REDIRECT_PURPOSE);
20
20
  }
21
- /** Full-spec generated export (specification.vbrief.json source line). */
21
+ /** Full-spec generated export (layout-resolved specification artifact source line). */
22
22
  export function isGeneratedSpecificationExport(projectRoot, content) {
23
23
  return (content.includes(GENERATED_SPEC_PURPOSE) &&
24
- content.includes(GENERATED_SPEC_SOURCE_SPEC) &&
25
- existsSync(join(projectRoot, SPEC_SOURCE_RELPATH)));
24
+ contentHasGeneratedSpecSource(content) &&
25
+ existsSync(resolveSpecArtifactPath(projectRoot)));
26
26
  }
27
27
  /** Return true for a fully current generated spec export (full-spec or greenfield). */
28
28
  export function isCurrentGeneratedSpecification(projectRoot, content) {
@@ -56,6 +56,8 @@ function rootMarkdownIsLegacy(projectRoot, filename, content) {
56
56
  return false;
57
57
  if (isGreenfieldSpecExport(projectRoot))
58
58
  return false;
59
+ if (isFullSpecState(projectRoot))
60
+ return false;
59
61
  }
60
62
  return filename === "SPECIFICATION.md" || filename === "PROJECT.md";
61
63
  }
@@ -89,11 +91,11 @@ export function detectPreCutover(projectRoot) {
89
91
  reasons.push("PROJECT.md is a pre-v0.20 hand-authored doc (not a deprecation redirect)");
90
92
  }
91
93
  }
92
- const vbriefRoot = join(projectRoot, "vbrief");
93
- if (existsSync(vbriefRoot)) {
94
+ const layout = resolveLifecycleLayout(projectRoot);
95
+ if (existsSync(layout.root)) {
94
96
  const missing = missingLifecycleFolders(projectRoot);
95
97
  if (missing.length > 0) {
96
- reasons.push(`vbrief/ is missing lifecycle folder(s): ${missing.join(", ")}`);
98
+ reasons.push(`${layout.artifactDir}/ is missing lifecycle folder(s): ${missing.join(", ")}`);
97
99
  }
98
100
  }
99
101
  return { preCutover: reasons.length > 0, reasons };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.67.0",
3
+ "version": "0.68.0",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -110,6 +110,10 @@
110
110
  "types": "./dist/pr-wait-mergeable/index.d.ts",
111
111
  "default": "./dist/pr-wait-mergeable/index.js"
112
112
  },
113
+ "./pr-watch": {
114
+ "types": "./dist/pr-watch/index.d.ts",
115
+ "default": "./dist/pr-watch/index.js"
116
+ },
113
117
  "./vbrief-build": {
114
118
  "types": "./dist/vbrief-build/index.d.ts",
115
119
  "default": "./dist/vbrief-build/index.js"
@@ -218,10 +222,6 @@
218
222
  "types": "./dist/umbrella-current-shape/index.d.ts",
219
223
  "default": "./dist/umbrella-current-shape/index.js"
220
224
  },
221
- "./install-upgrade": {
222
- "types": "./dist/install-upgrade/index.d.ts",
223
- "default": "./dist/install-upgrade/index.js"
224
- },
225
225
  "./task-surface": {
226
226
  "types": "./dist/task-surface/index.d.ts",
227
227
  "default": "./dist/task-surface/index.js"
@@ -245,8 +245,8 @@
245
245
  "provenance": true
246
246
  },
247
247
  "dependencies": {
248
- "@deftai/directive-content": "^0.67.0",
249
- "@deftai/directive-types": "^0.67.0",
248
+ "@deftai/directive-content": "^0.68.0",
249
+ "@deftai/directive-types": "^0.68.0",
250
250
  "archiver": "^8.0.0"
251
251
  },
252
252
  "scripts": {
@@ -1,13 +0,0 @@
1
- export interface InstallUpgradeArgs {
2
- readonly projectRoot: string;
3
- readonly frameworkRoot: string;
4
- readonly migrate?: boolean;
5
- readonly force?: boolean;
6
- }
7
- export interface InstallUpgradeIo {
8
- writeOut: (text: string) => void;
9
- writeErr: (text: string) => void;
10
- }
11
- /** Port of ``run upgrade`` / ``task install:upgrade`` for the consumer task surface (#1061 / #2022). */
12
- export declare function runInstallUpgrade(args: InstallUpgradeArgs, io: InstallUpgradeIo): number;
13
- //# sourceMappingURL=index.d.ts.map
@@ -1,251 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
2
- import { join, relative, resolve } from "node:path";
3
- import { locateManifest, manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
4
- import { readCorePackageVersion } from "../engine-version.js";
5
- import { buildInstallManifestText, CANONICAL_INSTALL_ROOT, } from "../init-deposit/scaffold.js";
6
- import { resolveLifecycleRoot } from "../layout/resolve.js";
7
- import { agentsRefreshPlan } from "../platform/agents-md.js";
8
- import { DEV_FALLBACK } from "../platform/constants.js";
9
- import { resolveVersion } from "../platform/resolve-version.js";
10
- import { detectPreCutoverLegacy, frozenPreCutoverMigrationGuidance, } from "../vbrief-validate/precutover.js";
11
- import { detectLegacyVbriefLayout, emitXbriefMigration, isPatchOnlyUpgrade, renderXbriefMigrationLine, runXbriefMigration, } from "../xbrief-migrate/index.js";
12
- const ENGINE_PACKAGE_FALLBACK = "0.0.0";
13
- /**
14
- * Resolve the framework version for an upgrade (#2053).
15
- *
16
- * `resolveVersion({ frameworkRoot })` reads env / install-manifest / .deft-version
17
- * / git against the *engine* framework root. When `deft install-upgrade` runs from
18
- * a global npm install, that root is the npm package directory — which carries no
19
- * install manifest, no bare marker, and is not its own git repo — so the chain
20
- * dead-ends at `0.0.0-dev`. Recover by auto-detecting the *consumer project's*
21
- * deposited manifest, then fall back to the engine package.json version, before
22
- * surrendering to the dev fallback.
23
- */
24
- function resolveUpgradeVersion(frameworkRoot, projectRoot) {
25
- const primary = resolveVersion({ frameworkRoot });
26
- if (primary !== DEV_FALLBACK)
27
- return primary;
28
- const manifestPath = locateManifest(projectRoot, null);
29
- if (manifestPath) {
30
- try {
31
- const tag = manifestTagToVersion(parseInstallManifest(readFileSync(manifestPath, "utf8")));
32
- if (tag)
33
- return tag;
34
- }
35
- catch {
36
- // fall through to package.json fallback
37
- }
38
- }
39
- const pkgVersion = readCorePackageVersion();
40
- if (pkgVersion && pkgVersion !== ENGINE_PACKAGE_FALLBACK)
41
- return pkgVersion;
42
- return DEV_FALLBACK;
43
- }
44
- /**
45
- * The consumer's deposited framework install root (`.deft/core`, then legacy
46
- * `deft/`), if present. Used as the AGENTS.md render root and manifest target so
47
- * the refresh renders from the *deposited* templates that match the installed
48
- * payload -- not the (possibly newer) templates bundled in a global npm engine.
49
- */
50
- function resolveInstallRoot(projectRoot) {
51
- for (const candidate of [join(projectRoot, ".deft", "core"), join(projectRoot, "deft")]) {
52
- try {
53
- if (statSync(candidate).isDirectory())
54
- return candidate;
55
- }
56
- catch {
57
- // try next candidate
58
- }
59
- }
60
- return null;
61
- }
62
- /** Prior `managed_by` provenance sentinel from an install manifest, if any (#2056). */
63
- function readManagedByAt(installRoot) {
64
- const manifestPath = join(installRoot, "VERSION");
65
- if (!existsSync(manifestPath) || !statSync(manifestPath).isFile())
66
- return null;
67
- try {
68
- const value = (parseInstallManifest(readFileSync(manifestPath, "utf8")).managed_by ?? "").trim();
69
- return value || null;
70
- }
71
- catch {
72
- return null;
73
- }
74
- }
75
- function versionMarkerPaths(projectRoot) {
76
- return [
77
- join(resolveLifecycleRoot(projectRoot), ".deft-version"),
78
- join(projectRoot, ".deft-version"),
79
- ];
80
- }
81
- function readVersionMarker(projectRoot) {
82
- for (const candidate of versionMarkerPaths(projectRoot)) {
83
- if (!existsSync(candidate) || !statSync(candidate).isFile())
84
- continue;
85
- try {
86
- const value = readFileSync(candidate, "utf8").trim();
87
- if (value)
88
- return value;
89
- }
90
- catch {
91
- // try next candidate
92
- }
93
- }
94
- return null;
95
- }
96
- function writeVersionMarker(targetDir, version) {
97
- if (version === DEV_FALLBACK)
98
- return;
99
- try {
100
- mkdirSync(targetDir, { recursive: true });
101
- writeFileSync(join(targetDir, ".deft-version"), `${version}\n`, "utf8");
102
- }
103
- catch {
104
- // best-effort, mirrors Python
105
- }
106
- }
107
- function deriveInstallRootString(installRoot, projectRoot) {
108
- try {
109
- return relative(projectRoot, installRoot).split("\\").join("/") || CANONICAL_INSTALL_ROOT;
110
- }
111
- catch {
112
- return resolve(installRoot).split("\\").join("/");
113
- }
114
- }
115
- function writeInstallManifestAt(installRoot, projectRoot, version) {
116
- if (version.replace(/^v/, "") === DEV_FALLBACK)
117
- return null;
118
- const priorManagedBy = readManagedByAt(installRoot);
119
- const fields = {
120
- ref: version.startsWith("v") ? version : `v${version}`,
121
- sha: "content-package",
122
- tag: version.startsWith("v") ? version : `v${version}`,
123
- installRoot: deriveInstallRootString(installRoot, projectRoot),
124
- fetchedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
125
- fetchedBy: "deft-upgrade",
126
- ...(priorManagedBy ? { managedBy: priorManagedBy } : {}),
127
- };
128
- try {
129
- mkdirSync(installRoot, { recursive: true });
130
- const body = buildInstallManifestText(fields);
131
- const path = join(installRoot, "VERSION");
132
- writeFileSync(path, body, "utf8");
133
- return path;
134
- }
135
- catch {
136
- return null;
137
- }
138
- }
139
- function migrateLegacyInstallManifest(projectRoot, canonicalManifestPath) {
140
- if (canonicalManifestPath === null)
141
- return;
142
- const canonical = resolve(canonicalManifestPath);
143
- const expectedParent = resolve(projectRoot, ".deft", "core");
144
- if (resolve(canonical, "..") !== expectedParent)
145
- return;
146
- const legacy = join(projectRoot, ".deft", "VERSION");
147
- if (!existsSync(legacy) || !statSync(legacy).isFile())
148
- return;
149
- try {
150
- const legacyVersion = manifestTagToVersion(parseInstallManifest(readFileSync(legacy, "utf8")));
151
- const canonicalVersion = manifestTagToVersion(parseInstallManifest(readFileSync(canonical, "utf8")));
152
- if (legacyVersion !== null && legacyVersion === canonicalVersion)
153
- return;
154
- renameSync(legacy, join(projectRoot, ".deft", "VERSION.premigrate"));
155
- }
156
- catch {
157
- // best-effort
158
- }
159
- }
160
- function runAgentsRefresh(projectRoot, frameworkRoot, io) {
161
- const plan = agentsRefreshPlan(projectRoot, { frameworkRoot });
162
- const state = String(plan.state ?? "unknown");
163
- if (state === "current") {
164
- io.writeOut("AGENTS.md managed section is current — no changes.\n");
165
- return 0;
166
- }
167
- if (state === "template-missing" || state === "template-malformed" || state === "unreadable") {
168
- io.writeErr(`agents:refresh failed: ${state}\n`);
169
- return 2;
170
- }
171
- const newContent = plan.new_content;
172
- if (typeof newContent !== "string") {
173
- io.writeErr("agents:refresh failed: plan produced no new_content\n");
174
- return 2;
175
- }
176
- const path = String(plan.path ?? join(projectRoot, "AGENTS.md"));
177
- writeFileSync(path, newContent, "utf8");
178
- io.writeOut(`AGENTS.md updated (state=${state}).\n`);
179
- return 0;
180
- }
181
- function handleLegacyXbriefLayout(args, io, recorded, normalizedVersion) {
182
- const detection = detectLegacyVbriefLayout(args.projectRoot);
183
- if (!detection.legacyLayout) {
184
- return 0;
185
- }
186
- io.writeOut(`${renderXbriefMigrationLine(args.projectRoot)}\n`);
187
- const patchInert = isPatchOnlyUpgrade(recorded, normalizedVersion);
188
- if (!args.migrate || patchInert) {
189
- return 0;
190
- }
191
- const outcome = runXbriefMigration({
192
- projectRoot: args.projectRoot,
193
- frameworkRoot: args.frameworkRoot,
194
- force: args.force,
195
- }, io);
196
- const code = emitXbriefMigration(outcome, io, { projectRoot: args.projectRoot });
197
- return code;
198
- }
199
- /** Port of ``run upgrade`` / ``task install:upgrade`` for the consumer task surface (#1061 / #2022). */
200
- export function runInstallUpgrade(args, io) {
201
- const projectRoot = resolve(args.projectRoot);
202
- const frameworkRoot = resolve(args.frameworkRoot);
203
- const version = resolveUpgradeVersion(frameworkRoot, projectRoot);
204
- const normalizedVersion = version.startsWith("v") ? version.slice(1) : version;
205
- // Render AGENTS.md from the deposited install root so the managed section
206
- // matches the installed payload, not the engine's bundled templates (which a
207
- // global npm `deft` may carry at a different version). Falls back to the engine
208
- // framework root when no deposit is present.
209
- const installRoot = resolveInstallRoot(projectRoot);
210
- const agentsRoot = installRoot ?? frameworkRoot;
211
- io.writeOut(`Deft CLI v${normalizedVersion} - Upgrade\n\n`);
212
- const recorded = readVersionMarker(projectRoot);
213
- if (recorded === normalizedVersion) {
214
- io.writeOut(`Project already at ${normalizedVersion}. Nothing to do.\n`);
215
- const xbriefCode = handleLegacyXbriefLayout(args, io, recorded, normalizedVersion);
216
- if (xbriefCode !== 0)
217
- return xbriefCode;
218
- return runAgentsRefresh(projectRoot, agentsRoot, io);
219
- }
220
- const legacy = detectPreCutoverLegacy(projectRoot);
221
- if (legacy.length > 0) {
222
- io.writeOut(`Pre-v0.20 document model detected (${legacy.join(", ")}). ${frozenPreCutoverMigrationGuidance()}\n`);
223
- }
224
- const vbriefDir = resolveLifecycleRoot(projectRoot);
225
- const targetDir = existsSync(vbriefDir) && statSync(vbriefDir).isDirectory() ? vbriefDir : projectRoot;
226
- writeVersionMarker(targetDir, normalizedVersion);
227
- const writtenManifestPath = installRoot !== null
228
- ? writeInstallManifestAt(installRoot, projectRoot, normalizedVersion)
229
- : null;
230
- migrateLegacyInstallManifest(projectRoot, writtenManifestPath);
231
- if (normalizedVersion === DEV_FALLBACK) {
232
- // #2053: the marker + manifest writers above no-op on the dev fallback, so do
233
- // not claim a marker update that never happened.
234
- io.writeOut("Could not resolve a published framework version (resolved 0.0.0-dev); " +
235
- ".deft-version marker and install manifest were left unchanged. On a consumer " +
236
- "install, ensure <project>/.deft/core/VERSION carries a real tag, or upgrade the " +
237
- "engine with `npm i -g @deftai/directive@latest`.\n");
238
- }
239
- else if (recorded === null) {
240
- io.writeOut(`Recorded framework version ${normalizedVersion} in .deft-version.\n`);
241
- }
242
- else {
243
- io.writeOut(`Updated .deft-version from ${recorded} to ${normalizedVersion}.\n`);
244
- }
245
- io.writeOut(`If legacy SPECIFICATION.md or PROJECT.md content remains, see UPGRADING.md § Frozen pre-v0.20 document-model migration (#2068).\n`);
246
- const xbriefCode = handleLegacyXbriefLayout(args, io, recorded, normalizedVersion);
247
- if (xbriefCode !== 0)
248
- return xbriefCode;
249
- return runAgentsRefresh(projectRoot, agentsRoot, io);
250
- }
251
- //# sourceMappingURL=index.js.map