@deftai/directive-core 0.66.2 → 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 (147) hide show
  1. package/dist/agents-md-advisory/evaluate.d.ts +44 -0
  2. package/dist/agents-md-advisory/evaluate.js +115 -0
  3. package/dist/agents-md-advisory/index.d.ts +2 -0
  4. package/dist/agents-md-advisory/index.js +2 -0
  5. package/dist/agents-md-budget/evaluate.d.ts +38 -0
  6. package/dist/agents-md-budget/evaluate.js +152 -0
  7. package/dist/agents-md-budget/index.d.ts +2 -0
  8. package/dist/agents-md-budget/index.js +2 -0
  9. package/dist/check/index.d.ts +1 -1
  10. package/dist/check/index.js +1 -1
  11. package/dist/check/orchestrator.d.ts +14 -4
  12. package/dist/check/orchestrator.js +30 -6
  13. package/dist/codebase/map.d.ts +1 -0
  14. package/dist/codebase/map.js +5 -2
  15. package/dist/doctor/checks.js +11 -11
  16. package/dist/doctor/constants.d.ts +7 -2
  17. package/dist/doctor/constants.js +17 -1
  18. package/dist/doctor/flags.js +5 -1
  19. package/dist/doctor/main.js +99 -8
  20. package/dist/doctor/payload-staleness.js +15 -6
  21. package/dist/doctor/types.d.ts +7 -0
  22. package/dist/forward-coverage/evaluate.d.ts +55 -0
  23. package/dist/forward-coverage/evaluate.js +254 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/init-deposit/constants.d.ts +14 -0
  27. package/dist/init-deposit/constants.js +10 -0
  28. package/dist/init-deposit/hygiene.d.ts +50 -0
  29. package/dist/init-deposit/hygiene.js +176 -0
  30. package/dist/init-deposit/init-deposit.d.ts +1 -0
  31. package/dist/init-deposit/init-deposit.js +7 -1
  32. package/dist/init-deposit/refresh.d.ts +2 -0
  33. package/dist/init-deposit/refresh.js +48 -30
  34. package/dist/init-deposit/scaffold.d.ts +3 -4
  35. package/dist/init-deposit/scaffold.js +3 -24
  36. package/dist/intake/github-auth-modes.d.ts +1 -0
  37. package/dist/intake/github-auth-modes.js +1 -0
  38. package/dist/intake/issue-ingest.d.ts +6 -2
  39. package/dist/intake/issue-ingest.js +65 -9
  40. package/dist/integration-e2e/helpers.d.ts +0 -1
  41. package/dist/integration-e2e/helpers.js +3 -7
  42. package/dist/layout/lifecycle-ref.d.ts +15 -0
  43. package/dist/layout/lifecycle-ref.js +55 -0
  44. package/dist/platform/agents-consumer-header.d.ts +13 -0
  45. package/dist/platform/agents-consumer-header.js +57 -0
  46. package/dist/platform/agents-md.d.ts +28 -0
  47. package/dist/platform/agents-md.js +38 -1
  48. package/dist/platform/index.d.ts +1 -0
  49. package/dist/platform/index.js +1 -0
  50. package/dist/policy/agents-md-advisory.d.ts +52 -0
  51. package/dist/policy/agents-md-advisory.js +63 -0
  52. package/dist/policy/agents-md-budget.d.ts +24 -0
  53. package/dist/policy/agents-md-budget.js +89 -0
  54. package/dist/policy/index.d.ts +1 -0
  55. package/dist/policy/index.js +1 -0
  56. package/dist/policy/resolve.js +56 -46
  57. package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
  58. package/dist/pr-merge-readiness/ci-gate.js +79 -0
  59. package/dist/pr-merge-readiness/compute.d.ts +15 -1
  60. package/dist/pr-merge-readiness/compute.js +129 -9
  61. package/dist/pr-merge-readiness/gh.d.ts +8 -0
  62. package/dist/pr-merge-readiness/gh.js +30 -5
  63. package/dist/pr-merge-readiness/index.d.ts +5 -1
  64. package/dist/pr-merge-readiness/index.js +3 -0
  65. package/dist/pr-merge-readiness/main.d.ts +3 -0
  66. package/dist/pr-merge-readiness/main.js +73 -6
  67. package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
  68. package/dist/pr-merge-readiness/mergeability.js +105 -0
  69. package/dist/pr-merge-readiness/output.js +20 -0
  70. package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
  71. package/dist/pr-merge-readiness/slizard-gate.js +119 -0
  72. package/dist/pr-monitor/index.d.ts +1 -1
  73. package/dist/pr-monitor/index.js +1 -1
  74. package/dist/pr-monitor/monitor.d.ts +8 -2
  75. package/dist/pr-monitor/monitor.js +35 -6
  76. package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
  77. package/dist/pr-wait-mergeable/wrappers.js +13 -5
  78. package/dist/pr-watch/constants.d.ts +33 -0
  79. package/dist/pr-watch/constants.js +33 -0
  80. package/dist/pr-watch/index.d.ts +6 -0
  81. package/dist/pr-watch/index.js +6 -0
  82. package/dist/pr-watch/main.d.ts +21 -0
  83. package/dist/pr-watch/main.js +206 -0
  84. package/dist/pr-watch/probe.d.ts +13 -0
  85. package/dist/pr-watch/probe.js +100 -0
  86. package/dist/pr-watch/types.d.ts +47 -0
  87. package/dist/pr-watch/types.js +2 -0
  88. package/dist/pr-watch/watch.d.ts +12 -0
  89. package/dist/pr-watch/watch.js +122 -0
  90. package/dist/preflight-cache/evaluate.d.ts +1 -0
  91. package/dist/preflight-cache/evaluate.js +17 -10
  92. package/dist/render/project-render.d.ts +16 -2
  93. package/dist/render/project-render.js +155 -97
  94. package/dist/scope/capacity-stamp.d.ts +24 -2
  95. package/dist/scope/capacity-stamp.js +80 -28
  96. package/dist/scope/decomposed-refs.js +2 -2
  97. package/dist/scope/project-definition-sync.js +3 -3
  98. package/dist/scope/vbrief-ref.d.ts +2 -1
  99. package/dist/scope/vbrief-ref.js +3 -2
  100. package/dist/spec-authority/constants.d.ts +6 -0
  101. package/dist/spec-authority/constants.js +11 -0
  102. package/dist/spec-authority/resolver.js +3 -3
  103. package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
  104. package/dist/swarm/finalize-cohort-cli.js +100 -0
  105. package/dist/swarm/finalize-cohort.d.ts +39 -0
  106. package/dist/swarm/finalize-cohort.js +589 -0
  107. package/dist/swarm/index.d.ts +2 -0
  108. package/dist/swarm/index.js +2 -0
  109. package/dist/swarm/launch.d.ts +13 -0
  110. package/dist/swarm/launch.js +19 -7
  111. package/dist/swarm/routing-verify.d.ts +1 -1
  112. package/dist/swarm/routing-verify.js +11 -10
  113. package/dist/swarm/routing.d.ts +9 -0
  114. package/dist/swarm/routing.js +46 -0
  115. package/dist/swarm/verify-review-clean-cli.js +25 -1
  116. package/dist/swarm/verify-review-clean.d.ts +9 -1
  117. package/dist/swarm/verify-review-clean.js +76 -3
  118. package/dist/triage/queue/index.d.ts +1 -0
  119. package/dist/triage/queue/index.js +1 -0
  120. package/dist/triage/queue/project.d.ts +6 -1
  121. package/dist/triage/queue/project.js +9 -4
  122. package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
  123. package/dist/triage/queue/reconcile-live-state.js +54 -0
  124. package/dist/triage/scope/mutations-core.js +91 -81
  125. package/dist/triage/scope/resolve.js +4 -3
  126. package/dist/triage/scope-drift/add-ignore.js +33 -32
  127. package/dist/triage/scope-drift/scope-rules.js +3 -3
  128. package/dist/triage/subscribe/index.js +51 -45
  129. package/dist/triage/welcome/writers.js +79 -64
  130. package/dist/vbrief-build/project-definition-io.js +8 -3
  131. package/dist/vbrief-validate/paths.js +8 -4
  132. package/dist/vbrief-validate/precutover.d.ts +1 -1
  133. package/dist/vbrief-validate/precutover.js +12 -10
  134. package/dist/verify-source/biome-config.d.ts +41 -0
  135. package/dist/verify-source/biome-config.js +97 -0
  136. package/dist/verify-source/index.d.ts +1 -0
  137. package/dist/verify-source/index.js +1 -0
  138. package/dist/xbrief-migrate/agents-header.d.ts +69 -0
  139. package/dist/xbrief-migrate/agents-header.js +179 -0
  140. package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
  141. package/dist/xbrief-migrate/fs-helpers.js +11 -0
  142. package/dist/xbrief-migrate/index.d.ts +1 -0
  143. package/dist/xbrief-migrate/index.js +1 -0
  144. package/dist/xbrief-migrate/migrate-project.js +29 -10
  145. package/package.json +15 -7
  146. package/dist/install-upgrade/index.d.ts +0 -13
  147. package/dist/install-upgrade/index.js +0 -251
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Scoped staging + installer-managed allowlist for TS-native init/update (#1453).
3
+ *
4
+ * Mirrors cmd/deft-install/hygiene.go + deposit.go installerManagedMatchers.
5
+ * Refs #1576, #1453, #1430.
6
+ */
7
+ import { execFileSync } from "node:child_process";
8
+ import { existsSync } from "node:fs";
9
+ import { join, relative } from "node:path";
10
+ import { gitPorcelain } from "../story-ready/git.js";
11
+ import { CANONICAL_INSTALL_ROOT } from "./constants.js";
12
+ export const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
13
+ export const CORE_GUARD_WORKFLOW_REL = ".github/workflows/deft-core-guard.yml";
14
+ const VBRIEF_LIFECYCLE_DIRS = ["proposed", "pending", "active", "completed", "cancelled"];
15
+ /** Single source of truth for installer-managed paths (#1440 / #1576). */
16
+ export function installerManagedMatchers() {
17
+ return [
18
+ { exact: "AGENTS.md" },
19
+ { prefix: ".agents/" },
20
+ { prefix: ".githooks/" },
21
+ { exact: ".gitattributes" },
22
+ { exact: ".gitignore" },
23
+ { exact: "greptile.json" },
24
+ { exact: CODEQL_CONFIG_REL },
25
+ { exact: CORE_GUARD_WORKFLOW_REL },
26
+ { exact: "Taskfile.yml" },
27
+ { exact: "vbrief/.deft-version" },
28
+ { exact: "vbrief/vbrief.md" },
29
+ { prefix: "vbrief/schemas/" },
30
+ { prefix: "vbrief/migration/" },
31
+ ...VBRIEF_LIFECYCLE_DIRS.map((sub) => ({ exact: `vbrief/${sub}/.gitkeep` })),
32
+ ];
33
+ }
34
+ function escapeEre(value) {
35
+ return value.replace(/[.^$*+?()[\]{}|\\]/g, "\\$&");
36
+ }
37
+ function matcherToEre(matcher) {
38
+ if (matcher.exact)
39
+ return `^${escapeEre(matcher.exact)}$`;
40
+ return `^${escapeEre(matcher.prefix ?? "")}`;
41
+ }
42
+ /** POSIX ERE alternation embedded in the deposited deft-core-guard workflow. */
43
+ export function installerManagedGuardEre() {
44
+ return installerManagedMatchers()
45
+ .map((matcher) => matcherToEre(matcher))
46
+ .join("|");
47
+ }
48
+ function matchesInstallerManaged(path, matchers) {
49
+ for (const matcher of matchers) {
50
+ if (matcher.exact && path === matcher.exact)
51
+ return true;
52
+ if (matcher.prefix && path.startsWith(matcher.prefix))
53
+ return true;
54
+ }
55
+ return false;
56
+ }
57
+ export function isInstallerManagedPath(path) {
58
+ return matchesInstallerManaged(path, installerManagedMatchers());
59
+ }
60
+ export function frameworkStagePaths(projectDir, deftDir, options = {}) {
61
+ const paths = [];
62
+ const seen = new Set();
63
+ const add = (rel) => {
64
+ const normalized = rel.replace(/\\/g, "/");
65
+ if (!normalized || normalized === "." || seen.has(normalized))
66
+ return;
67
+ if (!existsSync(join(projectDir, normalized)))
68
+ return;
69
+ seen.add(normalized);
70
+ paths.push(normalized);
71
+ };
72
+ const relDeft = relative(projectDir, deftDir);
73
+ if (relDeft && !relDeft.startsWith("..") && !relDeft.startsWith("/")) {
74
+ add(relDeft);
75
+ }
76
+ for (const matcher of installerManagedMatchers()) {
77
+ if (matcher.exact === "Taskfile.yml" && !options.includeTaskfile)
78
+ continue;
79
+ if (matcher.exact) {
80
+ add(matcher.exact);
81
+ }
82
+ else if (matcher.prefix) {
83
+ add(matcher.prefix.replace(/\/$/, ""));
84
+ }
85
+ }
86
+ return paths;
87
+ }
88
+ /** Best-effort scoped `git add` — never fails the install/update (#1453 Layer 2b). */
89
+ export function stageFrameworkPaths(projectDir, paths, seams = {}) {
90
+ if (paths.length === 0)
91
+ return { staged: false, error: null };
92
+ const readPorcelain = seams.gitPorcelain ?? gitPorcelain;
93
+ if (readPorcelain(projectDir) === null)
94
+ return { staged: false, error: null };
95
+ const runGitAdd = seams.runGitAdd ??
96
+ ((root, stagePaths) => {
97
+ execFileSync("git", ["add", ...stagePaths], {
98
+ cwd: root,
99
+ encoding: "utf8",
100
+ stdio: ["ignore", "pipe", "pipe"],
101
+ });
102
+ });
103
+ try {
104
+ runGitAdd(projectDir, paths);
105
+ return { staged: true, error: null };
106
+ }
107
+ catch (cause) {
108
+ const error = cause instanceof Error ? cause : new Error(String(cause));
109
+ return { staged: false, error };
110
+ }
111
+ }
112
+ export const COMMIT_HYGIENE_BRANCH_NAME = "chore/deft-framework-upgrade";
113
+ export function printCommitGuidance(io, paths, staged) {
114
+ if (paths.length === 0)
115
+ return;
116
+ const addCmd = `git add ${paths.join(" ")}`;
117
+ io.printf("\nCommit hygiene (#1453, #1671): keep the framework deposit in its OWN branch/PR.\n");
118
+ io.printf("Do NOT use `git add -A` -- mixing the payload with your own files trips the\n");
119
+ io.printf("deft-core-guard CI check.\n");
120
+ if (staged) {
121
+ io.printf("The installer already staged ONLY these framework + installer-managed paths:\n");
122
+ io.printf(` ${addCmd}\n`);
123
+ }
124
+ else {
125
+ io.printf("Stage ONLY these framework + installer-managed paths:\n");
126
+ io.printf(` ${addCmd}\n`);
127
+ }
128
+ io.printf("Then take the framework deposit through the full PR lifecycle so deft-core-guard\n");
129
+ io.printf("evaluates a clean, standalone PR:\n");
130
+ io.printf(` 1. Branch: git switch -c ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
131
+ io.printf(' 2. Commit: git commit -m "chore(deft): update framework payload"\n');
132
+ io.printf(` 3. Push: git push -u origin ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
133
+ io.printf(' 4. PR: gh pr create --fill --title "chore(deft): update framework payload"\n');
134
+ io.printf(" 5. Merge: gh pr merge --squash --delete-branch # after deft-core-guard passes\n");
135
+ }
136
+ function defaultCachedNames(projectDir) {
137
+ try {
138
+ const out = execFileSync("git", ["diff", "--cached", "--name-only", "-z"], {
139
+ cwd: projectDir,
140
+ encoding: "utf8",
141
+ stdio: ["ignore", "pipe", "pipe"],
142
+ });
143
+ return out
144
+ .split("\0")
145
+ .map((entry) => entry.trim().replace(/\\/g, "/"))
146
+ .filter(Boolean);
147
+ }
148
+ catch {
149
+ return [];
150
+ }
151
+ }
152
+ /**
153
+ * Narrow the candidate stage paths to those that actually have staged index
154
+ * changes. A candidate file matches when the cached name is identical; a
155
+ * candidate directory (e.g. ``.deft/core``) matches when any cached name lives
156
+ * beneath it. This keeps ``staged_paths`` honest for downstream automation --
157
+ * it reports what git actually staged, not every path passed to ``git add``.
158
+ */
159
+ function actuallyStagedPaths(stagePaths, cachedNames) {
160
+ return stagePaths.filter((candidate) => cachedNames.some((name) => name === candidate || name.startsWith(`${candidate}/`)));
161
+ }
162
+ export function depositStagePaths(projectDir, options = {}) {
163
+ const deftDir = join(projectDir, CANONICAL_INSTALL_ROOT);
164
+ const stagePaths = frameworkStagePaths(projectDir, deftDir, {
165
+ includeTaskfile: options.includeTaskfile ?? false,
166
+ });
167
+ const { staged } = stageFrameworkPaths(projectDir, stagePaths, options);
168
+ const readCachedNames = options.readCachedNames ?? defaultCachedNames;
169
+ const cachedNames = staged ? readCachedNames(projectDir) : [];
170
+ return {
171
+ stagePaths,
172
+ staged,
173
+ stagedPaths: staged ? actuallyStagedPaths(stagePaths, cachedNames) : [],
174
+ };
175
+ }
176
+ //# sourceMappingURL=hygiene.js.map
@@ -19,6 +19,7 @@ export interface InitDepositResult {
19
19
  readonly taskfileWired: boolean;
20
20
  readonly configDir: string;
21
21
  readonly legacyLayout: boolean;
22
+ readonly stagedPaths: string[];
22
23
  }
23
24
  export interface InitDepositSeams {
24
25
  resolveContentRoot?: () => Promise<string>;
@@ -13,6 +13,7 @@ import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
13
13
  import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
14
14
  import { readCorePackageVersion } from "../engine-version.js";
15
15
  import { ensureInitGitignoreLines, reconstituteDepositFromContent } from "./gitignore.js";
16
+ import { depositStagePaths, printCommitGuidance } from "./hygiene.js";
16
17
  import { buildLegacyRefusalJson, buildLegacyRefusalMessage, detectLegacyLayout, LEGACY_LAYOUT_REFUSED_EXIT_CODE, LegacyLayoutRefusedError, } from "./legacy-detect.js";
17
18
  import { printMigrateNudgeIfNeeded } from "./migrate.js";
18
19
  import { CANONICAL_INSTALL_ROOT, depositNeutralization, ensureTaskfile, writeAgentsMd, writeAgentsSkills, writeConsumerGitHooks, writeConsumerVbrief, writeInstallManifest, } from "./scaffold.js";
@@ -93,7 +94,7 @@ export function buildInstallSummaryJson(result, options) {
93
94
  strategy: "vendor",
94
95
  dirty_tree: false,
95
96
  dirty_files: [],
96
- staged_paths: [],
97
+ staged_paths: result.stagedPaths,
97
98
  backup_path: "",
98
99
  previous_version: "",
99
100
  };
@@ -150,6 +151,10 @@ export async function runInitDeposit(args, io, seams = {}) {
150
151
  taskfileWired = ensureTaskfile(projectDir, io);
151
152
  }
152
153
  const configDir = createUserConfigDir(io);
154
+ const { stagePaths, staged, stagedPaths } = depositStagePaths(projectDir, {
155
+ includeTaskfile: taskfileWired,
156
+ });
157
+ printCommitGuidance(io, stagePaths, staged);
153
158
  return {
154
159
  projectDir,
155
160
  deftDir,
@@ -157,6 +162,7 @@ export async function runInitDeposit(args, io, seams = {}) {
157
162
  taskfileWired,
158
163
  configDir,
159
164
  legacyLayout: false,
165
+ stagedPaths,
160
166
  };
161
167
  }
162
168
  /** CLI-facing wrapper: runs deposit, emits JSON or wizard UX, returns exit code. */
@@ -22,6 +22,8 @@ export interface RefreshDepositResult {
22
22
  readonly agentsMdUpdated: boolean;
23
23
  readonly versionSkewNotice: string | null;
24
24
  readonly legacyLayout: boolean;
25
+ readonly taskfileWired: boolean;
26
+ readonly stagedPaths: string[];
25
27
  }
26
28
  export interface RefreshDepositSeams {
27
29
  resolveContentRoot?: () => Promise<string>;
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Refs #1942, #1430, #1671.
9
9
  */
10
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
10
+ import { existsSync, mkdirSync, readFileSync, renameSync, 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";
@@ -17,31 +17,11 @@ import { readCorePackageVersion } from "../engine-version.js";
17
17
  import { resolveLifecycleRoot } from "../layout/resolve.js";
18
18
  import { DEV_FALLBACK } from "../platform/constants.js";
19
19
  import { gitPorcelain } from "../story-ready/git.js";
20
+ import { depositStagePaths, isInstallerManagedPath, printCommitGuidance } from "./hygiene.js";
20
21
  import { parseInitArgv } from "./init-deposit.js";
21
22
  import { buildLegacyRefusalJson, buildLegacyRefusalMessage, detectLegacyLayout, LEGACY_LAYOUT_REFUSED_EXIT_CODE, LegacyLayoutRefusedError, } from "./legacy-detect.js";
22
23
  import { printMigrateNudgeIfNeeded } from "./migrate.js";
23
- import { CANONICAL_INSTALL_ROOT, depositNeutralization, writeAgentsMd, writeInstallManifest, } from "./scaffold.js";
24
- const INSTALLER_MANAGED_EXACT = new Set([
25
- "AGENTS.md",
26
- ".gitattributes",
27
- ".gitignore",
28
- "greptile.json",
29
- ".github/codeql/codeql-config.yml",
30
- ".github/workflows/deft-core-guard.yml",
31
- "vbrief/.deft-version",
32
- "vbrief/vbrief.md",
33
- "vbrief/proposed/.gitkeep",
34
- "vbrief/pending/.gitkeep",
35
- "vbrief/active/.gitkeep",
36
- "vbrief/completed/.gitkeep",
37
- "vbrief/cancelled/.gitkeep",
38
- ]);
39
- const INSTALLER_MANAGED_PREFIXES = [
40
- ".agents/",
41
- ".githooks/",
42
- "vbrief/schemas/",
43
- "vbrief/migration/",
44
- ];
24
+ import { CANONICAL_INSTALL_ROOT, depositNeutralization, ensureTaskfile, writeAgentsMd, writeInstallManifest, } from "./scaffold.js";
45
25
  function normalizeVersion(version) {
46
26
  return version.trim().replace(/^v/, "");
47
27
  }
@@ -110,10 +90,34 @@ function syncBareVersionMarker(projectDir, version) {
110
90
  // best-effort, mirrors install-upgrade marker write
111
91
  }
112
92
  }
113
- function isInstallerManagedPath(path) {
114
- if (INSTALLER_MANAGED_EXACT.has(path))
115
- return true;
116
- return INSTALLER_MANAGED_PREFIXES.some((prefix) => path.startsWith(prefix));
93
+ /**
94
+ * Retire a stale legacy `.deft/VERSION` after the canonical `.deft/core/VERSION`
95
+ * has been (re)written (#2064). Folded in from the former `install-upgrade`
96
+ * path so the shared refresh transaction covers the v0.27.x -> v0.28 manifest
97
+ * transition (#1046 PR-B) that the doctor's `install-manifest-disagreement`
98
+ * check still cites `task upgrade` to repair. Only acts when the canonical
99
+ * manifest lives at `<project>/.deft/core/VERSION` and a legacy
100
+ * `<project>/.deft/VERSION` disagrees; renames the legacy file to
101
+ * `.deft/VERSION.premigrate` (best-effort, never fatal).
102
+ */
103
+ function migrateLegacyInstallManifest(projectDir, canonicalManifestPath) {
104
+ const canonical = resolve(canonicalManifestPath);
105
+ const expectedParent = resolve(projectDir, ".deft", "core");
106
+ if (resolve(canonical, "..") !== expectedParent)
107
+ return;
108
+ const legacy = join(projectDir, ".deft", "VERSION");
109
+ if (!existsSync(legacy) || !statSync(legacy).isFile())
110
+ return;
111
+ try {
112
+ const legacyVersion = manifestTagToVersion(parseInstallManifest(readFileSync(legacy, "utf8")));
113
+ const canonicalVersion = manifestTagToVersion(parseInstallManifest(readFileSync(canonical, "utf8")));
114
+ if (legacyVersion !== null && legacyVersion === canonicalVersion)
115
+ return;
116
+ renameSync(legacy, join(projectDir, ".deft", "VERSION.premigrate"));
117
+ }
118
+ catch {
119
+ // best-effort
120
+ }
117
121
  }
118
122
  function unquoteGitPath(path) {
119
123
  if (path.length >= 2 && path.startsWith('"') && path.endsWith('"')) {
@@ -206,7 +210,7 @@ export function buildUpdateSummaryJson(result, options) {
206
210
  update: true,
207
211
  non_interactive: options.nonInteractive,
208
212
  upgrade: options.upgrade,
209
- taskfile_wired: false,
213
+ taskfile_wired: result.taskfileWired,
210
214
  missing_tools: [],
211
215
  maintainer_mode: false,
212
216
  maintainer_tools: [],
@@ -217,7 +221,7 @@ export function buildUpdateSummaryJson(result, options) {
217
221
  strategy: "file-swap",
218
222
  dirty_tree: false,
219
223
  dirty_files: [],
220
- staged_paths: [],
224
+ staged_paths: result.stagedPaths,
221
225
  backup_path: "",
222
226
  previous_version: result.previousDepositVersion ?? "",
223
227
  content_version: result.contentVersion,
@@ -268,13 +272,25 @@ export async function runRefreshDeposit(args, io, seams = {}) {
268
272
  fetchedBy: "directive-update",
269
273
  ...(previousManagedBy ? { managedBy: previousManagedBy } : {}),
270
274
  };
271
- writeInstallManifest(projectDir, deftDir, manifestFields);
275
+ const writtenManifestPath = writeInstallManifest(projectDir, deftDir, manifestFields);
276
+ // #2064: retire a stale legacy .deft/VERSION now that the canonical
277
+ // .deft/core/VERSION has been rewritten (folded in from install-upgrade so no
278
+ // manifest behavior is lost by the redirect). Best-effort; never fatal.
279
+ migrateLegacyInstallManifest(projectDir, writtenManifestPath);
272
280
  // #2055: regenerate the bare .deft-version derivative so it agrees with the
273
281
  // freshly written manifest tag (otherwise doctor's manifest-agreement check
274
282
  // fails and the operator must hand-edit the marker).
275
283
  syncBareVersionMarker(projectDir, contentVersion);
276
284
  const agentsMdUpdated = writeAgentsMd(projectDir, deftDir, io);
277
285
  await depositNeutralization(projectDir, io);
286
+ let taskfileWired = false;
287
+ if (args.nonInteractive) {
288
+ taskfileWired = ensureTaskfile(projectDir, io);
289
+ }
290
+ const { stagePaths, staged, stagedPaths } = depositStagePaths(projectDir, {
291
+ includeTaskfile: taskfileWired,
292
+ });
293
+ printCommitGuidance(io, stagePaths, staged);
278
294
  const readPorcelain = seams.gitPorcelain ?? gitPorcelain;
279
295
  printRefreshSideEffects(io, frameworkRefreshSideEffects(projectDir, readPorcelain));
280
296
  if (versionSkewNotice) {
@@ -289,6 +305,8 @@ export async function runRefreshDeposit(args, io, seams = {}) {
289
305
  agentsMdUpdated,
290
306
  versionSkewNotice,
291
307
  legacyLayout: false,
308
+ taskfileWired,
309
+ stagedPaths,
292
310
  };
293
311
  }
294
312
  /** CLI-facing wrapper: runs refresh, emits JSON or wizard UX, returns exit code. */
@@ -4,10 +4,9 @@
4
4
  *
5
5
  * Refs #1942, #1430, #1463, #1179.
6
6
  */
7
- export interface InitDepositIo {
8
- printf: (text: string) => void;
9
- }
10
- export declare const CANONICAL_INSTALL_ROOT = ".deft/core";
7
+ import { CANONICAL_INSTALL_ROOT, type InitDepositIo } from "./constants.js";
8
+ export type { InitDepositIo };
9
+ export { CANONICAL_INSTALL_ROOT };
11
10
  export declare const CORE_GLOB = ".deft/core/**";
12
11
  export declare const MINIMAL_TASKFILE = "version: '3'\n\n# Taskfile for this project.\n# Installed by deft-install --yes (Epic-4). Add your own tasks below or in\n# additional included files. The deft include makes all framework tasks\n# (task check, task vbrief:*, task doctor, etc.) available from the project root.\n\nincludes:\n deft:\n taskfile: ./.deft/core/Taskfile.yml\n optional: true\n";
13
12
  export declare const CANONICAL_TASKFILE_INCLUDE = "taskfile: ./.deft/core/Taskfile.yml";
@@ -12,7 +12,9 @@ import { dirname, join, relative } from "node:path";
12
12
  import { copyTree } from "../deposit/copy-tree.js";
13
13
  import { resolveLifecycleRoot } from "../layout/resolve.js";
14
14
  import { agentsRefreshPlan } from "../platform/agents-md.js";
15
- export const CANONICAL_INSTALL_ROOT = ".deft/core";
15
+ import { CANONICAL_INSTALL_ROOT } from "./constants.js";
16
+ import { installerManagedGuardEre } from "./hygiene.js";
17
+ export { CANONICAL_INSTALL_ROOT };
16
18
  export const CORE_GLOB = ".deft/core/**";
17
19
  const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
18
20
  const CORE_GUARD_WORKFLOW_REL = ".github/workflows/deft-core-guard.yml";
@@ -475,29 +477,6 @@ export function writeConsumerGitHooks(projectDir, deftDir, io, seams = {}) {
475
477
  }
476
478
  return filesDeposited || configWired;
477
479
  }
478
- function escapeEre(value) {
479
- return value.replace(/[.^$*+?()[\]{}|\\]/g, "\\$&");
480
- }
481
- function installerManagedGuardEre() {
482
- const matchers = [
483
- { exact: "AGENTS.md" },
484
- { prefix: ".agents/" },
485
- { prefix: ".githooks/" },
486
- { exact: ".gitattributes" },
487
- { exact: ".gitignore" },
488
- { exact: "greptile.json" },
489
- { exact: CODEQL_CONFIG_REL },
490
- { exact: CORE_GUARD_WORKFLOW_REL },
491
- { exact: "vbrief/.deft-version" },
492
- { exact: "vbrief/vbrief.md" },
493
- { prefix: "vbrief/schemas/" },
494
- { prefix: "vbrief/migration/" },
495
- ...VBRIEF_LIFECYCLE_DIRS.map((sub) => ({ exact: `vbrief/${sub}/.gitkeep` })),
496
- ];
497
- return matchers
498
- .map((m) => (m.exact ? `^${escapeEre(m.exact)}$` : `^${escapeEre(m.prefix ?? "")}`))
499
- .join("|");
500
- }
501
480
  function githubActionsExpr(expression) {
502
481
  return ["$", "{{ ", expression, " }}"].join("");
503
482
  }
@@ -48,6 +48,7 @@ export interface GitHubAuthModesCliArgs {
48
48
  githubAuthMode?: string | null;
49
49
  repo?: string;
50
50
  json?: boolean;
51
+ runGh?: GhRunner;
51
52
  }
52
53
  export declare function githubAuthModesMain(args: GitHubAuthModesCliArgs): number;
53
54
  export { probeRuntimeCapabilities };
@@ -257,6 +257,7 @@ export function resultToDict(result) {
257
257
  export function githubAuthModesMain(args) {
258
258
  const result = validateGithubAuthForWorker(args.githubAuthMode ?? null, {
259
259
  repo: args.repo ?? DEFAULT_VALIDATION_REPO,
260
+ runGh: args.runGh,
260
261
  });
261
262
  if (args.json) {
262
263
  process.stdout.write(`${JSON.stringify(resultToDict(result), null, 2)}\n`);
@@ -1,3 +1,4 @@
1
+ import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
1
2
  import { type ScmCallFn } from "./reconcile-issues.js";
2
3
  export declare const INGEST_STATUSES: readonly ["proposed", "pending", "active"];
3
4
  export type IngestStatus = (typeof INGEST_STATUSES)[number];
@@ -22,8 +23,11 @@ export declare function scanProvenanceRefs(vbriefDir: string): Map<number, strin
22
23
  export declare function composeOverviewWithComments(body: string, comments: readonly IssueComment[]): string;
23
24
  export declare function issueCommentThread(issue: Record<string, unknown>): IssueComment[];
24
25
  export declare function issueCommentsAlreadyFetched(issue: Record<string, unknown>): boolean;
25
- export declare function buildIssueVbrief(issue: Record<string, unknown>, status: IngestStatus, repoUrl: string): [Record<string, unknown>, string];
26
- export declare function targetFilename(number: number, title: string): string;
26
+ export declare function buildIssueVbrief(issue: Record<string, unknown>, status: IngestStatus, repoUrl: string, options?: {
27
+ infoRootKey?: typeof LEGACY_INFO_ROOT_KEY | typeof MIGRATED_INFO_ROOT_KEY;
28
+ infoVersion?: string;
29
+ }): [Record<string, unknown>, string];
30
+ export declare function targetFilename(number: number, title: string, artifactSuffix?: typeof LEGACY_ARTIFACT_SUFFIX | typeof MIGRATED_ARTIFACT_SUFFIX): string;
27
31
  export interface FetchIssueOptions {
28
32
  readonly cwd?: string | null;
29
33
  readonly cacheRoot?: string | null;
@@ -1,5 +1,5 @@
1
1
  import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
2
+ import { basename, join, resolve } from "node:path";
3
3
  import { cacheGet } from "../cache/operations.js";
4
4
  import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
5
5
  import { call } from "../scm/call.js";
@@ -7,6 +7,7 @@ import { resolveProjectRoot } from "../scope/project-context.js";
7
7
  import { resolveProjectRepo } from "../slice/project-context.js";
8
8
  import { slugify, TODAY } from "../vbrief-build/build.js";
9
9
  import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
10
+ import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, LEGACY_VBRIEF_VERSION, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, VBRIEF_VERSION, } from "../xbrief-migrate/constants.js";
10
11
  import { findAcHeading, parseCheckboxItems, parseListItems, sliceAcSection, stripCodeBlocks, stripFencedCodeBlocks, } from "./markdown-scanners.js";
11
12
  import { detectRepo, extractReferencesFromVbrief, fetchOpenIssues, GITHUB_ISSUE_REF_TYPES, LIFECYCLE_FOLDERS, parseIssueNumber, } from "./reconcile-issues.js";
12
13
  export const INGEST_STATUSES = ["proposed", "pending", "active"];
@@ -17,6 +18,53 @@ const STATUS_MAP = {
17
18
  pending: ["pending", "pending"],
18
19
  active: ["active", "running"],
19
20
  };
21
+ function readDeclaredInfoVersion(vbriefDir, infoRootKey, artifactSuffix, fallbackVersion) {
22
+ const projectDefinitionPath = join(vbriefDir, `PROJECT-DEFINITION${artifactSuffix}`);
23
+ try {
24
+ const parsed = JSON.parse(readFileSync(projectDefinitionPath, "utf8"));
25
+ const info = parsed[infoRootKey];
26
+ if (info !== null && typeof info === "object" && !Array.isArray(info)) {
27
+ const declared = info.version;
28
+ if (typeof declared === "string" && declared.length > 0) {
29
+ return declared;
30
+ }
31
+ }
32
+ }
33
+ catch {
34
+ // Fall back to the schema-default version when no declaration is available.
35
+ }
36
+ return fallbackVersion;
37
+ }
38
+ /**
39
+ * Decide the emission format (`.xbrief.json` + `xBRIEFInfo` vs legacy `.vbrief.json`
40
+ * + `vBRIEFInfo`) for an ingested scope artifact.
41
+ *
42
+ * The decision is STRUCTURAL and keyed on the resolved lifecycle root directory
43
+ * (`vbriefDir`), which is itself produced by `resolveLifecycleLayout` / `resolveLifecycleRoot`
44
+ * -- the exact same layout-decision logic `project:render` uses. This guarantees the two
45
+ * surfaces cannot diverge (#2149 finding #1): whichever tree `resolveLifecycleLayout` selected
46
+ * (`xbrief/` when migrated, else `vbrief/`) is the tree ingest writes into, and the emission
47
+ * format always matches that directory.
48
+ *
49
+ * It deliberately does NOT content-scan the tree for legacy markers (`detectLegacyVbriefLayout`).
50
+ * A historical vBRIEF-serialized artifact sitting in `xbrief/completed/` is migrated content,
51
+ * not a legacy layout, and must NOT force legacy emission on a migrated project (#2149 finding #3).
52
+ */
53
+ function resolveIngestEmissionLayout(vbriefDir) {
54
+ const migrated = basename(vbriefDir) === MIGRATED_ARTIFACT_DIR;
55
+ if (!migrated) {
56
+ return {
57
+ artifactSuffix: LEGACY_ARTIFACT_SUFFIX,
58
+ infoRootKey: LEGACY_INFO_ROOT_KEY,
59
+ infoVersion: LEGACY_VBRIEF_VERSION,
60
+ };
61
+ }
62
+ return {
63
+ artifactSuffix: MIGRATED_ARTIFACT_SUFFIX,
64
+ infoRootKey: MIGRATED_INFO_ROOT_KEY,
65
+ infoVersion: readDeclaredInfoVersion(vbriefDir, MIGRATED_INFO_ROOT_KEY, MIGRATED_ARTIFACT_SUFFIX, VBRIEF_VERSION),
66
+ };
67
+ }
20
68
  const CONTROL_CHAR_LABELS = {
21
69
  "\b": "U+0008 backspace",
22
70
  "\t": "U+0009 tab",
@@ -230,7 +278,7 @@ export function issueCommentThread(issue) {
230
278
  export function issueCommentsAlreadyFetched(issue) {
231
279
  return Object.hasOwn(issue, ISSUE_COMMENT_THREAD_KEY);
232
280
  }
233
- export function buildIssueVbrief(issue, status, repoUrl) {
281
+ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
234
282
  const number = Number(issue.number);
235
283
  const title = (typeof issue.title === "string" && issue.title.length > 0
236
284
  ? issue.title
@@ -291,20 +339,23 @@ export function buildIssueVbrief(issue, status, repoUrl) {
291
339
  }
292
340
  plan.references = references;
293
341
  }
342
+ const infoRootKey = options.infoRootKey ?? LEGACY_INFO_ROOT_KEY;
343
+ const infoVersion = options.infoVersion ?? EMITTED_VBRIEF_VERSION;
344
+ const briefLabel = infoRootKey === MIGRATED_INFO_ROOT_KEY ? "xBRIEF" : "vBRIEF";
294
345
  return [
295
346
  {
296
- vBRIEFInfo: {
297
- version: EMITTED_VBRIEF_VERSION,
298
- description: `Scope vBRIEF ingested from GitHub issue #${number}`,
347
+ [infoRootKey]: {
348
+ version: infoVersion,
349
+ description: `Scope ${briefLabel} ingested from GitHub issue #${number}`,
299
350
  },
300
351
  plan,
301
352
  },
302
353
  folder,
303
354
  ];
304
355
  }
305
- export function targetFilename(number, title) {
356
+ export function targetFilename(number, title, artifactSuffix = LEGACY_ARTIFACT_SUFFIX) {
306
357
  const slug = slugify(title) || `issue-${number}`;
307
- return `${TODAY}-${number}-${slug}.vbrief.json`;
358
+ return `${TODAY}-${number}-${slug}${artifactSuffix}`;
308
359
  }
309
360
  export function fetchFromCache(repo, number, options = {}) {
310
361
  const key = `${repo}/${number}`;
@@ -423,8 +474,12 @@ export function ingestOne(issue, options) {
423
474
  cwd: options.cwd,
424
475
  cacheRoot: options.cacheRoot,
425
476
  });
426
- const [vbrief, folder] = buildIssueVbrief(enriched, options.status, options.repoUrl);
427
- const filename = targetFilename(number, String(issue.title ?? ""));
477
+ const emissionLayout = resolveIngestEmissionLayout(options.vbriefDir);
478
+ const [vbrief, folder] = buildIssueVbrief(enriched, options.status, options.repoUrl, {
479
+ infoRootKey: emissionLayout.infoRootKey,
480
+ infoVersion: emissionLayout.infoVersion,
481
+ });
482
+ const filename = targetFilename(number, String(issue.title ?? ""), emissionLayout.artifactSuffix);
428
483
  const target = join(options.vbriefDir, folder, filename);
429
484
  if (options.dryRun) {
430
485
  return ["dryrun", target, `DRY-RUN would write ${folder}/${filename}`];
@@ -568,6 +623,7 @@ export function ingestSingleForAccept(n, repo, options = {}) {
568
623
  vbriefDir,
569
624
  status: options.status ?? "proposed",
570
625
  repoUrl,
626
+ cwd: root,
571
627
  });
572
628
  return [result, path];
573
629
  }
@@ -11,7 +11,6 @@ export interface DispatchCall {
11
11
  projectRoot: string;
12
12
  frameworkRoot: string;
13
13
  }
14
- export declare function isFrameworkSourceContext(frameworkRoot: string, projectRoot: string): boolean;
15
14
  /** Mirror ``scripts/_project_context.dispatch_task_check`` for consumer-task e2e. */
16
15
  export declare function dispatchTaskCheck(frameworkRoot: string, projectRoot: string, runner: (command: string, projectRoot: string, frameworkRoot: string) => {
17
16
  code: number;
@@ -1,6 +1,6 @@
1
1
  import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
- import { join, resolve } from "node:path";
3
+ import { join } from "node:path";
4
4
  import { afterEach } from "vitest";
5
5
  import { restIssueListPaginated, setPaginatedLister, setSingleIssueFetcher, setSleepFn, } from "../cache/fetch.js";
6
6
  import { auditPath } from "../cache/paths.js";
@@ -108,14 +108,10 @@ export function writeScopeVbrief(projectRoot, folder, filename, status = "propos
108
108
  }, null, 2)}\n`, "utf8");
109
109
  return target;
110
110
  }
111
- export function isFrameworkSourceContext(frameworkRoot, projectRoot) {
112
- return resolve(frameworkRoot) === resolve(projectRoot);
113
- }
111
+ import { resolveCheckTarget } from "../check/orchestrator.js";
114
112
  /** Mirror ``scripts/_project_context.dispatch_task_check`` for consumer-task e2e. */
115
113
  export function dispatchTaskCheck(frameworkRoot, projectRoot, runner) {
116
- const target = isFrameworkSourceContext(frameworkRoot, projectRoot)
117
- ? "check:framework-source"
118
- : "check:consumer";
114
+ const target = resolveCheckTarget(frameworkRoot, projectRoot);
119
115
  return runner(target, projectRoot, frameworkRoot).code;
120
116
  }
121
117
  /** Hermetic mirror of ``tests/integration/test_scm_smoke._probe_rate_limit``. */
@@ -0,0 +1,15 @@
1
+ /** Lifecycle folders searched when a folder-qualified ref dangles (#1926). */
2
+ export declare const LIFECYCLE_SCOPE_FOLDERS: readonly ["proposed", "pending", "active", "completed", "cancelled"];
3
+ export interface ResolveLifecycleArtifactRefOptions {
4
+ /** When false, only resolve the literal path (for rewrite matching). Default true (#1926). */
5
+ readonly allowCrossFolderSearch?: boolean;
6
+ }
7
+ /**
8
+ * Resolve a lifecycle-relative artifact reference to an absolute path.
9
+ *
10
+ * When the direct path is missing but the ref names a lifecycle artifact
11
+ * (folder-qualified or bare basename), search all lifecycle folders for the
12
+ * same filename (#1926).
13
+ */
14
+ export declare function resolveLifecycleArtifactRef(rel: string, lifecycleRoot: string, options?: ResolveLifecycleArtifactRefOptions): string;
15
+ //# sourceMappingURL=lifecycle-ref.d.ts.map