@deftai/directive-core 0.77.0 → 0.79.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 (126) hide show
  1. package/dist/cache/empty-populate.d.ts +44 -0
  2. package/dist/cache/empty-populate.js +117 -0
  3. package/dist/cache/index.d.ts +1 -0
  4. package/dist/cache/index.js +1 -0
  5. package/dist/check-updates/index.d.ts +10 -0
  6. package/dist/check-updates/index.js +81 -1
  7. package/dist/doctor/main.d.ts +1 -0
  8. package/dist/doctor/main.js +48 -0
  9. package/dist/doctor/payload-staleness.js +7 -1
  10. package/dist/doctor/types.d.ts +3 -0
  11. package/dist/eval/triggers.d.ts +76 -0
  12. package/dist/eval/triggers.js +259 -0
  13. package/dist/eval-triggers-relocation/evaluate.d.ts +36 -0
  14. package/dist/eval-triggers-relocation/evaluate.js +115 -0
  15. package/dist/eval-triggers-relocation/index.d.ts +2 -0
  16. package/dist/eval-triggers-relocation/index.js +2 -0
  17. package/dist/hooks/dispatcher.d.ts +43 -0
  18. package/dist/hooks/dispatcher.js +173 -0
  19. package/dist/hooks/index.d.ts +3 -0
  20. package/dist/hooks/index.js +3 -0
  21. package/dist/hooks/scope.d.ts +12 -0
  22. package/dist/hooks/scope.js +46 -0
  23. package/dist/hooks/tools.d.ts +4 -0
  24. package/dist/hooks/tools.js +23 -0
  25. package/dist/init-deposit/agent-hooks.d.ts +22 -0
  26. package/dist/init-deposit/agent-hooks.js +238 -0
  27. package/dist/init-deposit/hygiene.js +4 -0
  28. package/dist/init-deposit/index.d.ts +1 -0
  29. package/dist/init-deposit/index.js +1 -0
  30. package/dist/init-deposit/init-deposit.js +4 -0
  31. package/dist/init-deposit/prettierignore.d.ts +21 -0
  32. package/dist/init-deposit/prettierignore.js +83 -0
  33. package/dist/init-deposit/refresh.d.ts +3 -1
  34. package/dist/init-deposit/refresh.js +31 -40
  35. package/dist/init-deposit/scaffold.js +6 -12
  36. package/dist/init-deposit/xbrief-projections.d.ts +18 -0
  37. package/dist/init-deposit/xbrief-projections.js +117 -0
  38. package/dist/intake/reconcile-issues.d.ts +11 -0
  39. package/dist/intake/reconcile-issues.js +166 -51
  40. package/dist/issue-sync/index.d.ts +2 -0
  41. package/dist/issue-sync/index.js +2 -0
  42. package/dist/issue-sync/sync-from-xbrief-cli.d.ts +11 -0
  43. package/dist/issue-sync/sync-from-xbrief-cli.js +44 -0
  44. package/dist/issue-sync/sync-from-xbrief.d.ts +46 -0
  45. package/dist/issue-sync/sync-from-xbrief.js +208 -0
  46. package/dist/platform/index.d.ts +1 -0
  47. package/dist/platform/index.js +1 -0
  48. package/dist/platform/platform-capabilities.d.ts +5 -0
  49. package/dist/platform/platform-capabilities.js +9 -0
  50. package/dist/platform/shell-context.d.ts +30 -0
  51. package/dist/platform/shell-context.js +111 -0
  52. package/dist/pr-monitor/cadence.d.ts +6 -0
  53. package/dist/pr-monitor/cadence.js +13 -0
  54. package/dist/pr-monitor/index.d.ts +1 -1
  55. package/dist/pr-monitor/index.js +1 -1
  56. package/dist/pr-monitor/monitor.d.ts +7 -0
  57. package/dist/pr-monitor/monitor.js +59 -13
  58. package/dist/preflight-cache/evaluate.d.ts +3 -8
  59. package/dist/preflight-cache/evaluate.js +19 -19
  60. package/dist/release/build-dist.js +1 -0
  61. package/dist/release/constants.d.ts +2 -0
  62. package/dist/release/constants.js +8 -1
  63. package/dist/release/flags.js +26 -0
  64. package/dist/release/issue-state-fetch.d.ts +27 -0
  65. package/dist/release/issue-state-fetch.js +146 -0
  66. package/dist/release/main.js +1 -0
  67. package/dist/release/native-steps.js +6 -4
  68. package/dist/release/pipeline.js +15 -5
  69. package/dist/release/preflight.d.ts +6 -2
  70. package/dist/release/preflight.js +19 -5
  71. package/dist/release/types.d.ts +3 -1
  72. package/dist/release-e2e/constants.js +6 -6
  73. package/dist/release-e2e/flags.d.ts +2 -0
  74. package/dist/release-e2e/flags.js +8 -4
  75. package/dist/release-e2e/greenfield-python-free-smoke-cli.js +40 -1
  76. package/dist/release-e2e/greenfield-python-free-smoke.d.ts +2 -0
  77. package/dist/release-e2e/greenfield-python-free-smoke.js +28 -8
  78. package/dist/release-e2e/main.js +15 -9
  79. package/dist/release-e2e/types.d.ts +4 -2
  80. package/dist/resolution/classify.d.ts +1 -1
  81. package/dist/resolution/classify.js +36 -11
  82. package/dist/scope/project-definition-sync.js +2 -150
  83. package/dist/scope/registry-artifact-sync.d.ts +3 -0
  84. package/dist/scope/registry-artifact-sync.js +159 -0
  85. package/dist/scope/specification-sync.d.ts +3 -0
  86. package/dist/scope/specification-sync.js +16 -0
  87. package/dist/scope/transition.js +40 -12
  88. package/dist/session/cache-recovery.d.ts +19 -0
  89. package/dist/session/cache-recovery.js +26 -0
  90. package/dist/session/session-start.d.ts +2 -0
  91. package/dist/session/session-start.js +14 -3
  92. package/dist/session/verify-session-ritual.d.ts +17 -0
  93. package/dist/session/verify-session-ritual.js +64 -0
  94. package/dist/triage/help/registry-data.d.ts +12 -1
  95. package/dist/triage/help/registry-data.js +22 -1
  96. package/dist/triage/welcome/default-mode.js +2 -0
  97. package/dist/triage/welcome/onboard.js +2 -0
  98. package/dist/ts-check-lane/run-lane.d.ts +5 -0
  99. package/dist/ts-check-lane/run-lane.js +9 -0
  100. package/dist/vbrief-reconcile/labels.d.ts +3 -0
  101. package/dist/vbrief-reconcile/labels.js +14 -0
  102. package/dist/vbrief-reconcile/main.d.ts +1 -0
  103. package/dist/vbrief-reconcile/main.js +13 -3
  104. package/dist/vbrief-reconcile/parity-scenarios.js +18 -4
  105. package/dist/vbrief-reconcile/repo-guard.d.ts +17 -0
  106. package/dist/vbrief-reconcile/repo-guard.js +33 -0
  107. package/dist/vbrief-reconcile/umbrellas.d.ts +3 -0
  108. package/dist/vbrief-reconcile/umbrellas.js +17 -1
  109. package/dist/verify-env/agent-hooks.d.ts +11 -0
  110. package/dist/verify-env/agent-hooks.js +46 -0
  111. package/dist/verify-env/command-spawn.d.ts +5 -0
  112. package/dist/verify-env/command-spawn.js +28 -9
  113. package/dist/verify-env/index.d.ts +1 -0
  114. package/dist/verify-env/index.js +1 -0
  115. package/dist/verify-env/verify-hooks-installed.js +5 -5
  116. package/dist/vitest-runner/coverage-debt-teardown.d.ts +3 -0
  117. package/dist/vitest-runner/coverage-debt-teardown.js +33 -0
  118. package/dist/vitest-runner/coverage-debt.d.ts +50 -0
  119. package/dist/vitest-runner/coverage-debt.js +155 -0
  120. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +12 -0
  121. package/dist/vitest-runner/win32-coverage-tmp-setup.js +24 -0
  122. package/dist/xbrief-migrate/detect.js +46 -1
  123. package/dist/xbrief-migrate/migrate-project.js +19 -4
  124. package/dist/xbrief-migrate/migration-containment.d.ts +6 -0
  125. package/dist/xbrief-migrate/migration-containment.js +37 -0
  126. package/package.json +19 -3
@@ -0,0 +1,46 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { hasArtifactSuffix } from "../layout/resolve.js";
4
+ import { evaluate } from "../preflight/evaluate.js";
5
+ /**
6
+ * Find an implementation-eligible scope by delegating every candidate to the
7
+ * existing xBRIEF preflight evaluator. This intentionally creates no second
8
+ * lifecycle/status policy stack.
9
+ */
10
+ export function inspectActiveScope(projectRoot) {
11
+ const candidates = [];
12
+ for (const relativeDir of [join("xbrief", "active"), join("vbrief", "active")]) {
13
+ const activeDir = join(projectRoot, relativeDir);
14
+ try {
15
+ for (const entry of readdirSync(activeDir, { withFileTypes: true })) {
16
+ if (entry.isFile() && hasArtifactSuffix(entry.name)) {
17
+ candidates.push(join(activeDir, entry.name));
18
+ }
19
+ }
20
+ }
21
+ catch {
22
+ // A missing/unreadable active folder contributes no eligible candidate.
23
+ }
24
+ }
25
+ // Stable traversal makes the selected path and first rejection reproducible.
26
+ // This is not a priority rule: every candidate is evaluated until one passes.
27
+ candidates.sort();
28
+ let firstRejection = null;
29
+ for (const candidate of candidates) {
30
+ const result = evaluate(candidate);
31
+ if (result.exitCode === 0) {
32
+ return { ready: true, path: candidate, message: result.message };
33
+ }
34
+ firstRejection ??= result.message;
35
+ }
36
+ if (firstRejection !== null) {
37
+ return { ready: false, path: null, message: firstRejection };
38
+ }
39
+ return {
40
+ ready: false,
41
+ path: null,
42
+ message: "No active xBRIEF artifact was found under xbrief/active/ " +
43
+ "(or the legacy vbrief/active/ compatibility path).",
44
+ };
45
+ }
46
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1,4 @@
1
+ /** Host spellings that install-time matchers and runtime classification share. */
2
+ export declare const DIRECT_WRITE_TOOL_NAMES: readonly ["Edit", "Write", "WriteFile", "CreateFile", "MultiEdit", "NotebookEdit", "StrReplace", "SearchReplace", "Delete", "DeleteFile", "ApplyPatch", "apply_patch"];
3
+ export declare function isDirectWriteTool(toolName: string): boolean;
4
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1,23 @@
1
+ /** Host spellings that install-time matchers and runtime classification share. */
2
+ export const DIRECT_WRITE_TOOL_NAMES = [
3
+ "Edit",
4
+ "Write",
5
+ "WriteFile",
6
+ "CreateFile",
7
+ "MultiEdit",
8
+ "NotebookEdit",
9
+ "StrReplace",
10
+ "SearchReplace",
11
+ "Delete",
12
+ "DeleteFile",
13
+ "ApplyPatch",
14
+ "apply_patch",
15
+ ];
16
+ function normalizedToolName(toolName) {
17
+ return toolName.toLowerCase().replace(/[^a-z0-9]/g, "");
18
+ }
19
+ const DIRECT_WRITE_TOOLS = new Set(DIRECT_WRITE_TOOL_NAMES.map(normalizedToolName));
20
+ export function isDirectWriteTool(toolName) {
21
+ return DIRECT_WRITE_TOOLS.has(normalizedToolName(toolName));
22
+ }
23
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1,22 @@
1
+ import type { HookHost } from "../hooks/dispatcher.js";
2
+ import type { InitDepositIo } from "./constants.js";
3
+ export declare const DIRECT_WRITE_HOOK_MATCHER: string;
4
+ export declare const DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
5
+ export declare const AGENT_HOOK_PATHS: readonly [".claude/settings.json", ".grok/hooks/deft.json", ".cursor/hooks.json", ".codex/hooks.json"];
6
+ export type AgentHookPath = (typeof AGENT_HOOK_PATHS)[number];
7
+ export type AgentHookRegistrationStatus = "healthy" | "missing" | "drifted";
8
+ export interface AgentHookInspection {
9
+ readonly host: HookHost;
10
+ readonly path: AgentHookPath;
11
+ readonly status: AgentHookRegistrationStatus;
12
+ readonly detail: string;
13
+ }
14
+ export interface AgentHookDepositResult {
15
+ readonly changed: boolean;
16
+ readonly changedPaths: AgentHookPath[];
17
+ }
18
+ /** Merge Directive-owned project hook entries without replacing user configuration. */
19
+ export declare function writeAgentHookDeposit(projectRoot: string, io?: InitDepositIo): AgentHookDepositResult;
20
+ /** Read-only registration probe shared by verify and doctor. */
21
+ export declare function inspectAgentHookDeposit(projectRoot: string): AgentHookInspection[];
22
+ //# sourceMappingURL=agent-hooks.d.ts.map
@@ -0,0 +1,238 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { assertDepositContained } from "../deposit/contain.js";
4
+ import { DIRECT_WRITE_TOOL_NAMES } from "../hooks/tools.js";
5
+ export const DIRECT_WRITE_HOOK_MATCHER = DIRECT_WRITE_TOOL_NAMES.join("|");
6
+ export const DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
7
+ export const AGENT_HOOK_PATHS = [
8
+ ".claude/settings.json",
9
+ ".grok/hooks/deft.json",
10
+ ".cursor/hooks.json",
11
+ ".codex/hooks.json",
12
+ ];
13
+ function object(value) {
14
+ return value !== null && typeof value === "object" && !Array.isArray(value)
15
+ ? value
16
+ : null;
17
+ }
18
+ function command(host, event) {
19
+ return `${DEFT_HOOK_COMMAND_MARKER} --host ${host} --event ${event}`;
20
+ }
21
+ function readConfig(path) {
22
+ if (!existsSync(path))
23
+ return {};
24
+ const raw = readFileSync(path, "utf8");
25
+ let parsed;
26
+ try {
27
+ parsed = JSON.parse(raw);
28
+ }
29
+ catch (cause) {
30
+ throw new Error(`${path} is not valid JSON; refusing to overwrite user configuration: ${cause}`);
31
+ }
32
+ const config = object(parsed);
33
+ if (config === null) {
34
+ throw new Error(`${path} must contain a JSON object; refusing to overwrite user configuration.`);
35
+ }
36
+ return config;
37
+ }
38
+ function hooksObject(config, path) {
39
+ if (config.hooks === undefined)
40
+ return {};
41
+ const hooks = object(config.hooks);
42
+ if (hooks === null)
43
+ throw new Error(`${path}: hooks must be a JSON object.`);
44
+ return { ...hooks };
45
+ }
46
+ function eventArray(hooks, key, path) {
47
+ const value = hooks[key];
48
+ if (value === undefined)
49
+ return [];
50
+ if (!Array.isArray(value))
51
+ throw new Error(`${path}: hooks.${key} must be an array.`);
52
+ return [...value];
53
+ }
54
+ function nestedCommands(value) {
55
+ const group = object(value);
56
+ if (group === null || !Array.isArray(group.hooks))
57
+ return [];
58
+ return group.hooks.flatMap((candidate) => {
59
+ const hook = object(candidate);
60
+ return typeof hook?.command === "string" ? [hook.command] : [];
61
+ });
62
+ }
63
+ function isManagedNestedGroup(value) {
64
+ return nestedCommands(value).some((value) => value.includes(DEFT_HOOK_COMMAND_MARKER));
65
+ }
66
+ function isManagedCursorEntry(value) {
67
+ const entry = object(value);
68
+ return typeof entry?.command === "string" && entry.command.includes(DEFT_HOOK_COMMAND_MARKER);
69
+ }
70
+ function nestedGroup(host, event) {
71
+ return {
72
+ ...(event === "tool.before" ? { matcher: DIRECT_WRITE_HOOK_MATCHER } : {}),
73
+ hooks: [
74
+ {
75
+ type: "command",
76
+ command: command(host, event),
77
+ timeout: 5,
78
+ },
79
+ ],
80
+ };
81
+ }
82
+ function mergeNestedConfig(config, path, host) {
83
+ const hooks = hooksObject(config, path);
84
+ const session = eventArray(hooks, "SessionStart", path).filter((entry) => !isManagedNestedGroup(entry));
85
+ const preTool = eventArray(hooks, "PreToolUse", path).filter((entry) => !isManagedNestedGroup(entry));
86
+ hooks.SessionStart = [...session, nestedGroup(host, "session.start")];
87
+ hooks.PreToolUse = [...preTool, nestedGroup(host, "tool.before")];
88
+ return { ...config, hooks };
89
+ }
90
+ function mergeCursorConfig(config, path) {
91
+ const hooks = hooksObject(config, path);
92
+ const session = eventArray(hooks, "sessionStart", path).filter((entry) => !isManagedCursorEntry(entry));
93
+ const preTool = eventArray(hooks, "preToolUse", path).filter((entry) => !isManagedCursorEntry(entry));
94
+ hooks.sessionStart = [...session, { command: command("cursor", "session.start"), timeout: 5 }];
95
+ hooks.preToolUse = [
96
+ ...preTool,
97
+ {
98
+ command: command("cursor", "tool.before"),
99
+ matcher: DIRECT_WRITE_HOOK_MATCHER,
100
+ failClosed: true,
101
+ timeout: 5,
102
+ },
103
+ ];
104
+ return { ...config, version: 1, hooks };
105
+ }
106
+ function writeJsonIfChanged(path, payload) {
107
+ const next = `${JSON.stringify(payload, null, 2)}\n`;
108
+ if (existsSync(path) && readFileSync(path, "utf8") === next)
109
+ return false;
110
+ mkdirSync(dirname(path), { recursive: true });
111
+ const temporary = `${path}.deft-${process.pid}.tmp`;
112
+ writeFileSync(temporary, next, "utf8");
113
+ renameSync(temporary, path);
114
+ return true;
115
+ }
116
+ /** Merge Directive-owned project hook entries without replacing user configuration. */
117
+ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefined }) {
118
+ const changedPaths = [];
119
+ const definitions = [
120
+ {
121
+ path: AGENT_HOOK_PATHS[0],
122
+ merge: (config, path) => mergeNestedConfig(config, path, "claude"),
123
+ },
124
+ {
125
+ path: AGENT_HOOK_PATHS[1],
126
+ merge: (config, path) => mergeNestedConfig(config, path, "grok"),
127
+ },
128
+ { path: AGENT_HOOK_PATHS[2], merge: mergeCursorConfig },
129
+ {
130
+ path: AGENT_HOOK_PATHS[3],
131
+ merge: (config, path) => mergeNestedConfig(config, path, "codex"),
132
+ },
133
+ ];
134
+ const prepared = definitions.map((definition) => {
135
+ const absolute = join(projectRoot, definition.path);
136
+ assertDepositContained(projectRoot, absolute);
137
+ const merged = definition.merge(readConfig(absolute), absolute);
138
+ return { ...definition, absolute, merged };
139
+ });
140
+ for (const definition of prepared) {
141
+ if (writeJsonIfChanged(definition.absolute, definition.merged)) {
142
+ changedPaths.push(definition.path);
143
+ }
144
+ }
145
+ if (changedPaths.length > 0) {
146
+ io.printf(`Installed Directive agent hooks: ${changedPaths.join(", ")}\n`);
147
+ }
148
+ else {
149
+ io.printf("Directive agent hooks already current.\n");
150
+ }
151
+ return { changed: changedPaths.length > 0, changedPaths };
152
+ }
153
+ function hasNestedRegistration(config, host) {
154
+ const hooks = object(config.hooks);
155
+ if (hooks === null)
156
+ return false;
157
+ const session = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
158
+ const preTool = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
159
+ const sessionCommand = command(host, "session.start");
160
+ const toolCommand = command(host, "tool.before");
161
+ return (session.some((entry) => nestedCommands(entry).includes(sessionCommand)) &&
162
+ preTool.some((entry) => {
163
+ const group = object(entry);
164
+ return (group?.matcher === DIRECT_WRITE_HOOK_MATCHER && nestedCommands(entry).includes(toolCommand));
165
+ }));
166
+ }
167
+ function hasCursorRegistration(config) {
168
+ const hooks = object(config.hooks);
169
+ if (hooks === null || config.version !== 1)
170
+ return false;
171
+ const session = Array.isArray(hooks.sessionStart) ? hooks.sessionStart : [];
172
+ const preTool = Array.isArray(hooks.preToolUse) ? hooks.preToolUse : [];
173
+ return (session.some((entry) => object(entry)?.command === command("cursor", "session.start")) &&
174
+ preTool.some((entry) => {
175
+ const hook = object(entry);
176
+ return (hook?.command === command("cursor", "tool.before") &&
177
+ hook.matcher === DIRECT_WRITE_HOOK_MATCHER &&
178
+ hook.failClosed === true);
179
+ }));
180
+ }
181
+ /** Read-only registration probe shared by verify and doctor. */
182
+ export function inspectAgentHookDeposit(projectRoot) {
183
+ const definitions = [
184
+ {
185
+ host: "claude",
186
+ path: AGENT_HOOK_PATHS[0],
187
+ valid: (config) => hasNestedRegistration(config, "claude"),
188
+ },
189
+ {
190
+ host: "grok",
191
+ path: AGENT_HOOK_PATHS[1],
192
+ valid: (config) => hasNestedRegistration(config, "grok"),
193
+ },
194
+ { host: "cursor", path: AGENT_HOOK_PATHS[2], valid: hasCursorRegistration },
195
+ {
196
+ host: "codex",
197
+ path: AGENT_HOOK_PATHS[3],
198
+ valid: (config) => hasNestedRegistration(config, "codex"),
199
+ },
200
+ ];
201
+ return definitions.map((definition) => {
202
+ const absolute = join(projectRoot, definition.path);
203
+ if (!existsSync(absolute)) {
204
+ return {
205
+ host: definition.host,
206
+ path: definition.path,
207
+ status: "missing",
208
+ detail: `${definition.path} is missing.`,
209
+ };
210
+ }
211
+ try {
212
+ const config = readConfig(absolute);
213
+ if (definition.valid(config)) {
214
+ return {
215
+ host: definition.host,
216
+ path: definition.path,
217
+ status: "healthy",
218
+ detail: "SessionStart and direct-write PreToolUse registrations are current.",
219
+ };
220
+ }
221
+ return {
222
+ host: definition.host,
223
+ path: definition.path,
224
+ status: "drifted",
225
+ detail: "Directive SessionStart or direct-write PreToolUse registration is missing/drifted.",
226
+ };
227
+ }
228
+ catch (cause) {
229
+ return {
230
+ host: definition.host,
231
+ path: definition.path,
232
+ status: "drifted",
233
+ detail: String(cause),
234
+ };
235
+ }
236
+ });
237
+ }
238
+ //# sourceMappingURL=agent-hooks.js.map
@@ -21,6 +21,10 @@ export function installerManagedMatchers() {
21
21
  { exact: "AGENTS.md" },
22
22
  { prefix: ".agents/" },
23
23
  { prefix: ".githooks/" },
24
+ { exact: ".claude/settings.json" },
25
+ { exact: ".grok/hooks/deft.json" },
26
+ { exact: ".cursor/hooks.json" },
27
+ { exact: ".codex/hooks.json" },
24
28
  { exact: ".gitattributes" },
25
29
  { exact: ".gitignore" },
26
30
  { exact: "greptile.json" },
@@ -1,3 +1,4 @@
1
+ export * from "./agent-hooks.js";
1
2
  export * from "./headless-manifest.js";
2
3
  export * from "./init-deposit.js";
3
4
  export * from "./init-dispatch.js";
@@ -1,3 +1,4 @@
1
+ export * from "./agent-hooks.js";
1
2
  export * from "./headless-manifest.js";
2
3
  export * from "./init-deposit.js";
3
4
  export * from "./init-dispatch.js";
@@ -13,10 +13,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 { readCorePackageVersion } from "../engine-version.js";
16
+ import { writeAgentHookDeposit } from "./agent-hooks.js";
16
17
  import { ensureInitGitignoreLines, reconstituteDepositFromContent } from "./gitignore.js";
17
18
  import { depositStagePaths, printCommitGuidance } from "./hygiene.js";
18
19
  import { buildLegacyRefusalJson, buildLegacyRefusalMessage, detectLegacyLayout, LEGACY_LAYOUT_REFUSED_EXIT_CODE, LegacyLayoutRefusedError, } from "./legacy-detect.js";
19
20
  import { printMigrateNudgeIfNeeded } from "./migrate.js";
21
+ import { ensurePrettierIgnoreLines } from "./prettierignore.js";
20
22
  import { CANONICAL_INSTALL_ROOT, depositNeutralization, ensureTaskfile, writeAgentsMd, writeAgentsSkills, writeConsumerGitHooks, writeConsumerVbrief, writeInstallManifest, } from "./scaffold.js";
21
23
  export function parseInitArgv(canonicalArgv, userArgv = []) {
22
24
  const args = [...canonicalArgv, ...userArgv];
@@ -136,6 +138,7 @@ export async function runInitDeposit(args, io, seams = {}) {
136
138
  await reconstituteDepositFromContent(contentRoot, deftDir, copyContent);
137
139
  await prunePythonArtifactsFromDeposit(deftDir, projectDir, io);
138
140
  ensureInitGitignoreLines(projectDir, io);
141
+ ensurePrettierIgnoreLines(projectDir, io);
139
142
  const nowIso = seams.nowIso ?? (() => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"));
140
143
  const version = readContentVersion(contentRoot, seams.readPackageVersion ?? readCorePackageVersion);
141
144
  const manifestFields = {
@@ -152,6 +155,7 @@ export async function runInitDeposit(args, io, seams = {}) {
152
155
  await depositNeutralization(projectDir, io);
153
156
  await writeConsumerVbrief(projectDir, deftDir, io);
154
157
  writeConsumerGitHooks(projectDir, deftDir, io, seams.gitHooks);
158
+ writeAgentHookDeposit(projectDir, io);
155
159
  let taskfileWired = false;
156
160
  if (args.nonInteractive) {
157
161
  taskfileWired = ensureTaskfile(projectDir, io);
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Consumer `.prettierignore` upkeep for install and npm-managed update (#2534).
3
+ *
4
+ * Managed `.deft/core/` is outside the consumer Prettier format gate. Official
5
+ * install and `directive update` heal the ignore entry idempotently without
6
+ * reformatting the vendored deposit.
7
+ *
8
+ * Refs #2534, #670.
9
+ */
10
+ import type { InitDepositIo } from "./constants.js";
11
+ /** Directory ignore entry for the hybrid deposit. */
12
+ export declare const PRETTIERIGNORE_DEFT_CORE_LINE = ".deft/core/";
13
+ export interface EnsurePrettierIgnoreResult {
14
+ readonly changed: boolean;
15
+ }
16
+ /**
17
+ * Ensure the consumer `.prettierignore` excludes managed `.deft/core/` from
18
+ * Prettier. Idempotent; preserves consumer preamble.
19
+ */
20
+ export declare function ensurePrettierIgnoreLines(projectDir: string, io: InitDepositIo): EnsurePrettierIgnoreResult;
21
+ //# sourceMappingURL=prettierignore.d.ts.map
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Consumer `.prettierignore` upkeep for install and npm-managed update (#2534).
3
+ *
4
+ * Managed `.deft/core/` is outside the consumer Prettier format gate. Official
5
+ * install and `directive update` heal the ignore entry idempotently without
6
+ * reformatting the vendored deposit.
7
+ *
8
+ * Refs #2534, #670.
9
+ */
10
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { assertProjectionContained } from "../fs/projection-containment.js";
13
+ import { stripGitignoreInlineComment } from "../triage/bootstrap/gitignore.js";
14
+ /** Directory ignore entry for the hybrid deposit. */
15
+ export const PRETTIERIGNORE_DEFT_CORE_LINE = ".deft/core/";
16
+ /** Alternate spellings that already cover the deposit ignore entry. */
17
+ const DEFT_CORE_COVERING_LINES = new Set([".deft/core/", ".deft/core", ".deft/core/**"]);
18
+ const DEFT_FRAMEWORK_PRETTIERIGNORE_HEADER = "# Deft framework: the vendored payload is outside the consumer Prettier gate (#2534).\n";
19
+ function projectionTarget(projectDir) {
20
+ const target = join(projectDir, ".prettierignore");
21
+ assertProjectionContained(projectDir, target);
22
+ return target;
23
+ }
24
+ function prettierIgnoreCoversLine(present, line) {
25
+ if (present.has(line))
26
+ return true;
27
+ if (line === PRETTIERIGNORE_DEFT_CORE_LINE) {
28
+ return [...DEFT_CORE_COVERING_LINES].some((candidate) => present.has(candidate));
29
+ }
30
+ return false;
31
+ }
32
+ function collectPresentPrettierIgnoreLines(existing) {
33
+ const present = new Set();
34
+ for (const raw of existing.split("\n")) {
35
+ const stripped = stripGitignoreInlineComment(raw);
36
+ if (stripped)
37
+ present.add(stripped);
38
+ }
39
+ return present;
40
+ }
41
+ /**
42
+ * Ensure the consumer `.prettierignore` excludes managed `.deft/core/` from
43
+ * Prettier. Idempotent; preserves consumer preamble.
44
+ */
45
+ export function ensurePrettierIgnoreLines(projectDir, io) {
46
+ const path = projectionTarget(projectDir);
47
+ let existing = "";
48
+ if (existsSync(path)) {
49
+ try {
50
+ existing = readFileSync(path, { encoding: "utf8" });
51
+ }
52
+ catch (cause) {
53
+ throw new Error(`could not read .prettierignore: ${String(cause)}`);
54
+ }
55
+ }
56
+ const present = collectPresentPrettierIgnoreLines(existing);
57
+ const targetLines = [PRETTIERIGNORE_DEFT_CORE_LINE];
58
+ const additions = targetLines.filter((line) => !prettierIgnoreCoversLine(present, line));
59
+ if (additions.length === 0) {
60
+ io.printf(".prettierignore already excludes the managed deft framework deposit — skipping.\n");
61
+ return { changed: false };
62
+ }
63
+ let body = existing;
64
+ if (body !== "" && !body.endsWith("\n")) {
65
+ body += "\n";
66
+ }
67
+ if (body !== "" && !body.endsWith("\n\n")) {
68
+ body += "\n";
69
+ }
70
+ body += DEFT_FRAMEWORK_PRETTIERIGNORE_HEADER;
71
+ for (const add of additions) {
72
+ body += `${add}\n`;
73
+ }
74
+ try {
75
+ writeFileSync(path, body, { encoding: "utf8", mode: 0o644 });
76
+ }
77
+ catch (cause) {
78
+ throw new Error(`could not write .prettierignore: ${String(cause)}`);
79
+ }
80
+ io.printf(`.prettierignore updated with Deft framework exclusions: ${additions.join(", ")}\n`);
81
+ return { changed: true };
82
+ }
83
+ //# sourceMappingURL=prettierignore.js.map
@@ -12,7 +12,7 @@ import { type ClassifySeams, type EngineInstallRunner, type EngineResolution, ty
12
12
  import { type GitLsFiles } from "./gitignore.js";
13
13
  import { type InitDepositArgs } from "./init-deposit.js";
14
14
  import { type LegacyLayoutDetection } from "./legacy-detect.js";
15
- import { type InitDepositIo } from "./scaffold.js";
15
+ import { type GitHooksSeams, type InitDepositIo } from "./scaffold.js";
16
16
  export interface RefreshDepositArgs extends InitDepositArgs {
17
17
  readonly upgrade: boolean;
18
18
  }
@@ -45,6 +45,8 @@ export interface RefreshDepositSeams {
45
45
  * upkeep so the refresh never invokes a destructive `git rm --cached` path.
46
46
  */
47
47
  gitLsFiles?: GitLsFiles;
48
+ /** #2530: injected git config seams for {@link writeConsumerGitHooks}. */
49
+ gitHooks?: GitHooksSeams;
48
50
  }
49
51
  /**
50
52
  * The four states `directive update` classifies an EXISTING install into BEFORE
@@ -8,7 +8,7 @@
8
8
  * Refs #1942, #1430, #1671.
9
9
  */
10
10
  import { execFileSync } from "node:child_process";
11
- import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
11
+ import { existsSync, readFileSync, renameSync, statSync } from "node:fs";
12
12
  import { platform as osPlatform } from "node:os";
13
13
  import { join, resolve } from "node:path";
14
14
  import { assertDepositContained } from "../deposit/contain.js";
@@ -18,15 +18,26 @@ import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
18
18
  import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
19
19
  import { readCorePackageVersion } from "../engine-version.js";
20
20
  import { resolveLifecycleRoot } from "../layout/resolve.js";
21
- import { DEV_FALLBACK } from "../platform/constants.js";
22
21
  import { checkLocalEngineIntegrity, classify, ENGINE_PACKAGE, plan, renderGlobalInstall, resolveEngine, } from "../resolution/index.js";
23
22
  import { gitPorcelain } from "../story-ready/git.js";
23
+ import { writeAgentHookDeposit } from "./agent-hooks.js";
24
24
  import { ensureInitGitignoreLines, isDepositTrackedInGit } from "./gitignore.js";
25
25
  import { depositStagePaths, isInstallerManagedPath, printCommitGuidance, pruneStrayDepositPaths, } from "./hygiene.js";
26
26
  import { parseInitArgv } from "./init-deposit.js";
27
27
  import { buildLegacyRefusalJson, buildLegacyRefusalMessage, detectLegacyLayout, LEGACY_LAYOUT_REFUSED_EXIT_CODE, LegacyLayoutRefusedError, } from "./legacy-detect.js";
28
28
  import { printMigrateNudgeIfNeeded } from "./migrate.js";
29
- import { CANONICAL_INSTALL_ROOT, depositNeutralization, ensureTaskfile, writeAgentsMd, writeInstallManifest, } from "./scaffold.js";
29
+ import { ensurePrettierIgnoreLines } from "./prettierignore.js";
30
+ import { CANONICAL_INSTALL_ROOT, depositNeutralization, ensureTaskfile, writeAgentsMd, writeConsumerGitHooks, writeInstallManifest, } from "./scaffold.js";
31
+ import { syncBareVersionMarker, syncConsumerXbriefSchemas, syncExistingBareVersionMarker, } from "./xbrief-projections.js";
32
+ function hasCanonicalXbriefLifecycle(projectDir) {
33
+ try {
34
+ resolveLifecycleRoot(projectDir);
35
+ return true;
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
30
41
  /** The exact refusal shown when `update` runs against an un-initialized project. */
31
42
  export const NOT_INITIALIZED_MESSAGE = "This project is not initialized. Run directive init.";
32
43
  /** `update` refused because the project has no install / needs a different verb. */
@@ -146,39 +157,6 @@ function readRecordedManagedBy(deftDir) {
146
157
  return null;
147
158
  }
148
159
  }
149
- /**
150
- * Regenerate the bare `.deft-version` derivative from the deposited content
151
- * version in the same transaction as the payload swap (#2055). The canonical
152
- * marker lives at `vbrief/.deft-version`; fall back to the project root only
153
- * when `vbrief/` is absent. Never persist the dev fallback.
154
- */
155
- function syncBareVersionMarker(projectDir, version) {
156
- const normalized = normalizeVersion(version);
157
- if (!normalized || normalized === DEV_FALLBACK)
158
- return;
159
- let vbriefDir = null;
160
- try {
161
- vbriefDir = resolveLifecycleRoot(projectDir);
162
- }
163
- catch {
164
- // No xbrief/ layout present — write the root-level derivative instead.
165
- }
166
- let targetDir = projectDir;
167
- try {
168
- if (vbriefDir !== null && statSync(vbriefDir).isDirectory())
169
- targetDir = vbriefDir;
170
- }
171
- catch {
172
- // xbrief/ absent — write the root-level derivative instead
173
- }
174
- try {
175
- mkdirSync(targetDir, { recursive: true });
176
- writeFileSync(join(targetDir, ".deft-version"), `${normalized}\n`, "utf8");
177
- }
178
- catch {
179
- // best-effort, mirrors install-upgrade marker write
180
- }
181
- }
182
160
  /**
183
161
  * Retire a stale legacy `.deft/VERSION` after the canonical `.deft/core/VERSION`
184
162
  * has been (re)written (#2064). Folded in from the former `install-upgrade`
@@ -450,13 +428,25 @@ export async function runRefreshDeposit(args, io, seams = {}) {
450
428
  // .deft/core/VERSION has been rewritten (folded in from install-upgrade so no
451
429
  // manifest behavior is lost by the redirect). Best-effort; never fatal.
452
430
  migrateLegacyInstallManifest(projectDir, writtenManifestPath);
453
- // #2055: regenerate the bare .deft-version derivative so it agrees with the
454
- // freshly written manifest tag (otherwise doctor's manifest-agreement check
455
- // fails and the operator must hand-edit the marker). No-op refreshes skip
456
- // this projection because the manifest tag was not rewritten (#2118).
431
+ }
432
+ // #2595: payload freshness and consumer derivative freshness are independent.
433
+ // Always repair these cheap projections, including on the #2118 no-op path.
434
+ if (alreadyCurrent) {
435
+ syncExistingBareVersionMarker(projectDir, contentVersion);
436
+ }
437
+ else {
457
438
  syncBareVersionMarker(projectDir, contentVersion);
458
439
  }
440
+ // Do not turn a legacy-only or cache-only support tree into canonical
441
+ // lifecycle content before migrate:xbrief can transactionally converge it.
442
+ if (hasCanonicalXbriefLifecycle(projectDir)) {
443
+ syncConsumerXbriefSchemas(projectDir, deftDir);
444
+ }
459
445
  const agentsMdUpdated = writeAgentsMd(projectDir, deftDir, io);
446
+ writeAgentHookDeposit(projectDir, io);
447
+ // #2530: root `.githooks/` is a consumer derivative like #2595 marker/schemas —
448
+ // repair on every refresh, including the already-current no-op path.
449
+ writeConsumerGitHooks(projectDir, deftDir, io, seams.gitHooks);
460
450
  // #2148: the deft-core-guard CI workflow is only meaningful when the deposit
461
451
  // is git-tracked (committed vendor layout). On an npm-managed (gitignored)
462
452
  // deposit it creates untracked noise after every `directive update`. Probe
@@ -470,6 +460,7 @@ export async function runRefreshDeposit(args, io, seams = {}) {
470
460
  // destructive `git rm --cached .deft/core` un-track is the deliberate
471
461
  // `migrate --untrack-core` step (#2269), not `update`.
472
462
  ensureInitGitignoreLines(projectDir, io, { gitLsFiles: seams.gitLsFiles });
463
+ ensurePrettierIgnoreLines(projectDir, io);
473
464
  let taskfileWired = false;
474
465
  if (args.nonInteractive) {
475
466
  taskfileWired = ensureTaskfile(projectDir, io);