@deftai/directive 0.119.3 → 0.119.4
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/check.d.ts +9 -5
- package/dist/check.js +108 -24
- package/dist/doctor.d.ts +7 -2
- package/dist/doctor.js +103 -26
- package/dist/verify-hooks-installed.d.ts +3 -1
- package/dist/verify-hooks-installed.js +55 -2
- package/package.json +3 -3
package/dist/check.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
interface ParsedArgs {
|
|
2
|
+
export interface ParsedArgs {
|
|
3
3
|
frameworkRoot?: string;
|
|
4
|
-
projectRoot
|
|
4
|
+
projectRoot: string;
|
|
5
5
|
noCache?: boolean;
|
|
6
6
|
error?: string;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Resolve check's framework-root from `projectRoot` without last-resort
|
|
10
|
+
* `resolveDefaultFrameworkRoot` (#4722). Returns null on miss (fail-closed).
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveCheckFrameworkRoot(projectRoot: string, explicitRoot?: string | null): string | null;
|
|
13
|
+
export declare function parseArgs(argv: readonly string[]): ParsedArgs;
|
|
14
|
+
export declare function run(argv: readonly string[]): number;
|
|
11
15
|
//# sourceMappingURL=check.d.ts.map
|
package/dist/check.js
CHANGED
|
@@ -2,53 +2,137 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* check.ts -- CLI wrapper for the context-aware `task check` orchestrator (#1854, #1713).
|
|
4
4
|
*
|
|
5
|
-
* Usage: deft-ts check --framework-root <path> --project-root <path> [--no-cache]
|
|
5
|
+
* Usage: deft-ts check [--framework-root <path>] [--project-root <path>] [--no-cache]
|
|
6
|
+
*
|
|
7
|
+
* Omitted --project-root defaults to cwd. Omitted --framework-root walks
|
|
8
|
+
* explicit / DEFT_ROOT / source checkout / .deft/core / legacy deft/ and
|
|
9
|
+
* fail-closes instead of resolveDefaultFrameworkRoot (#4722).
|
|
6
10
|
*/
|
|
11
|
+
import { statSync } from "node:fs";
|
|
12
|
+
import { join, resolve } from "node:path";
|
|
7
13
|
import { fileURLToPath } from "node:url";
|
|
8
14
|
import { dispatchTaskCheck } from "@deftai/directive-core/check";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
import { DEFT_REPO_POSITIVE_MARKERS } from "@deftai/directive-core/doctor";
|
|
16
|
+
function isFrameworkSourceCheckoutAt(projectRoot) {
|
|
17
|
+
try {
|
|
18
|
+
if (!statSync(join(projectRoot, "main.md")).isFile()) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return DEFT_REPO_POSITIVE_MARKERS.every((marker) => {
|
|
26
|
+
try {
|
|
27
|
+
return statSync(join(projectRoot, marker)).isFile();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve check's framework-root from `projectRoot` without last-resort
|
|
36
|
+
* `resolveDefaultFrameworkRoot` (#4722). Returns null on miss (fail-closed).
|
|
37
|
+
*/
|
|
38
|
+
export function resolveCheckFrameworkRoot(projectRoot, explicitRoot) {
|
|
39
|
+
const explicit = explicitRoot?.trim();
|
|
40
|
+
if (explicit) {
|
|
41
|
+
return resolve(explicit);
|
|
42
|
+
}
|
|
43
|
+
const envRoot = process.env.DEFT_ROOT?.trim();
|
|
44
|
+
if (envRoot) {
|
|
45
|
+
return resolve(envRoot);
|
|
46
|
+
}
|
|
47
|
+
const root = resolve(projectRoot);
|
|
48
|
+
if (isFrameworkSourceCheckoutAt(root)) {
|
|
49
|
+
return root;
|
|
50
|
+
}
|
|
51
|
+
for (const rel of [join(".deft", "core"), "deft"]) {
|
|
52
|
+
const candidate = join(root, rel);
|
|
53
|
+
try {
|
|
54
|
+
if (statSync(candidate).isDirectory()) {
|
|
55
|
+
return candidate;
|
|
17
56
|
}
|
|
18
|
-
parsed.frameworkRoot = next;
|
|
19
|
-
i++;
|
|
20
57
|
}
|
|
21
|
-
|
|
22
|
-
|
|
58
|
+
catch {
|
|
59
|
+
// try next candidate
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function takeRootOptionValue(argv, index, flag) {
|
|
65
|
+
const next = argv[index + 1];
|
|
66
|
+
if (next === undefined || next.length === 0 || next.startsWith("-")) {
|
|
67
|
+
return { error: `argument ${flag}: expected one argument`, next: index };
|
|
68
|
+
}
|
|
69
|
+
return { value: next, next: index + 1 };
|
|
70
|
+
}
|
|
71
|
+
export function parseArgs(argv) {
|
|
72
|
+
let projectRoot = process.cwd();
|
|
73
|
+
let explicitFrameworkRoot;
|
|
74
|
+
let noCache = false;
|
|
75
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
76
|
+
const arg = argv[i] ?? "";
|
|
77
|
+
if (arg === "--no-cache") {
|
|
78
|
+
noCache = true;
|
|
23
79
|
}
|
|
24
80
|
else if (arg === "--project-root") {
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
27
|
-
return {
|
|
81
|
+
const taken = takeRootOptionValue(argv, i, "--project-root");
|
|
82
|
+
if (taken.error !== undefined) {
|
|
83
|
+
return { projectRoot, error: taken.error };
|
|
28
84
|
}
|
|
29
|
-
|
|
30
|
-
i
|
|
85
|
+
projectRoot = taken.value ?? projectRoot;
|
|
86
|
+
i = taken.next;
|
|
31
87
|
}
|
|
32
88
|
else if (arg.startsWith("--project-root=")) {
|
|
33
|
-
|
|
89
|
+
const value = arg.slice("--project-root=".length);
|
|
90
|
+
if (value.length === 0) {
|
|
91
|
+
return { projectRoot, error: "argument --project-root: expected one argument" };
|
|
92
|
+
}
|
|
93
|
+
projectRoot = value;
|
|
34
94
|
}
|
|
35
|
-
else if (arg === "--
|
|
36
|
-
|
|
95
|
+
else if (arg === "--framework-root") {
|
|
96
|
+
const taken = takeRootOptionValue(argv, i, "--framework-root");
|
|
97
|
+
if (taken.error !== undefined) {
|
|
98
|
+
return { projectRoot, error: taken.error };
|
|
99
|
+
}
|
|
100
|
+
explicitFrameworkRoot = taken.value;
|
|
101
|
+
i = taken.next;
|
|
102
|
+
}
|
|
103
|
+
else if (arg.startsWith("--framework-root=")) {
|
|
104
|
+
const value = arg.slice("--framework-root=".length);
|
|
105
|
+
if (value.length === 0) {
|
|
106
|
+
return { projectRoot, error: "argument --framework-root: expected one argument" };
|
|
107
|
+
}
|
|
108
|
+
explicitFrameworkRoot = value;
|
|
37
109
|
}
|
|
38
110
|
else {
|
|
39
|
-
return {
|
|
111
|
+
return {
|
|
112
|
+
projectRoot,
|
|
113
|
+
error: `unrecognized argument: ${arg}`,
|
|
114
|
+
};
|
|
40
115
|
}
|
|
41
116
|
}
|
|
117
|
+
const parsed = { projectRoot };
|
|
118
|
+
if (noCache) {
|
|
119
|
+
parsed.noCache = true;
|
|
120
|
+
}
|
|
121
|
+
const frameworkRoot = resolveCheckFrameworkRoot(projectRoot, explicitFrameworkRoot);
|
|
122
|
+
if (frameworkRoot !== null) {
|
|
123
|
+
parsed.frameworkRoot = frameworkRoot;
|
|
124
|
+
}
|
|
42
125
|
return parsed;
|
|
43
126
|
}
|
|
127
|
+
const FRAMEWORK_ROOT_MISS = "check: no framework-root at this project (source checkout, .deft/core, or legacy deft/). Run directive init.\n";
|
|
44
128
|
export function run(argv) {
|
|
45
129
|
const args = parseArgs(argv);
|
|
46
130
|
if (args.error !== undefined) {
|
|
47
131
|
process.stderr.write(`check: ${args.error}\n`);
|
|
48
132
|
return 2;
|
|
49
133
|
}
|
|
50
|
-
if (args.frameworkRoot === undefined
|
|
51
|
-
process.stderr.write(
|
|
134
|
+
if (args.frameworkRoot === undefined) {
|
|
135
|
+
process.stderr.write(FRAMEWORK_ROOT_MISS);
|
|
52
136
|
return 2;
|
|
53
137
|
}
|
|
54
138
|
return dispatchTaskCheck(args.frameworkRoot, args.projectRoot, { noCache: args.noCache });
|
package/dist/doctor.d.ts
CHANGED
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
export interface DepositFileSetHygieneResult {
|
|
3
3
|
readonly absent: readonly string[];
|
|
4
4
|
readonly contentRoot: string | null;
|
|
5
|
+
readonly walkRoot: string | null;
|
|
6
|
+
readonly installedRoot: string | null;
|
|
5
7
|
readonly skipped: boolean;
|
|
6
8
|
}
|
|
7
9
|
export interface EvaluateDepositFileSetOptions {
|
|
8
10
|
readonly contentRoot?: string;
|
|
11
|
+
readonly walkRoot?: string | null;
|
|
12
|
+
readonly installedRoot?: string | null;
|
|
9
13
|
}
|
|
10
|
-
|
|
14
|
+
export declare function sameResolvedPath(left: string, right: string): boolean;
|
|
15
|
+
/** Compare `.deft/core/` against `@deftai/directive-content` (#2804 / #4706). */
|
|
11
16
|
export declare function evaluateDepositFileSetHygiene(projectRoot: string, options?: EvaluateDepositFileSetOptions): DepositFileSetHygieneResult;
|
|
12
17
|
export declare function renderDepositFileSetHygieneLine(projectRoot: string, result?: DepositFileSetHygieneResult): string;
|
|
13
|
-
export declare function run(argv: string[]): number
|
|
18
|
+
export declare function run(argv: string[]): Promise<number>;
|
|
14
19
|
//# sourceMappingURL=doctor.d.ts.map
|
package/dist/doctor.js
CHANGED
|
@@ -1,30 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { resolveContentPackageRoot } from "@deftai/directive-core/dist/content-root.js";
|
|
6
|
+
import { resolveInstalledContentRoot } from "@deftai/directive-core/dist/deposit/resolve-content.js";
|
|
7
|
+
import { setDoctorAgentsTemplateRoot } from "@deftai/directive-core/dist/doctor/agents-md.js";
|
|
6
8
|
import { parseDoctorFlags } from "@deftai/directive-core/dist/doctor/flags.js";
|
|
7
9
|
import { cmdDoctor } from "@deftai/directive-core/dist/doctor/main.js";
|
|
8
10
|
import { findPackageAbsentDepositPathsSync } from "@deftai/directive-core/dist/init-deposit/hygiene.js";
|
|
9
11
|
import { evaluateInstalledDepositClosure, renderDeclaredDepositClosureLine, } from "@deftai/directive-core/dist/validate-content/deposit-required.js";
|
|
10
12
|
import { renderPrecutoverLine } from "@deftai/directive-core/dist/vbrief-validate/precutover.js";
|
|
11
13
|
import { renderStaleHeaderLine, renderXbriefMigrationLine, } from "@deftai/directive-core/xbrief-migrate";
|
|
12
|
-
|
|
14
|
+
export function sameResolvedPath(left, right) {
|
|
15
|
+
return canonicalizeExistingPath(left) === canonicalizeExistingPath(right);
|
|
16
|
+
}
|
|
17
|
+
function canonicalizeExistingPath(path) {
|
|
18
|
+
let canonical = resolve(path);
|
|
19
|
+
try {
|
|
20
|
+
if (existsSync(canonical))
|
|
21
|
+
canonical = realpathSync(canonical);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// keep resolve() fallback
|
|
25
|
+
}
|
|
26
|
+
return process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
27
|
+
}
|
|
28
|
+
function rootsDiverge(walkRoot, installedRoot) {
|
|
29
|
+
return walkRoot !== null && installedRoot !== null && !sameResolvedPath(walkRoot, installedRoot);
|
|
30
|
+
}
|
|
31
|
+
/** Compare `.deft/core/` against `@deftai/directive-content` (#2804 / #4706). */
|
|
13
32
|
export function evaluateDepositFileSetHygiene(projectRoot, options = {}) {
|
|
14
33
|
const deftDir = join(projectRoot, ".deft", "core");
|
|
34
|
+
const walkRoot = options.walkRoot !== undefined ? options.walkRoot : resolveContentPackageRoot(projectRoot);
|
|
35
|
+
const installedRoot = options.installedRoot ?? null;
|
|
15
36
|
if (!existsSync(deftDir)) {
|
|
16
|
-
return { absent: [], contentRoot: null, skipped: true };
|
|
37
|
+
return { absent: [], contentRoot: null, walkRoot, installedRoot, skipped: true };
|
|
17
38
|
}
|
|
18
|
-
const contentRoot = options.contentRoot ??
|
|
39
|
+
const contentRoot = options.contentRoot ?? installedRoot ?? walkRoot;
|
|
19
40
|
if (contentRoot === null || !existsSync(contentRoot)) {
|
|
20
|
-
return { absent: [], contentRoot: null, skipped: true };
|
|
41
|
+
return { absent: [], contentRoot: null, walkRoot, installedRoot, skipped: true };
|
|
21
42
|
}
|
|
22
43
|
return {
|
|
23
44
|
absent: findPackageAbsentDepositPathsSync(deftDir, contentRoot),
|
|
24
45
|
contentRoot,
|
|
46
|
+
walkRoot,
|
|
47
|
+
installedRoot,
|
|
25
48
|
skipped: false,
|
|
26
49
|
};
|
|
27
50
|
}
|
|
51
|
+
function hygieneRootDivergenceNote(result) {
|
|
52
|
+
if (!rootsDiverge(result.walkRoot, result.installedRoot ?? result.contentRoot)) {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
const compared = result.contentRoot ?? "";
|
|
56
|
+
const walk = result.walkRoot ?? "";
|
|
57
|
+
const installed = result.installedRoot ?? compared;
|
|
58
|
+
return ` Compared content root: ${compared}. Project walk-root: ${walk}. Engine content root: ${installed}.`;
|
|
59
|
+
}
|
|
60
|
+
function namesUpdatePruneRecovery(result) {
|
|
61
|
+
const compared = result.contentRoot;
|
|
62
|
+
const walkRoot = result.walkRoot;
|
|
63
|
+
if (compared === null || walkRoot === null) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (!rootsDiverge(walkRoot, result.installedRoot)) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return !sameResolvedPath(compared, walkRoot);
|
|
70
|
+
}
|
|
28
71
|
export function renderDepositFileSetHygieneLine(projectRoot, result = evaluateDepositFileSetHygiene(projectRoot)) {
|
|
29
72
|
const deftDir = join(projectRoot, ".deft", "core");
|
|
30
73
|
if (!existsSync(deftDir)) {
|
|
@@ -39,37 +82,71 @@ export function renderDepositFileSetHygieneLine(projectRoot, result = evaluateDe
|
|
|
39
82
|
}
|
|
40
83
|
const sample = result.absent.slice(0, 5).join(", ");
|
|
41
84
|
const suffix = result.absent.length > 5 ? ` (+${result.absent.length - 5} more)` : "";
|
|
85
|
+
const divergence = hygieneRootDivergenceNote(result);
|
|
86
|
+
const recovery = namesUpdatePruneRecovery(result)
|
|
87
|
+
? "Run `directive update` to auto-prune these stale deposit files (#2804)."
|
|
88
|
+
: "Do not run `directive update` to prune extras versus the project walk-root; that tree is not the engine content update reconciled (#4706). Leftover pin reconstitution is #4710.";
|
|
42
89
|
return (`Deposit hygiene: fail -- ${result.absent.length} package-absent file(s) in .deft/core ` +
|
|
43
|
-
`(not shipped by @deftai/directive-content). Examples: ${sample}${suffix}
|
|
44
|
-
|
|
90
|
+
`(not shipped by @deftai/directive-content). Examples: ${sample}${suffix}.${divergence} ` +
|
|
91
|
+
recovery);
|
|
92
|
+
}
|
|
93
|
+
async function resolveEngineContentRoot() {
|
|
94
|
+
try {
|
|
95
|
+
const installed = await resolveInstalledContentRoot();
|
|
96
|
+
return typeof installed === "string" && installed.length > 0 ? installed : null;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
45
101
|
}
|
|
46
|
-
export function run(argv) {
|
|
102
|
+
export async function run(argv) {
|
|
47
103
|
// #2022: surface pre-cutover (pre-v0.20 document model) migration state alongside the
|
|
48
104
|
// core doctor report. Only emit on a valid, human-readable invocation: suppressed under
|
|
49
105
|
// --json (so the machine-readable report stays valid), on --help, and when unknown flags
|
|
50
106
|
// are present (so an invalid invocation still mirrors the core error path exactly).
|
|
51
107
|
const flags = parseDoctorFlags(argv);
|
|
52
108
|
let depositHygieneExit = 0;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
109
|
+
const installedRoot = await resolveEngineContentRoot();
|
|
110
|
+
if (installedRoot !== null) {
|
|
111
|
+
setDoctorAgentsTemplateRoot(installedRoot);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
if (!flags.json && !flags.help && flags.unknown.length === 0) {
|
|
115
|
+
const projectRoot = flags.projectRoot ?? process.cwd();
|
|
116
|
+
const walkRoot = resolveContentPackageRoot(projectRoot);
|
|
117
|
+
const depositResult = evaluateDepositFileSetHygiene(projectRoot, {
|
|
118
|
+
contentRoot: installedRoot ?? undefined,
|
|
119
|
+
walkRoot,
|
|
120
|
+
installedRoot,
|
|
121
|
+
});
|
|
122
|
+
process.stdout.write(`${renderPrecutoverLine(projectRoot)}\n`);
|
|
123
|
+
process.stdout.write(`${renderXbriefMigrationLine(projectRoot)}\n`);
|
|
124
|
+
process.stdout.write(`${renderStaleHeaderLine(projectRoot)}\n`);
|
|
125
|
+
process.stdout.write(`${renderDepositFileSetHygieneLine(projectRoot, depositResult)}\n`);
|
|
126
|
+
const closure = evaluateInstalledDepositClosure(projectRoot);
|
|
127
|
+
process.stdout.write(`${renderDeclaredDepositClosureLine(closure)}\n`);
|
|
128
|
+
if (flags.full && !depositResult.skipped && depositResult.absent.length > 0) {
|
|
129
|
+
depositHygieneExit = 1;
|
|
130
|
+
}
|
|
131
|
+
if (flags.full &&
|
|
132
|
+
!closure.skipped &&
|
|
133
|
+
(closure.missing.length > 0 || closure.error !== null)) {
|
|
134
|
+
depositHygieneExit = 1;
|
|
135
|
+
}
|
|
67
136
|
}
|
|
137
|
+
const doctorExit = cmdDoctor(argv);
|
|
138
|
+
return Math.max(depositHygieneExit, doctorExit);
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
setDoctorAgentsTemplateRoot(undefined);
|
|
68
142
|
}
|
|
69
|
-
const doctorExit = cmdDoctor(argv);
|
|
70
|
-
return Math.max(depositHygieneExit, doctorExit);
|
|
71
143
|
}
|
|
72
144
|
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
73
|
-
|
|
145
|
+
void run(process.argv.slice(2)).then((code) => {
|
|
146
|
+
process.exit(code);
|
|
147
|
+
}, (err) => {
|
|
148
|
+
process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|
|
74
151
|
}
|
|
75
152
|
//# sourceMappingURL=doctor.js.map
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { type AgentHookHealthResult, type AgentHookReadinessResult, type EvaluateResult } from "@deftai/directive-core/verify-env";
|
|
2
|
+
import { type AgentHookHealthResult, type AgentHookReadinessResult, type EvaluateResult, repairAgentHookRegistrations } from "@deftai/directive-core/verify-env";
|
|
3
3
|
type HookScope = "git" | "agent" | "all";
|
|
4
4
|
interface ParsedArgs {
|
|
5
5
|
projectRoot: string;
|
|
6
6
|
quiet: boolean;
|
|
7
7
|
scope: HookScope;
|
|
8
8
|
live: boolean;
|
|
9
|
+
repair: boolean;
|
|
9
10
|
error?: string;
|
|
10
11
|
}
|
|
11
12
|
export interface VerifyHooksInstalledCliSeams {
|
|
12
13
|
readonly evaluateGit?: (projectRoot: string) => EvaluateResult;
|
|
13
14
|
readonly evaluateAgent?: (projectRoot: string) => AgentHookHealthResult;
|
|
14
15
|
readonly evaluateReadiness?: (projectRoot: string) => AgentHookReadinessResult;
|
|
16
|
+
readonly repairRegistrations?: typeof repairAgentHookRegistrations;
|
|
15
17
|
readonly writeOut?: (text: string) => void;
|
|
16
18
|
readonly writeErr?: (text: string) => void;
|
|
17
19
|
}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { evaluate, evaluateAgentHookReadinessSafely, evaluateAgentHooks, } from "@deftai/directive-core/verify-env";
|
|
4
|
+
import { evaluate, evaluateAgentHookReadiness, evaluateAgentHookReadinessSafely, evaluateAgentHooks, formatAgentHookRepairDisposition, repairAgentHookRegistrations, } from "@deftai/directive-core/verify-env";
|
|
5
5
|
export function parseArgs(argv) {
|
|
6
|
-
const parsed = {
|
|
6
|
+
const parsed = {
|
|
7
|
+
projectRoot: ".",
|
|
8
|
+
quiet: false,
|
|
9
|
+
scope: "git",
|
|
10
|
+
live: false,
|
|
11
|
+
repair: false,
|
|
12
|
+
};
|
|
7
13
|
for (let i = 0; i < argv.length; i += 1) {
|
|
8
14
|
const arg = argv[i];
|
|
9
15
|
if (arg === "--quiet")
|
|
10
16
|
parsed.quiet = true;
|
|
11
17
|
else if (arg === "--live")
|
|
12
18
|
parsed.live = true;
|
|
19
|
+
else if (arg === "--repair")
|
|
20
|
+
parsed.repair = true;
|
|
13
21
|
else if (arg === "--project-root") {
|
|
14
22
|
const value = argv[i + 1];
|
|
15
23
|
if (value === undefined) {
|
|
@@ -46,6 +54,9 @@ export function parseArgs(argv) {
|
|
|
46
54
|
if (parsed.live && parsed.scope === "git") {
|
|
47
55
|
return { ...parsed, error: "argument --live requires --scope=agent or --scope=all" };
|
|
48
56
|
}
|
|
57
|
+
if (parsed.repair && parsed.scope === "git") {
|
|
58
|
+
return { ...parsed, error: "argument --repair requires --scope=agent or --scope=all" };
|
|
59
|
+
}
|
|
49
60
|
return parsed;
|
|
50
61
|
}
|
|
51
62
|
/** Verify git hooks, structural agent hooks, or functional agent-hook readiness. */
|
|
@@ -63,6 +74,48 @@ export function run(argv, seams = {}) {
|
|
|
63
74
|
const evaluateReadiness = seams.evaluateReadiness
|
|
64
75
|
? (root) => evaluateAgentHookReadinessSafely(root, seams.evaluateReadiness)
|
|
65
76
|
: evaluateAgentHookReadinessSafely;
|
|
77
|
+
if (args.repair) {
|
|
78
|
+
const repair = seams.repairRegistrations ?? repairAgentHookRegistrations;
|
|
79
|
+
try {
|
|
80
|
+
let live;
|
|
81
|
+
const evaluateRepairReadiness = seams.evaluateReadiness
|
|
82
|
+
? (root) => evaluateAgentHookReadinessSafely(root, seams.evaluateReadiness)
|
|
83
|
+
: (root) => evaluateAgentHookReadinessSafely(root, (next) => evaluateAgentHookReadiness(next, { consumerContext: () => true }));
|
|
84
|
+
const repaired = repair(projectRoot, {
|
|
85
|
+
reevaluate: (root) => {
|
|
86
|
+
live = evaluateRepairReadiness(root);
|
|
87
|
+
return live;
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
if (live === undefined) {
|
|
91
|
+
writeErr("agent hook repair refused: live recheck did not run\n");
|
|
92
|
+
return 2;
|
|
93
|
+
}
|
|
94
|
+
if (!args.quiet) {
|
|
95
|
+
writeOut(`${formatAgentHookRepairDisposition(repaired.written)}\n`);
|
|
96
|
+
if (live.stream === "stdout")
|
|
97
|
+
writeOut(`${live.message}\n`);
|
|
98
|
+
else
|
|
99
|
+
writeErr(`${live.message}\n`);
|
|
100
|
+
}
|
|
101
|
+
const gitResults = args.scope === "all" ? [evaluateGit(projectRoot)] : [];
|
|
102
|
+
if (!args.quiet) {
|
|
103
|
+
for (const result of gitResults) {
|
|
104
|
+
if (result.stream === "stdout")
|
|
105
|
+
writeOut(`${result.message}\n`);
|
|
106
|
+
else if (result.stream === "stderr")
|
|
107
|
+
writeErr(`${result.message}\n`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const gitCode = gitResults.length > 0 ? Math.max(...gitResults.map((result) => result.code)) : 0;
|
|
111
|
+
return Math.max(repaired.after.code, gitCode);
|
|
112
|
+
}
|
|
113
|
+
catch (cause) {
|
|
114
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
115
|
+
writeErr(`agent hook repair refused: ${detail}\n`);
|
|
116
|
+
return 2;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
66
119
|
const results = [
|
|
67
120
|
...(args.scope === "git" || args.scope === "all" ? [evaluateGit(projectRoot)] : []),
|
|
68
121
|
...(args.scope === "agent" || args.scope === "all"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive",
|
|
3
|
-
"version": "0.119.
|
|
3
|
+
"version": "0.119.4",
|
|
4
4
|
"description": "Directive CLI — npm install path for the Deft Directive framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://deftai.github.io/directive/",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"provenance": true
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@deftai/directive-core": "^0.119.
|
|
38
|
-
"@deftai/directive-content": "^0.119.
|
|
37
|
+
"@deftai/directive-core": "^0.119.4",
|
|
38
|
+
"@deftai/directive-content": "^0.119.4"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsc -b"
|