@deftai/directive-core 0.68.1 → 0.70.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/deposit/contain.d.ts +41 -0
- package/dist/deposit/contain.js +113 -0
- package/dist/deposit/copy-tree.js +8 -2
- package/dist/doctor/checks.d.ts +11 -0
- package/dist/doctor/checks.js +75 -2
- package/dist/doctor/constants.d.ts +9 -0
- package/dist/doctor/constants.js +11 -0
- package/dist/doctor/main.d.ts +47 -1
- package/dist/doctor/main.js +406 -3
- package/dist/doctor/manifest.d.ts +20 -0
- package/dist/doctor/manifest.js +22 -0
- package/dist/doctor/payload-staleness.d.ts +8 -0
- package/dist/doctor/payload-staleness.js +11 -8
- package/dist/doctor/types.d.ts +56 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/gitignore.d.ts +18 -0
- package/dist/init-deposit/gitignore.js +94 -15
- package/dist/init-deposit/headless-manifest.d.ts +87 -0
- package/dist/init-deposit/headless-manifest.js +261 -0
- package/dist/init-deposit/index.d.ts +3 -0
- package/dist/init-deposit/index.js +3 -0
- package/dist/init-deposit/init-deposit.js +6 -0
- package/dist/init-deposit/init-dispatch.d.ts +90 -0
- package/dist/init-deposit/init-dispatch.js +170 -0
- package/dist/init-deposit/refresh.d.ts +79 -2
- package/dist/init-deposit/refresh.js +182 -5
- package/dist/init-deposit/scaffold.d.ts +25 -0
- package/dist/init-deposit/scaffold.js +57 -0
- package/dist/init-deposit/untrack-core.d.ts +84 -0
- package/dist/init-deposit/untrack-core.js +150 -0
- package/dist/intake/issue-ingest.d.ts +11 -0
- package/dist/intake/issue-ingest.js +86 -11
- package/dist/policy/plan-extensions.d.ts +31 -0
- package/dist/policy/plan-extensions.js +45 -0
- package/dist/policy/wip.d.ts +2 -2
- package/dist/policy/wip.js +2 -2
- package/dist/resolution/classify.d.ts +41 -0
- package/dist/resolution/classify.js +138 -0
- package/dist/resolution/engine-ladder.d.ts +98 -0
- package/dist/resolution/engine-ladder.js +185 -0
- package/dist/resolution/index.d.ts +19 -0
- package/dist/resolution/index.js +19 -0
- package/dist/resolution/integrity.d.ts +48 -0
- package/dist/resolution/integrity.js +80 -0
- package/dist/resolution/package-manager.d.ts +77 -0
- package/dist/resolution/package-manager.js +103 -0
- package/dist/resolution/pin.d.ts +73 -0
- package/dist/resolution/pin.js +169 -0
- package/dist/resolution/plan.d.ts +66 -0
- package/dist/resolution/plan.js +219 -0
- package/dist/resolution/skew-policy.d.ts +46 -0
- package/dist/resolution/skew-policy.js +120 -0
- package/dist/session/session-start.d.ts +2 -0
- package/dist/session/session-start.js +28 -1
- package/dist/swarm/routing.js +10 -3
- package/dist/triage/bulk/index.d.ts +11 -2
- package/dist/triage/bulk/index.js +41 -4
- package/dist/triage/help/registry-data.d.ts +5 -5
- package/dist/triage/help/registry-data.js +18 -5
- package/dist/triage/scope/cli.d.ts +2 -1
- package/dist/triage/scope/cli.js +30 -6
- package/dist/triage/scope/mutations.d.ts +10 -0
- package/dist/triage/scope/mutations.js +22 -0
- package/dist/triage/welcome/constants.d.ts +1 -2
- package/dist/triage/welcome/constants.js +1 -2
- package/dist/triage/welcome/index.d.ts +1 -0
- package/dist/triage/welcome/index.js +1 -0
- package/dist/triage/welcome/onboard.d.ts +35 -0
- package/dist/triage/welcome/onboard.js +94 -0
- package/dist/umbrella-current-shape/index.d.ts +25 -1
- package/dist/umbrella-current-shape/index.js +84 -4
- package/dist/user-config/index.d.ts +2 -0
- package/dist/user-config/index.js +2 -0
- package/dist/user-config/resolve-user-md.d.ts +76 -0
- package/dist/user-config/resolve-user-md.js +120 -0
- package/dist/vbrief-build/project-definition-io.d.ts +6 -0
- package/dist/vbrief-build/project-definition-io.js +7 -2
- package/dist/vbrief-validate/precutover.js +7 -3
- package/dist/xbrief-migrate/constants.d.ts +10 -0
- package/dist/xbrief-migrate/constants.js +19 -0
- package/dist/xbrief-migrate/detect.d.ts +34 -0
- package/dist/xbrief-migrate/detect.js +33 -0
- package/dist/xbrief-migrate/fs-helpers.d.ts +13 -0
- package/dist/xbrief-migrate/fs-helpers.js +46 -1
- package/dist/xbrief-migrate/index.d.ts +4 -2
- package/dist/xbrief-migrate/index.js +4 -2
- package/dist/xbrief-migrate/migrate-project.d.ts +23 -1
- package/dist/xbrief-migrate/migrate-project.js +97 -8
- package/dist/xbrief-migrate/signpost.d.ts +7 -1
- package/dist/xbrief-migrate/signpost.js +18 -3
- package/package.json +7 -3
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orthogonal fact-set classifier for the resolution spine (#2264 / epic #2203).
|
|
3
|
+
*
|
|
4
|
+
* `classify()` returns a flat, independent fact-set — never a single collapsed
|
|
5
|
+
* enum. `plan()` owns the collapse into one recommended action, so there is
|
|
6
|
+
* exactly one precedence table in the system.
|
|
7
|
+
*
|
|
8
|
+
* It REUSES the existing detectors rather than re-implementing them:
|
|
9
|
+
* - pre-cutover artifacts -> `../vbrief-validate/precutover.ts`
|
|
10
|
+
* - managed-section sha -> `../platform/agents-md.ts::parseManagedSectionAttrs`
|
|
11
|
+
* - deposited payload version-> `../doctor/manifest.ts` + `../init-deposit/constants.ts`
|
|
12
|
+
* - committed pin -> `./pin.ts`
|
|
13
|
+
*/
|
|
14
|
+
import { execFileSync } from "node:child_process";
|
|
15
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { locateManifest, manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
|
|
18
|
+
import { CANONICAL_INSTALL_ROOT } from "../init-deposit/constants.js";
|
|
19
|
+
import { extractManagedSection, parseManagedSectionAttrs } from "../platform/agents-md.js";
|
|
20
|
+
import { detectPreCutover } from "../vbrief-validate/precutover.js";
|
|
21
|
+
import { readPin } from "./pin.js";
|
|
22
|
+
/** App-source markers used for the `hasAppCode` heuristic. */
|
|
23
|
+
const APP_CODE_MARKERS = [
|
|
24
|
+
"package.json",
|
|
25
|
+
"pyproject.toml",
|
|
26
|
+
"go.mod",
|
|
27
|
+
"Cargo.toml",
|
|
28
|
+
"pom.xml",
|
|
29
|
+
"build.gradle",
|
|
30
|
+
"Gemfile",
|
|
31
|
+
"src",
|
|
32
|
+
];
|
|
33
|
+
const SEMVER_IN_TEXT_RE = /(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/;
|
|
34
|
+
function defaultIsFile(p) {
|
|
35
|
+
try {
|
|
36
|
+
return statSync(p).isFile();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function defaultIsDir(p) {
|
|
43
|
+
try {
|
|
44
|
+
return statSync(p).isDirectory();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function defaultReadText(p) {
|
|
51
|
+
try {
|
|
52
|
+
if (!existsSync(p))
|
|
53
|
+
return null;
|
|
54
|
+
return readFileSync(p, "utf8");
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function probeEngineVersion(binary) {
|
|
61
|
+
try {
|
|
62
|
+
const out = execFileSync(binary, ["--version"], {
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
timeout: 5000,
|
|
65
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
66
|
+
});
|
|
67
|
+
const match = SEMVER_IN_TEXT_RE.exec(out);
|
|
68
|
+
return match?.[1] ?? null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Default engine probe: try `directive --version`, then `deft --version`. */
|
|
75
|
+
export function defaultEngineProbe() {
|
|
76
|
+
for (const binary of ["directive", "deft"]) {
|
|
77
|
+
const version = probeEngineVersion(binary);
|
|
78
|
+
if (version !== null) {
|
|
79
|
+
return { reachable: true, version };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { reachable: false, version: null };
|
|
83
|
+
}
|
|
84
|
+
function detectAppCode(cwd, isFile, isDir) {
|
|
85
|
+
return APP_CODE_MARKERS.some((marker) => marker === "src" ? isDir(join(cwd, marker)) : isFile(join(cwd, marker)));
|
|
86
|
+
}
|
|
87
|
+
function readManagedSection(cwd, readText) {
|
|
88
|
+
const text = readText(join(cwd, "AGENTS.md"));
|
|
89
|
+
if (text === null)
|
|
90
|
+
return { hasManagedSection: false, managedSectionSha: null };
|
|
91
|
+
const section = extractManagedSection(text);
|
|
92
|
+
if (section === null)
|
|
93
|
+
return { hasManagedSection: false, managedSectionSha: null };
|
|
94
|
+
const attrs = parseManagedSectionAttrs(section);
|
|
95
|
+
return { hasManagedSection: true, managedSectionSha: attrs?.sha ?? null };
|
|
96
|
+
}
|
|
97
|
+
function readPayloadVersion(cwd, hasDeftCore, isFile, readText) {
|
|
98
|
+
if (!hasDeftCore)
|
|
99
|
+
return null;
|
|
100
|
+
const manifestPath = locateManifest(cwd, CANONICAL_INSTALL_ROOT, isFile);
|
|
101
|
+
if (manifestPath === null)
|
|
102
|
+
return null;
|
|
103
|
+
const text = readText(manifestPath);
|
|
104
|
+
if (text === null)
|
|
105
|
+
return null;
|
|
106
|
+
return manifestTagToVersion(parseInstallManifest(text));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Classify a project directory into the orthogonal resolution fact-set. All I/O
|
|
110
|
+
* is behind injectable seams so callers (tests, sandboxed runtimes) can supply a
|
|
111
|
+
* deterministic view of the filesystem and the engine-reachability probe.
|
|
112
|
+
*/
|
|
113
|
+
export function classify(cwd, seams = {}) {
|
|
114
|
+
const isFile = seams.isFile ?? defaultIsFile;
|
|
115
|
+
const isDir = seams.isDir ?? defaultIsDir;
|
|
116
|
+
const readText = seams.readText ?? defaultReadText;
|
|
117
|
+
const engineProbe = seams.engineProbe ?? defaultEngineProbe;
|
|
118
|
+
const preCutoverProbe = seams.preCutoverProbe ?? ((dir) => detectPreCutover(dir).preCutover);
|
|
119
|
+
const hasDeftCore = isDir(join(cwd, CANONICAL_INSTALL_ROOT));
|
|
120
|
+
const { hasManagedSection, managedSectionSha } = readManagedSection(cwd, readText);
|
|
121
|
+
const engine = engineProbe();
|
|
122
|
+
const pin = readPin(cwd, { isFile, readText });
|
|
123
|
+
return {
|
|
124
|
+
hasGit: isDir(join(cwd, ".git")) || isFile(join(cwd, ".git")),
|
|
125
|
+
hasAppCode: detectAppCode(cwd, isFile, isDir),
|
|
126
|
+
hasDeftCore,
|
|
127
|
+
deftCorePayloadVersion: readPayloadVersion(cwd, hasDeftCore, isFile, readText),
|
|
128
|
+
hasManagedSection,
|
|
129
|
+
managedSectionSha,
|
|
130
|
+
hasVbrief: isDir(join(cwd, "vbrief")),
|
|
131
|
+
hasXbrief: isDir(join(cwd, "xbrief")),
|
|
132
|
+
preCutoverArtifacts: preCutoverProbe(cwd),
|
|
133
|
+
engineReachable: engine.reachable,
|
|
134
|
+
engineVersion: engine.version,
|
|
135
|
+
pinVersion: pin.pinVersion,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global-first engine-resolution ladder for the resolution spine (#2264, from #2124).
|
|
3
|
+
*
|
|
4
|
+
* The defect this closes is `execution-env != install-env`: an agent can read
|
|
5
|
+
* every rule in `.deft/core/` yet execute no gate because the engine that is
|
|
6
|
+
* reachable in the environment it is ACTUALLY running in is absent or stale.
|
|
7
|
+
* The ladder detects that mismatch and self-heals only then:
|
|
8
|
+
*
|
|
9
|
+
* 1. global `deft` reachable AND >= pin -> use it
|
|
10
|
+
* 2. local `.deft/.cli/<platform>` reachable, intact, >= pin -> use it
|
|
11
|
+
* 3. must install:
|
|
12
|
+
* a. registry up + global prefix writable -> npm i -g @deftai/directive@<pin>
|
|
13
|
+
* b. registry up + global prefix NOT writable (sandbox) -> npm install --prefix .deft/.cli/<platform>
|
|
14
|
+
* c. registry down + staged tarball -> install from staged payload
|
|
15
|
+
* d. registry down + no tarball -> hard-fail ("stage this")
|
|
16
|
+
*
|
|
17
|
+
* `decideEngineLadder` is a PURE decision function (no I/O). The side-effecting
|
|
18
|
+
* install is factored behind an injected runner in `resolveEngine` so the whole
|
|
19
|
+
* self-heal path is unit-testable without touching the network.
|
|
20
|
+
*/
|
|
21
|
+
import type { IntegrityResult } from "./integrity.js";
|
|
22
|
+
export type LadderRung = "global" | "local" | "install-global" | "install-sandbox" | "install-staged" | "hard-fail";
|
|
23
|
+
export interface LocalEngineFacts {
|
|
24
|
+
/** Version reported by the local engine, or null. */
|
|
25
|
+
readonly version: string | null;
|
|
26
|
+
/** Integrity classification of `.deft/.cli/<platform>`. */
|
|
27
|
+
readonly integrity: IntegrityResult;
|
|
28
|
+
}
|
|
29
|
+
export interface LadderFacts {
|
|
30
|
+
/** Canonical committed pin. */
|
|
31
|
+
readonly pinVersion: string | null;
|
|
32
|
+
/** Version of the globally-reachable engine, or null when absent. */
|
|
33
|
+
readonly globalEngineVersion: string | null;
|
|
34
|
+
/** Local engine facts, or null when never installed. */
|
|
35
|
+
readonly localEngine: LocalEngineFacts | null;
|
|
36
|
+
/** The npm registry is reachable. */
|
|
37
|
+
readonly registryUp: boolean;
|
|
38
|
+
/** The global npm prefix is writable (false inside a sandbox). */
|
|
39
|
+
readonly globalPrefixWritable: boolean;
|
|
40
|
+
/** A pre-staged tarball / vendored payload is available for offline install. */
|
|
41
|
+
readonly stagedTarballAvailable: boolean;
|
|
42
|
+
/** Platform id (for trace / install target). */
|
|
43
|
+
readonly platform: string;
|
|
44
|
+
}
|
|
45
|
+
export interface LadderDecision {
|
|
46
|
+
readonly rung: LadderRung;
|
|
47
|
+
/** True when the engine is resolved WITHOUT an install (global or local). */
|
|
48
|
+
readonly usable: boolean;
|
|
49
|
+
/** Version the ladder resolved to when usable, else null. */
|
|
50
|
+
readonly resolvedVersion: string | null;
|
|
51
|
+
/** Structured trace of every rung evaluated. */
|
|
52
|
+
readonly trace: string;
|
|
53
|
+
/** One-line reason for the chosen rung. */
|
|
54
|
+
readonly reason: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Pure global-first ladder decision. Emits a structured trace describing each
|
|
58
|
+
* rung it evaluated and why it was skipped or chosen.
|
|
59
|
+
*/
|
|
60
|
+
export declare function decideEngineLadder(facts: LadderFacts): LadderDecision;
|
|
61
|
+
/** Outcome of a side-effecting engine install. */
|
|
62
|
+
export interface EngineInstallOutcome {
|
|
63
|
+
readonly installed: boolean;
|
|
64
|
+
/** Version the install produced, or null on failure. */
|
|
65
|
+
readonly version: string | null;
|
|
66
|
+
readonly detail: string;
|
|
67
|
+
}
|
|
68
|
+
/** Injected side-effecting install runner (npm i -g / --prefix / staged). */
|
|
69
|
+
export type EngineInstallRunner = (context: {
|
|
70
|
+
readonly rung: Extract<LadderRung, "install-global" | "install-sandbox" | "install-staged">;
|
|
71
|
+
readonly pinVersion: string | null;
|
|
72
|
+
readonly platform: string;
|
|
73
|
+
}) => EngineInstallOutcome;
|
|
74
|
+
/** Injected content re-projection (`update`) run after a fresh install. */
|
|
75
|
+
export type ReprojectRunner = (version: string | null) => void;
|
|
76
|
+
export interface ResolveEngineOptions {
|
|
77
|
+
readonly installRunner?: EngineInstallRunner;
|
|
78
|
+
readonly reproject?: ReprojectRunner;
|
|
79
|
+
}
|
|
80
|
+
export interface EngineResolution {
|
|
81
|
+
readonly decision: LadderDecision;
|
|
82
|
+
/** The install outcome when a rung required an install, else null. */
|
|
83
|
+
readonly installOutcome: EngineInstallOutcome | null;
|
|
84
|
+
/** The engine version resolved after any install, or null when unresolved. */
|
|
85
|
+
readonly resolvedVersion: string | null;
|
|
86
|
+
/** True when the ladder healed a mismatch (installed) with zero manual steps. */
|
|
87
|
+
readonly selfHealed: boolean;
|
|
88
|
+
/** Full structured trace including any install + re-projection. */
|
|
89
|
+
readonly trace: string;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve the engine by composing the pure ladder decision with an injected
|
|
93
|
+
* install runner. When a rung requires an install, the runner performs it and
|
|
94
|
+
* (on success) the re-projection runner forward-migrates content — yielding the
|
|
95
|
+
* "self-heals with zero manual npm/PATH steps" behavior with a structured trace.
|
|
96
|
+
*/
|
|
97
|
+
export declare function resolveEngine(facts: LadderFacts, options?: ResolveEngineOptions): EngineResolution;
|
|
98
|
+
//# sourceMappingURL=engine-ladder.d.ts.map
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global-first engine-resolution ladder for the resolution spine (#2264, from #2124).
|
|
3
|
+
*
|
|
4
|
+
* The defect this closes is `execution-env != install-env`: an agent can read
|
|
5
|
+
* every rule in `.deft/core/` yet execute no gate because the engine that is
|
|
6
|
+
* reachable in the environment it is ACTUALLY running in is absent or stale.
|
|
7
|
+
* The ladder detects that mismatch and self-heals only then:
|
|
8
|
+
*
|
|
9
|
+
* 1. global `deft` reachable AND >= pin -> use it
|
|
10
|
+
* 2. local `.deft/.cli/<platform>` reachable, intact, >= pin -> use it
|
|
11
|
+
* 3. must install:
|
|
12
|
+
* a. registry up + global prefix writable -> npm i -g @deftai/directive@<pin>
|
|
13
|
+
* b. registry up + global prefix NOT writable (sandbox) -> npm install --prefix .deft/.cli/<platform>
|
|
14
|
+
* c. registry down + staged tarball -> install from staged payload
|
|
15
|
+
* d. registry down + no tarball -> hard-fail ("stage this")
|
|
16
|
+
*
|
|
17
|
+
* `decideEngineLadder` is a PURE decision function (no I/O). The side-effecting
|
|
18
|
+
* install is factored behind an injected runner in `resolveEngine` so the whole
|
|
19
|
+
* self-heal path is unit-testable without touching the network.
|
|
20
|
+
*/
|
|
21
|
+
import { semverGte } from "./pin.js";
|
|
22
|
+
function fmt(version) {
|
|
23
|
+
return version ?? "absent";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Pure global-first ladder decision. Emits a structured trace describing each
|
|
27
|
+
* rung it evaluated and why it was skipped or chosen.
|
|
28
|
+
*/
|
|
29
|
+
export function decideEngineLadder(facts) {
|
|
30
|
+
const { pinVersion } = facts;
|
|
31
|
+
const steps = [];
|
|
32
|
+
// Rung 1: global engine.
|
|
33
|
+
if (facts.globalEngineVersion !== null && semverGte(facts.globalEngineVersion, pinVersion)) {
|
|
34
|
+
steps.push(`global: ${fmt(facts.globalEngineVersion)} >= pin ${fmt(pinVersion)} -> use`);
|
|
35
|
+
return {
|
|
36
|
+
rung: "global",
|
|
37
|
+
usable: true,
|
|
38
|
+
resolvedVersion: facts.globalEngineVersion,
|
|
39
|
+
trace: steps.join("; "),
|
|
40
|
+
reason: `global engine ${facts.globalEngineVersion} satisfies pin ${fmt(pinVersion)}`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (facts.globalEngineVersion === null) {
|
|
44
|
+
steps.push("global: absent");
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
steps.push(`global: ${facts.globalEngineVersion} < pin ${fmt(pinVersion)}`);
|
|
48
|
+
}
|
|
49
|
+
// Rung 2: local sandbox engine (must be intact AND >= pin).
|
|
50
|
+
const local = facts.localEngine;
|
|
51
|
+
if (local !== null) {
|
|
52
|
+
if (!local.integrity.usable) {
|
|
53
|
+
steps.push(local.integrity.partial
|
|
54
|
+
? `local: partial install -> not-usable`
|
|
55
|
+
: `local: ${local.integrity.reason}`);
|
|
56
|
+
}
|
|
57
|
+
else if (!semverGte(local.version, pinVersion)) {
|
|
58
|
+
steps.push(`local: ${fmt(local.version)} < pin ${fmt(pinVersion)}`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
steps.push(`local: ${fmt(local.version)} >= pin ${fmt(pinVersion)} -> use`);
|
|
62
|
+
return {
|
|
63
|
+
rung: "local",
|
|
64
|
+
usable: true,
|
|
65
|
+
resolvedVersion: local.version,
|
|
66
|
+
trace: steps.join("; "),
|
|
67
|
+
reason: `local engine ${local.version} satisfies pin ${fmt(pinVersion)}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
steps.push("local: absent");
|
|
73
|
+
}
|
|
74
|
+
// Rung 3: must install.
|
|
75
|
+
if (facts.registryUp && facts.globalPrefixWritable) {
|
|
76
|
+
steps.push(`install: npm i -g @deftai/directive@${fmt(pinVersion)}`);
|
|
77
|
+
return {
|
|
78
|
+
rung: "install-global",
|
|
79
|
+
usable: false,
|
|
80
|
+
resolvedVersion: null,
|
|
81
|
+
trace: steps.join("; "),
|
|
82
|
+
reason: "registry up and global prefix writable -> global install",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (facts.registryUp && !facts.globalPrefixWritable) {
|
|
86
|
+
steps.push(`install: npm install --prefix .deft/.cli/${facts.platform} @deftai/directive@${fmt(pinVersion)}`);
|
|
87
|
+
return {
|
|
88
|
+
rung: "install-sandbox",
|
|
89
|
+
usable: false,
|
|
90
|
+
resolvedVersion: null,
|
|
91
|
+
trace: steps.join("; "),
|
|
92
|
+
reason: "registry up but global prefix not writable (sandbox) -> --prefix install",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (!facts.registryUp && facts.stagedTarballAvailable) {
|
|
96
|
+
steps.push("install: staged tarball / vendored payload");
|
|
97
|
+
return {
|
|
98
|
+
rung: "install-staged",
|
|
99
|
+
usable: false,
|
|
100
|
+
resolvedVersion: null,
|
|
101
|
+
trace: steps.join("; "),
|
|
102
|
+
reason: "registry down but staged tarball available -> offline install",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
steps.push("hard-fail: registry down and no staged tarball");
|
|
106
|
+
return {
|
|
107
|
+
rung: "hard-fail",
|
|
108
|
+
usable: false,
|
|
109
|
+
resolvedVersion: null,
|
|
110
|
+
trace: steps.join("; "),
|
|
111
|
+
reason: "registry down and no staged tarball -- stage a payload to proceed",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const INSTALL_RUNGS = new Set([
|
|
115
|
+
"install-global",
|
|
116
|
+
"install-sandbox",
|
|
117
|
+
"install-staged",
|
|
118
|
+
]);
|
|
119
|
+
/**
|
|
120
|
+
* Resolve the engine by composing the pure ladder decision with an injected
|
|
121
|
+
* install runner. When a rung requires an install, the runner performs it and
|
|
122
|
+
* (on success) the re-projection runner forward-migrates content — yielding the
|
|
123
|
+
* "self-heals with zero manual npm/PATH steps" behavior with a structured trace.
|
|
124
|
+
*/
|
|
125
|
+
export function resolveEngine(facts, options = {}) {
|
|
126
|
+
const decision = decideEngineLadder(facts);
|
|
127
|
+
const steps = [decision.trace];
|
|
128
|
+
if (decision.usable) {
|
|
129
|
+
return {
|
|
130
|
+
decision,
|
|
131
|
+
installOutcome: null,
|
|
132
|
+
resolvedVersion: decision.resolvedVersion,
|
|
133
|
+
selfHealed: false,
|
|
134
|
+
trace: steps.join("; "),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (decision.rung === "hard-fail" || !INSTALL_RUNGS.has(decision.rung)) {
|
|
138
|
+
return {
|
|
139
|
+
decision,
|
|
140
|
+
installOutcome: null,
|
|
141
|
+
resolvedVersion: null,
|
|
142
|
+
selfHealed: false,
|
|
143
|
+
trace: steps.join("; "),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const runner = options.installRunner;
|
|
147
|
+
if (!runner) {
|
|
148
|
+
steps.push("install: deferred (no install runner supplied)");
|
|
149
|
+
return {
|
|
150
|
+
decision,
|
|
151
|
+
installOutcome: null,
|
|
152
|
+
resolvedVersion: null,
|
|
153
|
+
selfHealed: false,
|
|
154
|
+
trace: steps.join("; "),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const outcome = runner({
|
|
158
|
+
rung: decision.rung,
|
|
159
|
+
pinVersion: facts.pinVersion,
|
|
160
|
+
platform: facts.platform,
|
|
161
|
+
});
|
|
162
|
+
if (!outcome.installed) {
|
|
163
|
+
steps.push(`install failed: ${outcome.detail}`);
|
|
164
|
+
return {
|
|
165
|
+
decision,
|
|
166
|
+
installOutcome: outcome,
|
|
167
|
+
resolvedVersion: null,
|
|
168
|
+
selfHealed: false,
|
|
169
|
+
trace: steps.join("; "),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
steps.push(`installed ${decision.rung} -> ${fmt(outcome.version)}`);
|
|
173
|
+
if (options.reproject) {
|
|
174
|
+
options.reproject(outcome.version);
|
|
175
|
+
steps.push(`re-projected content ${fmt(outcome.version)}`);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
decision,
|
|
179
|
+
installOutcome: outcome,
|
|
180
|
+
resolvedVersion: outcome.version,
|
|
181
|
+
selfHealed: true,
|
|
182
|
+
trace: steps.join("; "),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=engine-ladder.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolution spine (keystone #2264 / epic #2203) — shared library primitives.
|
|
3
|
+
*
|
|
4
|
+
* The classifier (`classify`) produces the orthogonal fact-set; `plan()` applies
|
|
5
|
+
* one ordered precedence table and emits the versioned public resolution-plan-v1
|
|
6
|
+
* schema (the single source of truth). The engine ladder, local-engine integrity
|
|
7
|
+
* check, three-band skew policy, and pin/VERSION/sha reconciliation are the
|
|
8
|
+
* supporting primitives that init / update / doctor / headless (children B–E)
|
|
9
|
+
* consume — this module ships them; it does NOT rewire verb behavior.
|
|
10
|
+
*/
|
|
11
|
+
export { RESOLUTION_ENCODINGS, RESOLUTION_MODES, RESOLUTION_PLAN_SCHEMA_VERSION, type ResolutionEncoding, type ResolutionFacts, type ResolutionFile, type ResolutionMode, type ResolutionNextAction, type ResolutionPlan, } from "@deftai/directive-types";
|
|
12
|
+
export { type ClassifySeams, classify, defaultEngineProbe, type EngineProbeResult, } from "./classify.js";
|
|
13
|
+
export { decideEngineLadder, type EngineInstallOutcome, type EngineInstallRunner, type EngineResolution, type LadderDecision, type LadderFacts, type LadderRung, type LocalEngineFacts, type ReprojectRunner, type ResolveEngineOptions, resolveEngine, } from "./engine-ladder.js";
|
|
14
|
+
export { checkLocalEngineIntegrity, type IntegrityResult, type IntegritySeams, LOCAL_ENGINE_MARKERS, LOCAL_ENGINE_ROOT, localEnginePlatformDir, } from "./integrity.js";
|
|
15
|
+
export { DEFAULT_PACKAGE_MANAGER, type DetectPackageManagerInput, detectPackageManager, ENGINE_PACKAGE, PACKAGE_MANAGERS, type PackageManager, type PackageManagerCommands, renderEphemeral, renderGlobalInstall, renderPackageManagerCommands, renderProjectInstall, } from "./package-manager.js";
|
|
16
|
+
export { compareSemver, isExactPin, PIN_DEPENDENCY_NAME, type PinReadResult, type PinReadSeams, parseSemver, type ReconcileInputs, type ReconcileResult, readPin, reconcileVersions, type SemverTriple, semverGte, } from "./pin.js";
|
|
17
|
+
export { type PlanOptions, plan } from "./plan.js";
|
|
18
|
+
export { ACCEPT_ENGINE_SKEW_ENV, DEFAULT_ENGINE_SKEW_WINDOW, evaluateSkew, type SkewBand, type SkewDecision, type SkewOptions, type SkewResult, } from "./skew-policy.js";
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolution spine (keystone #2264 / epic #2203) — shared library primitives.
|
|
3
|
+
*
|
|
4
|
+
* The classifier (`classify`) produces the orthogonal fact-set; `plan()` applies
|
|
5
|
+
* one ordered precedence table and emits the versioned public resolution-plan-v1
|
|
6
|
+
* schema (the single source of truth). The engine ladder, local-engine integrity
|
|
7
|
+
* check, three-band skew policy, and pin/VERSION/sha reconciliation are the
|
|
8
|
+
* supporting primitives that init / update / doctor / headless (children B–E)
|
|
9
|
+
* consume — this module ships them; it does NOT rewire verb behavior.
|
|
10
|
+
*/
|
|
11
|
+
export { RESOLUTION_ENCODINGS, RESOLUTION_MODES, RESOLUTION_PLAN_SCHEMA_VERSION, } from "@deftai/directive-types";
|
|
12
|
+
export { classify, defaultEngineProbe, } from "./classify.js";
|
|
13
|
+
export { decideEngineLadder, resolveEngine, } from "./engine-ladder.js";
|
|
14
|
+
export { checkLocalEngineIntegrity, LOCAL_ENGINE_MARKERS, LOCAL_ENGINE_ROOT, localEnginePlatformDir, } from "./integrity.js";
|
|
15
|
+
export { DEFAULT_PACKAGE_MANAGER, detectPackageManager, ENGINE_PACKAGE, PACKAGE_MANAGERS, renderEphemeral, renderGlobalInstall, renderPackageManagerCommands, renderProjectInstall, } from "./package-manager.js";
|
|
16
|
+
export { compareSemver, isExactPin, PIN_DEPENDENCY_NAME, parseSemver, readPin, reconcileVersions, semverGte, } from "./pin.js";
|
|
17
|
+
export { plan } from "./plan.js";
|
|
18
|
+
export { ACCEPT_ENGINE_SKEW_ENV, DEFAULT_ENGINE_SKEW_WINDOW, evaluateSkew, } from "./skew-policy.js";
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-engine integrity check for the resolution spine (#2264, from #2124).
|
|
3
|
+
*
|
|
4
|
+
* A locally-installed engine at `.deft/.cli/<platform>` is only usable when the
|
|
5
|
+
* install completed. A present-but-partial directory (an interrupted
|
|
6
|
+
* `npm install --prefix`) must be treated as NOT-USABLE — otherwise the ladder
|
|
7
|
+
* would select a half-installed engine and fail at execution time instead of
|
|
8
|
+
* falling through to a clean re-install.
|
|
9
|
+
*/
|
|
10
|
+
/** Root of the sandbox-local engine install, relative to the project root. */
|
|
11
|
+
export declare const LOCAL_ENGINE_ROOT = ".deft/.cli";
|
|
12
|
+
/**
|
|
13
|
+
* Marker paths (relative to the platform install dir) that MUST all exist for a
|
|
14
|
+
* local engine to count as a completed, usable install. `package.json` marks the
|
|
15
|
+
* install root; the `directive` bin under `node_modules/.bin` marks that npm
|
|
16
|
+
* finished linking the executable.
|
|
17
|
+
*/
|
|
18
|
+
export declare const LOCAL_ENGINE_MARKERS: readonly string[];
|
|
19
|
+
export interface IntegritySeams {
|
|
20
|
+
readonly isFile?: (p: string) => boolean;
|
|
21
|
+
readonly isDir?: (p: string) => boolean;
|
|
22
|
+
readonly platform?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface IntegrityResult {
|
|
25
|
+
/** The install is complete and the engine may be selected. */
|
|
26
|
+
readonly usable: boolean;
|
|
27
|
+
/** The platform install directory exists at all (vs. wholly absent). */
|
|
28
|
+
readonly present: boolean;
|
|
29
|
+
/** Present but missing required markers (interrupted install). */
|
|
30
|
+
readonly partial: boolean;
|
|
31
|
+
/** Absolute-relative platform install directory that was probed. */
|
|
32
|
+
readonly platformDir: string;
|
|
33
|
+
/** Marker paths that were missing. */
|
|
34
|
+
readonly missingMarkers: readonly string[];
|
|
35
|
+
/** Human-facing reason. */
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
}
|
|
38
|
+
/** Resolve the platform-specific local engine directory (e.g. `.deft/.cli/linux`). */
|
|
39
|
+
export declare function localEnginePlatformDir(projectRoot: string, platform?: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Classify the local engine install at `.deft/.cli/<platform>`.
|
|
42
|
+
*
|
|
43
|
+
* - wholly absent -> `{ usable: false, present: false, partial: false }`
|
|
44
|
+
* - present, all markers -> `{ usable: true, present: true, partial: false }`
|
|
45
|
+
* - present, missing markers -> `{ usable: false, present: true, partial: true }`
|
|
46
|
+
*/
|
|
47
|
+
export declare function checkLocalEngineIntegrity(projectRoot: string, seams?: IntegritySeams): IntegrityResult;
|
|
48
|
+
//# sourceMappingURL=integrity.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-engine integrity check for the resolution spine (#2264, from #2124).
|
|
3
|
+
*
|
|
4
|
+
* A locally-installed engine at `.deft/.cli/<platform>` is only usable when the
|
|
5
|
+
* install completed. A present-but-partial directory (an interrupted
|
|
6
|
+
* `npm install --prefix`) must be treated as NOT-USABLE — otherwise the ladder
|
|
7
|
+
* would select a half-installed engine and fail at execution time instead of
|
|
8
|
+
* falling through to a clean re-install.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { platform as osPlatform } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
/** Root of the sandbox-local engine install, relative to the project root. */
|
|
14
|
+
export const LOCAL_ENGINE_ROOT = ".deft/.cli";
|
|
15
|
+
/**
|
|
16
|
+
* Marker paths (relative to the platform install dir) that MUST all exist for a
|
|
17
|
+
* local engine to count as a completed, usable install. `package.json` marks the
|
|
18
|
+
* install root; the `directive` bin under `node_modules/.bin` marks that npm
|
|
19
|
+
* finished linking the executable.
|
|
20
|
+
*/
|
|
21
|
+
export const LOCAL_ENGINE_MARKERS = [
|
|
22
|
+
"package.json",
|
|
23
|
+
join("node_modules", ".bin", "directive"),
|
|
24
|
+
];
|
|
25
|
+
function defaultIsFile(p) {
|
|
26
|
+
try {
|
|
27
|
+
return existsSync(p);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Resolve the platform-specific local engine directory (e.g. `.deft/.cli/linux`). */
|
|
34
|
+
export function localEnginePlatformDir(projectRoot, platform) {
|
|
35
|
+
const plat = platform ?? osPlatform();
|
|
36
|
+
return join(projectRoot, LOCAL_ENGINE_ROOT, plat);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Classify the local engine install at `.deft/.cli/<platform>`.
|
|
40
|
+
*
|
|
41
|
+
* - wholly absent -> `{ usable: false, present: false, partial: false }`
|
|
42
|
+
* - present, all markers -> `{ usable: true, present: true, partial: false }`
|
|
43
|
+
* - present, missing markers -> `{ usable: false, present: true, partial: true }`
|
|
44
|
+
*/
|
|
45
|
+
export function checkLocalEngineIntegrity(projectRoot, seams = {}) {
|
|
46
|
+
const isFile = seams.isFile ?? defaultIsFile;
|
|
47
|
+
const isDir = seams.isDir ?? seams.isFile ?? defaultIsFile;
|
|
48
|
+
const platformDir = localEnginePlatformDir(projectRoot, seams.platform);
|
|
49
|
+
const rootPresent = isDir(platformDir);
|
|
50
|
+
const missingMarkers = LOCAL_ENGINE_MARKERS.filter((marker) => !isFile(join(platformDir, marker)));
|
|
51
|
+
if (!rootPresent && missingMarkers.length === LOCAL_ENGINE_MARKERS.length) {
|
|
52
|
+
return {
|
|
53
|
+
usable: false,
|
|
54
|
+
present: false,
|
|
55
|
+
partial: false,
|
|
56
|
+
platformDir,
|
|
57
|
+
missingMarkers,
|
|
58
|
+
reason: `no local engine at ${platformDir}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (missingMarkers.length > 0) {
|
|
62
|
+
return {
|
|
63
|
+
usable: false,
|
|
64
|
+
present: true,
|
|
65
|
+
partial: true,
|
|
66
|
+
platformDir,
|
|
67
|
+
missingMarkers,
|
|
68
|
+
reason: `partial local engine at ${platformDir} (missing: ${missingMarkers.join(", ")}) -- treated as not-usable`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
usable: true,
|
|
73
|
+
present: true,
|
|
74
|
+
partial: false,
|
|
75
|
+
platformDir,
|
|
76
|
+
missingMarkers: [],
|
|
77
|
+
reason: `intact local engine at ${platformDir}`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=integrity.js.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-manager abstraction for the resolution spine (#2197, from #2264).
|
|
3
|
+
*
|
|
4
|
+
* The documented install/upgrade path historically hard-coded npm
|
|
5
|
+
* (`npm i -g @deftai/directive`). pnpm-managed setups have no first-class
|
|
6
|
+
* flow, and mixing an npm global into a pnpm environment breaks PATH/shim/store
|
|
7
|
+
* consistency. This module makes the *command rendering* package-manager aware
|
|
8
|
+
* so the resolution plan and doctor emit the right form for the active manager.
|
|
9
|
+
*
|
|
10
|
+
* Two load-bearing facts keep this small (locked on issue #2197):
|
|
11
|
+
* 1. NO additional registry. pnpm resolves from the same npm registry
|
|
12
|
+
* (`registry.npmjs.org`) by default; the published `@deftai/directive`
|
|
13
|
+
* tarball is unchanged. No renderer here ever emits a `--registry` flag.
|
|
14
|
+
* 2. The internal `.deft/.cli/<platform>` sandbox vendoring STAYS on npm
|
|
15
|
+
* (its `node_modules/.bin` layout is validated by `integrity.ts` and is
|
|
16
|
+
* gitignored / package-manager-invisible). Only the global, project-local,
|
|
17
|
+
* ephemeral, and upgrade command forms vary by package manager.
|
|
18
|
+
*
|
|
19
|
+
* Every function here is PURE (no I/O): detection consumes an injected fact-set
|
|
20
|
+
* so the whole surface is unit-testable without touching the filesystem or env.
|
|
21
|
+
*/
|
|
22
|
+
export type PackageManager = "npm" | "pnpm";
|
|
23
|
+
export declare const PACKAGE_MANAGERS: readonly PackageManager[];
|
|
24
|
+
export declare const DEFAULT_PACKAGE_MANAGER: PackageManager;
|
|
25
|
+
/** Canonical published engine package name. */
|
|
26
|
+
export declare const ENGINE_PACKAGE = "@deftai/directive";
|
|
27
|
+
export interface DetectPackageManagerInput {
|
|
28
|
+
/** Environment map (reads `DEFT_PACKAGE_MANAGER` and `npm_config_user_agent`). */
|
|
29
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
30
|
+
/** The `packageManager` field from the project package.json (Corepack), if any. */
|
|
31
|
+
readonly packageManagerField?: string | null;
|
|
32
|
+
/** Whether a `pnpm-lock.yaml` is present at the project root. */
|
|
33
|
+
readonly pnpmLockPresent?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Detect the active package manager. Precedence (first match wins):
|
|
37
|
+
* 1. `DEFT_PACKAGE_MANAGER` env override
|
|
38
|
+
* 2. `packageManager` field / Corepack shim
|
|
39
|
+
* 3. `pnpm-lock.yaml` present
|
|
40
|
+
* 4. `npm_config_user_agent` (set by the manager that spawned the process)
|
|
41
|
+
* 5. default: npm
|
|
42
|
+
*/
|
|
43
|
+
export declare function detectPackageManager(input?: DetectPackageManagerInput): PackageManager;
|
|
44
|
+
/**
|
|
45
|
+
* Render a global install command, e.g. `npm i -g @deftai/directive@0.65.0`
|
|
46
|
+
* (npm) or `pnpm add -g @deftai/directive@0.65.0` (pnpm). `spec` is the full
|
|
47
|
+
* package spec including any `@version` suffix.
|
|
48
|
+
*/
|
|
49
|
+
export declare function renderGlobalInstall(pm: PackageManager, spec?: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Render a project-local (dev-dependency) install command. pnpm-managed repos
|
|
52
|
+
* that prefer not to install globally use this.
|
|
53
|
+
*/
|
|
54
|
+
export declare function renderProjectInstall(pm: PackageManager, spec?: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* Render an ephemeral (no-install) invocation, e.g. `npx @deftai/directive update`
|
|
57
|
+
* (npm) or `pnpm dlx @deftai/directive update` (pnpm).
|
|
58
|
+
*/
|
|
59
|
+
export declare function renderEphemeral(pm: PackageManager, subcommand: string, pkg?: string): string;
|
|
60
|
+
export interface PackageManagerCommands {
|
|
61
|
+
readonly packageManager: PackageManager;
|
|
62
|
+
/** Global install of the engine at the given spec. */
|
|
63
|
+
readonly globalInstall: string;
|
|
64
|
+
/** Project-local (dev-dependency) install of the engine. */
|
|
65
|
+
readonly projectInstall: string;
|
|
66
|
+
/** Ephemeral `update` invocation. */
|
|
67
|
+
readonly ephemeralUpdate: string;
|
|
68
|
+
/** The canonical upgrade one-liner (global install of `@latest`). */
|
|
69
|
+
readonly upgradeOneLiner: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Render the full command matrix for a package manager at a given spec
|
|
73
|
+
* (defaults to `@latest`). Single-sourced so docs, doctor, and the plan all
|
|
74
|
+
* derive from the same renderers.
|
|
75
|
+
*/
|
|
76
|
+
export declare function renderPackageManagerCommands(pm: PackageManager, spec?: string): PackageManagerCommands;
|
|
77
|
+
//# sourceMappingURL=package-manager.d.ts.map
|