@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.
- package/dist/cache/empty-populate.d.ts +44 -0
- package/dist/cache/empty-populate.js +117 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +1 -0
- package/dist/check-updates/index.d.ts +10 -0
- package/dist/check-updates/index.js +81 -1
- package/dist/doctor/main.d.ts +1 -0
- package/dist/doctor/main.js +48 -0
- package/dist/doctor/payload-staleness.js +7 -1
- package/dist/doctor/types.d.ts +3 -0
- package/dist/eval/triggers.d.ts +76 -0
- package/dist/eval/triggers.js +259 -0
- package/dist/eval-triggers-relocation/evaluate.d.ts +36 -0
- package/dist/eval-triggers-relocation/evaluate.js +115 -0
- package/dist/eval-triggers-relocation/index.d.ts +2 -0
- package/dist/eval-triggers-relocation/index.js +2 -0
- package/dist/hooks/dispatcher.d.ts +43 -0
- package/dist/hooks/dispatcher.js +173 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/scope.d.ts +12 -0
- package/dist/hooks/scope.js +46 -0
- package/dist/hooks/tools.d.ts +4 -0
- package/dist/hooks/tools.js +23 -0
- package/dist/init-deposit/agent-hooks.d.ts +22 -0
- package/dist/init-deposit/agent-hooks.js +238 -0
- package/dist/init-deposit/hygiene.js +4 -0
- package/dist/init-deposit/index.d.ts +1 -0
- package/dist/init-deposit/index.js +1 -0
- package/dist/init-deposit/init-deposit.js +4 -0
- package/dist/init-deposit/prettierignore.d.ts +21 -0
- package/dist/init-deposit/prettierignore.js +83 -0
- package/dist/init-deposit/refresh.d.ts +3 -1
- package/dist/init-deposit/refresh.js +31 -40
- package/dist/init-deposit/scaffold.js +6 -12
- package/dist/init-deposit/xbrief-projections.d.ts +18 -0
- package/dist/init-deposit/xbrief-projections.js +117 -0
- package/dist/intake/reconcile-issues.d.ts +11 -0
- package/dist/intake/reconcile-issues.js +166 -51
- package/dist/issue-sync/index.d.ts +2 -0
- package/dist/issue-sync/index.js +2 -0
- package/dist/issue-sync/sync-from-xbrief-cli.d.ts +11 -0
- package/dist/issue-sync/sync-from-xbrief-cli.js +44 -0
- package/dist/issue-sync/sync-from-xbrief.d.ts +46 -0
- package/dist/issue-sync/sync-from-xbrief.js +208 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +5 -0
- package/dist/platform/platform-capabilities.js +9 -0
- package/dist/platform/shell-context.d.ts +30 -0
- package/dist/platform/shell-context.js +111 -0
- package/dist/pr-monitor/cadence.d.ts +6 -0
- package/dist/pr-monitor/cadence.js +13 -0
- package/dist/pr-monitor/index.d.ts +1 -1
- package/dist/pr-monitor/index.js +1 -1
- package/dist/pr-monitor/monitor.d.ts +7 -0
- package/dist/pr-monitor/monitor.js +59 -13
- package/dist/preflight-cache/evaluate.d.ts +3 -8
- package/dist/preflight-cache/evaluate.js +19 -19
- package/dist/release/build-dist.js +1 -0
- package/dist/release/constants.d.ts +2 -0
- package/dist/release/constants.js +8 -1
- package/dist/release/flags.js +26 -0
- package/dist/release/issue-state-fetch.d.ts +27 -0
- package/dist/release/issue-state-fetch.js +146 -0
- package/dist/release/main.js +1 -0
- package/dist/release/native-steps.js +6 -4
- package/dist/release/pipeline.js +15 -5
- package/dist/release/preflight.d.ts +6 -2
- package/dist/release/preflight.js +19 -5
- package/dist/release/types.d.ts +3 -1
- package/dist/release-e2e/constants.js +6 -6
- package/dist/release-e2e/flags.d.ts +2 -0
- package/dist/release-e2e/flags.js +8 -4
- package/dist/release-e2e/greenfield-python-free-smoke-cli.js +40 -1
- package/dist/release-e2e/greenfield-python-free-smoke.d.ts +2 -0
- package/dist/release-e2e/greenfield-python-free-smoke.js +28 -8
- package/dist/release-e2e/main.js +15 -9
- package/dist/release-e2e/types.d.ts +4 -2
- package/dist/resolution/classify.d.ts +1 -1
- package/dist/resolution/classify.js +36 -11
- package/dist/scope/project-definition-sync.js +2 -150
- package/dist/scope/registry-artifact-sync.d.ts +3 -0
- package/dist/scope/registry-artifact-sync.js +159 -0
- package/dist/scope/specification-sync.d.ts +3 -0
- package/dist/scope/specification-sync.js +16 -0
- package/dist/scope/transition.js +40 -12
- package/dist/session/cache-recovery.d.ts +19 -0
- package/dist/session/cache-recovery.js +26 -0
- package/dist/session/session-start.d.ts +2 -0
- package/dist/session/session-start.js +14 -3
- package/dist/session/verify-session-ritual.d.ts +17 -0
- package/dist/session/verify-session-ritual.js +64 -0
- package/dist/triage/help/registry-data.d.ts +12 -1
- package/dist/triage/help/registry-data.js +22 -1
- package/dist/triage/welcome/default-mode.js +2 -0
- package/dist/triage/welcome/onboard.js +2 -0
- package/dist/ts-check-lane/run-lane.d.ts +5 -0
- package/dist/ts-check-lane/run-lane.js +9 -0
- package/dist/vbrief-reconcile/labels.d.ts +3 -0
- package/dist/vbrief-reconcile/labels.js +14 -0
- package/dist/vbrief-reconcile/main.d.ts +1 -0
- package/dist/vbrief-reconcile/main.js +13 -3
- package/dist/vbrief-reconcile/parity-scenarios.js +18 -4
- package/dist/vbrief-reconcile/repo-guard.d.ts +17 -0
- package/dist/vbrief-reconcile/repo-guard.js +33 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +3 -0
- package/dist/vbrief-reconcile/umbrellas.js +17 -1
- package/dist/verify-env/agent-hooks.d.ts +11 -0
- package/dist/verify-env/agent-hooks.js +46 -0
- package/dist/verify-env/command-spawn.d.ts +5 -0
- package/dist/verify-env/command-spawn.js +28 -9
- package/dist/verify-env/index.d.ts +1 -0
- package/dist/verify-env/index.js +1 -0
- package/dist/verify-env/verify-hooks-installed.js +5 -5
- package/dist/vitest-runner/coverage-debt-teardown.d.ts +3 -0
- package/dist/vitest-runner/coverage-debt-teardown.js +33 -0
- package/dist/vitest-runner/coverage-debt.d.ts +50 -0
- package/dist/vitest-runner/coverage-debt.js +155 -0
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +12 -0
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +24 -0
- package/dist/xbrief-migrate/detect.js +46 -1
- package/dist/xbrief-migrate/migrate-project.js +19 -4
- package/dist/xbrief-migrate/migration-containment.d.ts +6 -0
- package/dist/xbrief-migrate/migration-containment.js +37 -0
- 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" },
|
|
@@ -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,
|
|
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 {
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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);
|