@deftai/directive-core 0.99.0 → 0.101.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/authz/actions.d.ts +6 -0
- package/dist/authz/actions.js +3 -0
- package/dist/authz/classify.js +241 -30
- package/dist/authz/decompose-apply.d.ts +81 -0
- package/dist/authz/decompose-apply.js +377 -0
- package/dist/authz/index.d.ts +3 -1
- package/dist/authz/index.js +3 -1
- package/dist/authz/store.d.ts +53 -0
- package/dist/authz/store.js +219 -1
- package/dist/authz/types.d.ts +19 -2
- package/dist/authz/types.js +5 -1
- package/dist/check/cached-orchestrator.d.ts +26 -5
- package/dist/check/cached-orchestrator.js +228 -6
- package/dist/check/gate-lists.d.ts +10 -4
- package/dist/check/gate-lists.js +19 -5
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +2 -1
- package/dist/check/named-cause.d.ts +45 -0
- package/dist/check/named-cause.js +121 -0
- package/dist/check/orchestrator.js +6 -1
- package/dist/doctor/checks.d.ts +44 -0
- package/dist/doctor/checks.js +428 -2
- package/dist/doctor/help.d.ts +5 -0
- package/dist/doctor/help.js +31 -0
- package/dist/doctor/index.d.ts +2 -0
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/main.d.ts +6 -7
- package/dist/doctor/main.js +130 -79
- package/dist/doctor/session-coda.d.ts +82 -0
- package/dist/doctor/session-coda.js +151 -0
- package/dist/doctor/types.d.ts +16 -0
- package/dist/doctor/which.d.ts +20 -1
- package/dist/doctor/which.js +67 -1
- package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
- package/dist/finish-loop/pr-finish-loop.js +116 -5
- package/dist/finish-loop/types.d.ts +1 -1
- package/dist/hooks/dispatcher.d.ts +9 -5
- package/dist/hooks/dispatcher.js +23 -15
- package/dist/hooks/readonly.d.ts +6 -3
- package/dist/hooks/readonly.js +44 -25
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.d.ts +20 -0
- package/dist/init-deposit/agent-hooks.js +51 -31
- package/dist/init-deposit/gitignore.js +2 -0
- package/dist/intake/issue-ingest.js +36 -0
- package/dist/lifecycle/completed-consistency.d.ts +60 -0
- package/dist/lifecycle/completed-consistency.js +443 -0
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +52 -0
- package/dist/literal-acceptance/capture.js +658 -0
- package/dist/literal-acceptance/evaluate.d.ts +47 -0
- package/dist/literal-acceptance/evaluate.js +193 -0
- package/dist/literal-acceptance/index.d.ts +12 -0
- package/dist/literal-acceptance/index.js +12 -0
- package/dist/literal-acceptance/run.d.ts +38 -0
- package/dist/literal-acceptance/run.js +216 -0
- package/dist/literal-acceptance/safety.d.ts +24 -0
- package/dist/literal-acceptance/safety.js +251 -0
- package/dist/literal-acceptance/types.d.ts +82 -0
- package/dist/literal-acceptance/types.js +26 -0
- package/dist/policy/ac-pass-banking.d.ts +58 -0
- package/dist/policy/ac-pass-banking.js +150 -0
- package/dist/policy/ceremony-dial.d.ts +96 -11
- package/dist/policy/ceremony-dial.js +250 -23
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +16 -1
- package/dist/policy/merge-approval-head.d.ts +129 -0
- package/dist/policy/merge-approval-head.js +461 -0
- package/dist/policy/require-human-merge.d.ts +5 -0
- package/dist/policy/require-human-merge.js +5 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
- package/dist/pr-merge-readiness/ci-gate.js +65 -5
- package/dist/pr-merge-readiness/compute.d.ts +13 -0
- package/dist/pr-merge-readiness/compute.js +99 -14
- package/dist/pr-merge-readiness/gh.d.ts +49 -0
- package/dist/pr-merge-readiness/gh.js +268 -0
- package/dist/pr-merge-readiness/index.d.ts +2 -2
- package/dist/pr-merge-readiness/index.js +2 -2
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
- package/dist/pr-wait-mergeable/cascade.js +115 -1
- package/dist/pr-wait-mergeable/main.d.ts +4 -0
- package/dist/pr-wait-mergeable/main.js +4 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
- package/dist/pr-wait-mergeable/wrappers.js +7 -1
- package/dist/preflight/evaluate.d.ts +15 -1
- package/dist/preflight/evaluate.js +33 -2
- package/dist/preflight-cache/evaluate.d.ts +3 -0
- package/dist/preflight-cache/evaluate.js +3 -0
- package/dist/product-first-done-gate/acceptance.d.ts +25 -0
- package/dist/product-first-done-gate/acceptance.js +247 -0
- package/dist/product-first-done-gate/check-mode.d.ts +45 -0
- package/dist/product-first-done-gate/check-mode.js +148 -0
- package/dist/product-first-done-gate/evaluate.d.ts +50 -0
- package/dist/product-first-done-gate/evaluate.js +255 -0
- package/dist/product-first-done-gate/index.d.ts +11 -0
- package/dist/product-first-done-gate/index.js +11 -0
- package/dist/product-first-done-gate/types.d.ts +64 -0
- package/dist/product-first-done-gate/types.js +61 -0
- package/dist/render/framework-commands.js +4 -0
- package/dist/run-summary/emit.d.ts +56 -0
- package/dist/run-summary/emit.js +171 -0
- package/dist/run-summary/index.d.ts +4 -0
- package/dist/run-summary/index.js +4 -0
- package/dist/run-summary/path.d.ts +25 -0
- package/dist/run-summary/path.js +78 -0
- package/dist/run-summary/types.d.ts +76 -0
- package/dist/run-summary/types.js +21 -0
- package/dist/scope/acceptance-evidence.d.ts +110 -0
- package/dist/scope/acceptance-evidence.js +422 -0
- package/dist/scope/coverage-map.d.ts +98 -0
- package/dist/scope/coverage-map.js +558 -0
- package/dist/scope/decompose.d.ts +5 -0
- package/dist/scope/decompose.js +134 -11
- package/dist/scope/index.d.ts +3 -0
- package/dist/scope/index.js +3 -0
- package/dist/scope/parent-lineage.d.ts +125 -0
- package/dist/scope/parent-lineage.js +626 -0
- package/dist/scope/transition.d.ts +8 -0
- package/dist/scope/transition.js +61 -2
- package/dist/session/ac-pass-banking.d.ts +221 -0
- package/dist/session/ac-pass-banking.js +761 -0
- package/dist/session/active-cli.d.ts +79 -0
- package/dist/session/active-cli.js +382 -0
- package/dist/session/deposit-sha.d.ts +49 -0
- package/dist/session/deposit-sha.js +121 -0
- package/dist/session/effort-budget.d.ts +136 -0
- package/dist/session/effort-budget.js +352 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +7 -0
- package/dist/session/orientation-compression.d.ts +127 -0
- package/dist/session/orientation-compression.js +425 -0
- package/dist/session/orientation-state.d.ts +29 -0
- package/dist/session/orientation-state.js +85 -0
- package/dist/session/session-start.d.ts +31 -0
- package/dist/session/session-start.js +228 -2
- package/dist/session/toolchain-preflight.d.ts +64 -0
- package/dist/session/toolchain-preflight.js +151 -0
- package/dist/session/verify-session-ritual.d.ts +14 -0
- package/dist/session/verify-session-ritual.js +33 -0
- package/dist/story-ready/evaluate.d.ts +11 -0
- package/dist/story-ready/evaluate.js +41 -3
- package/dist/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +8 -2
- package/dist/triage/bootstrap/index.js +15 -3
- package/dist/triage/classify/index.d.ts +1 -0
- package/dist/triage/classify/index.js +2 -0
- package/dist/triage/classify/label-mirror.js +16 -0
- package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
- package/dist/triage/classify/mirror-discovery-tip.js +234 -0
- package/dist/triage/reconcile/reconcile.js +28 -6
- package/dist/triage/summary/index.js +12 -4
- package/dist/triage/welcome/default-mode.js +16 -0
- package/dist/ts-check-lane/run-lane.js +21 -4
- package/dist/vbrief-validate/conformance.js +7 -0
- package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- package/package.json +15 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active CLI path / engine-version check after global upgrade (#3233).
|
|
3
|
+
*
|
|
4
|
+
* A global `npm i -g` can land under one npm prefix while a higher-precedence
|
|
5
|
+
* PATH entry still runs an older `deft` / `directive`. The upgrade looks
|
|
6
|
+
* complete, but bare CLI invocations keep the stale engine.
|
|
7
|
+
*
|
|
8
|
+
* Shell-active binaries may be probed with `--version`. Lower-precedence PATH
|
|
9
|
+
* candidates are inspected via package.json only (never executed) so gated
|
|
10
|
+
* ritual cannot run unselected PATH entries (#3233 Greptile P1 security).
|
|
11
|
+
*/
|
|
12
|
+
/** Bare CLI names operators invoke after upgrade. */
|
|
13
|
+
export declare const ACTIVE_CLI_COMMANDS: readonly ["deft", "directive"];
|
|
14
|
+
export type ActiveCliCommand = (typeof ACTIVE_CLI_COMMANDS)[number];
|
|
15
|
+
export interface CliCandidate {
|
|
16
|
+
readonly command: ActiveCliCommand;
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly version: string | null;
|
|
19
|
+
/** Zero-based PATH precedence within this command (0 = shell-active). */
|
|
20
|
+
readonly precedence: number;
|
|
21
|
+
/** How the version was obtained (for diagnostics / tests). */
|
|
22
|
+
readonly versionSource: "exec" | "package-json" | "none";
|
|
23
|
+
}
|
|
24
|
+
export interface ActiveCliCheckSeams {
|
|
25
|
+
readonly whichAll?: (name: string) => string[];
|
|
26
|
+
/**
|
|
27
|
+
* Optional override for version probing. When omitted, active binaries may
|
|
28
|
+
* spawn `--version`; shadowed binaries use package.json only.
|
|
29
|
+
*/
|
|
30
|
+
readonly probeVersion?: (executablePath: string, precedence: number) => string | null;
|
|
31
|
+
readonly commands?: readonly ActiveCliCommand[];
|
|
32
|
+
/** Override default upgrade-target resolution (tests). */
|
|
33
|
+
readonly resolveDefaultTarget?: (candidates: readonly CliCandidate[]) => string | null;
|
|
34
|
+
/** Inject in-process engine version for default-target resolution. */
|
|
35
|
+
readonly inProcessVersion?: string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface ActiveCliCheckResult {
|
|
38
|
+
/** True when no shadowing / target mismatch was detected. */
|
|
39
|
+
readonly ok: boolean;
|
|
40
|
+
/** 0 = ok, 1 = shadow/mismatch (fail closed), 2 = probe config error (unused; reserved). */
|
|
41
|
+
readonly code: number;
|
|
42
|
+
readonly active: CliCandidate | null;
|
|
43
|
+
readonly candidates: readonly CliCandidate[];
|
|
44
|
+
readonly targetVersion: string | null;
|
|
45
|
+
/** Single-line summary for ritual / exit message. */
|
|
46
|
+
readonly message: string;
|
|
47
|
+
/** Multi-line stderr diagnostic (paths, versions, remediation). */
|
|
48
|
+
readonly lines: readonly string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve engine version by walking the install tree for package.json (no exec).
|
|
52
|
+
* Follows symlinks and win32 `.cmd` shims that embed a node path.
|
|
53
|
+
*/
|
|
54
|
+
export declare function readEngineVersionFromInstallTree(executablePath: string): string | null;
|
|
55
|
+
/** Probe shell-active CLI only via `--version` (never used for shadowed PATH entries). */
|
|
56
|
+
export declare function probeCliEngineVersion(executablePath: string): string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Version probe used by the default collector:
|
|
59
|
+
* - precedence 0 (shell-active): package.json first, then `--version` spawn
|
|
60
|
+
* - precedence > 0: package.json only (never execute shadowed PATH entries)
|
|
61
|
+
*/
|
|
62
|
+
export declare function probeCandidateVersion(executablePath: string, precedence: number): string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Default upgrade target when callers omit `targetEngineVersion` (#3233 P1):
|
|
65
|
+
* the max of (1) publishable in-process engine version and (2) versions from
|
|
66
|
+
* **non-active** PATH candidates only. The shell-active CLI must never become
|
|
67
|
+
* its own expected target (lone stale self-match).
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveDefaultActiveCliTarget(candidates: readonly CliCandidate[], inProcessVersion?: string | null): string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Fail-closed check: shell-active `deft`/`directive` must match `targetVersion`
|
|
72
|
+
* (or the resolved default target), and must not be older than another PATH
|
|
73
|
+
* candidate (#3233).
|
|
74
|
+
*
|
|
75
|
+
* When no CLI is on PATH, returns ok=true (absence is covered by verify:tools /
|
|
76
|
+
* engine ladder — this gate only catches shadowing after a multi-prefix install).
|
|
77
|
+
*/
|
|
78
|
+
export declare function checkActiveCliAgainstTarget(targetVersion?: string | null, seams?: ActiveCliCheckSeams): ActiveCliCheckResult;
|
|
79
|
+
//# sourceMappingURL=active-cli.d.ts.map
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active CLI path / engine-version check after global upgrade (#3233).
|
|
3
|
+
*
|
|
4
|
+
* A global `npm i -g` can land under one npm prefix while a higher-precedence
|
|
5
|
+
* PATH entry still runs an older `deft` / `directive`. The upgrade looks
|
|
6
|
+
* complete, but bare CLI invocations keep the stale engine.
|
|
7
|
+
*
|
|
8
|
+
* Shell-active binaries may be probed with `--version`. Lower-precedence PATH
|
|
9
|
+
* candidates are inspected via package.json only (never executed) so gated
|
|
10
|
+
* ritual cannot run unselected PATH entries (#3233 Greptile P1 security).
|
|
11
|
+
*/
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync } from "node:fs";
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { CANONICAL_UPGRADE_COMMAND } from "../doctor/constants.js";
|
|
16
|
+
import { defaultWhichAll } from "../doctor/which.js";
|
|
17
|
+
import { readCorePackageVersion } from "../engine-version.js";
|
|
18
|
+
import { compareSemver } from "../resolution/pin.js";
|
|
19
|
+
import { quoteWin32CommandForShell, shouldUseShellForCommand, } from "../verify-env/command-spawn.js";
|
|
20
|
+
/** Bare CLI names operators invoke after upgrade. */
|
|
21
|
+
export const ACTIVE_CLI_COMMANDS = ["deft", "directive"];
|
|
22
|
+
/** Bounded semver extract — avoids ReDoS on long zero-runs (CodeQL). */
|
|
23
|
+
const SEMVER_IN_TEXT_RE = /\b(\d{1,6}\.\d{1,6}\.\d{1,6})\b/;
|
|
24
|
+
const ENGINE_PKG_NAMES = new Set(["@deftai/directive", "@deftai/directive-core"]);
|
|
25
|
+
function parseVersionFromOutput(out) {
|
|
26
|
+
// Cap scan length so CodeQL polynomial-regex alerts cannot fire on huge text.
|
|
27
|
+
const sample = out.length > 512 ? out.slice(0, 512) : out;
|
|
28
|
+
const match = SEMVER_IN_TEXT_RE.exec(sample);
|
|
29
|
+
return match?.[1] ?? null;
|
|
30
|
+
}
|
|
31
|
+
function isPublishableSemver(version) {
|
|
32
|
+
if (typeof version !== "string" || version.length === 0)
|
|
33
|
+
return false;
|
|
34
|
+
if (version === "0.0.0" || version.includes("0.0.0-dev") || version.includes("dev")) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return SEMVER_IN_TEXT_RE.test(version);
|
|
38
|
+
}
|
|
39
|
+
function tryReadPackageVersion(pkgPath) {
|
|
40
|
+
try {
|
|
41
|
+
const raw = readFileSync(pkgPath, "utf8");
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
if (parsed === null || typeof parsed !== "object")
|
|
44
|
+
return null;
|
|
45
|
+
const rec = parsed;
|
|
46
|
+
const name = typeof rec.name === "string" ? rec.name : "";
|
|
47
|
+
if (!ENGINE_PKG_NAMES.has(name))
|
|
48
|
+
return null;
|
|
49
|
+
// Prefer directive-core dep when the CLI package version is a workspace/dev 0.0.0.
|
|
50
|
+
if (name === "@deftai/directive" && rec.dependencies && typeof rec.dependencies === "object") {
|
|
51
|
+
const core = rec.dependencies["@deftai/directive-core"];
|
|
52
|
+
if (typeof core === "string") {
|
|
53
|
+
const coreVer = parseVersionFromOutput(core);
|
|
54
|
+
if (coreVer !== null && isPublishableSemver(coreVer))
|
|
55
|
+
return coreVer;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return typeof rec.version === "string" && rec.version.length > 0 ? rec.version : null;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve engine version by walking the install tree for package.json (no exec).
|
|
66
|
+
* Follows symlinks and win32 `.cmd` shims that embed a node path.
|
|
67
|
+
*/
|
|
68
|
+
export function readEngineVersionFromInstallTree(executablePath) {
|
|
69
|
+
const seeds = [executablePath];
|
|
70
|
+
try {
|
|
71
|
+
if (existsSync(executablePath)) {
|
|
72
|
+
const st = lstatSync(executablePath);
|
|
73
|
+
if (st.isSymbolicLink()) {
|
|
74
|
+
try {
|
|
75
|
+
const link = readlinkSync(executablePath);
|
|
76
|
+
seeds.push(resolve(dirname(executablePath), link));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
/* ignore */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
seeds.push(realpathSync(executablePath));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
/* ignore */
|
|
87
|
+
}
|
|
88
|
+
// Win32 npm shims often contain a path to the real package bin.
|
|
89
|
+
if (/\.(cmd|bat)$/i.test(executablePath)) {
|
|
90
|
+
try {
|
|
91
|
+
const text = readFileSync(executablePath, "utf8");
|
|
92
|
+
const m = /%~dp0[\\/](\.\.[\\/].*?node_modules[\\/]@deftai[\\/]directive[\\/][^\s"']+)/i.exec(text) ??
|
|
93
|
+
/([A-Za-z]:\\[^\r\n"']*node_modules[\\/]@deftai[\\/]directive[\\/][^\r\n"']+)/i.exec(text) ??
|
|
94
|
+
/([^\s"']*node_modules[\\/]@deftai[\\/]directive[\\/][^\s"']+)/i.exec(text);
|
|
95
|
+
if (m?.[1]) {
|
|
96
|
+
seeds.push(resolve(dirname(executablePath), m[1].replace(/\//g, "\\")));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* ignore */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
/* ignore */
|
|
107
|
+
}
|
|
108
|
+
for (const seed of seeds) {
|
|
109
|
+
let dir = dirname(seed);
|
|
110
|
+
for (let i = 0; i < 12; i += 1) {
|
|
111
|
+
const pkgPath = join(dir, "package.json");
|
|
112
|
+
const ver = tryReadPackageVersion(pkgPath);
|
|
113
|
+
if (ver !== null) {
|
|
114
|
+
const parsed = parseVersionFromOutput(ver) ?? ver;
|
|
115
|
+
if (isPublishableSemver(parsed) || SEMVER_IN_TEXT_RE.test(parsed)) {
|
|
116
|
+
return parseVersionFromOutput(parsed) ?? parsed;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// npm global: .../node_modules/@deftai/directive
|
|
120
|
+
const nested = join(dir, "node_modules", "@deftai", "directive", "package.json");
|
|
121
|
+
const nestedVer = tryReadPackageVersion(nested);
|
|
122
|
+
if (nestedVer !== null) {
|
|
123
|
+
return parseVersionFromOutput(nestedVer) ?? nestedVer;
|
|
124
|
+
}
|
|
125
|
+
const nestedCore = join(dir, "node_modules", "@deftai", "directive-core", "package.json");
|
|
126
|
+
const nestedCoreVer = tryReadPackageVersion(nestedCore);
|
|
127
|
+
if (nestedCoreVer !== null) {
|
|
128
|
+
return parseVersionFromOutput(nestedCoreVer) ?? nestedCoreVer;
|
|
129
|
+
}
|
|
130
|
+
const parent = dirname(dir);
|
|
131
|
+
if (parent === dir)
|
|
132
|
+
break;
|
|
133
|
+
dir = parent;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
/** Probe shell-active CLI only via `--version` (never used for shadowed PATH entries). */
|
|
139
|
+
export function probeCliEngineVersion(executablePath) {
|
|
140
|
+
const fromTree = readEngineVersionFromInstallTree(executablePath);
|
|
141
|
+
if (fromTree !== null)
|
|
142
|
+
return fromTree;
|
|
143
|
+
const shell = shouldUseShellForCommand(executablePath);
|
|
144
|
+
const cmd = shell && process.platform === "win32"
|
|
145
|
+
? quoteWin32CommandForShell(executablePath)
|
|
146
|
+
: executablePath;
|
|
147
|
+
try {
|
|
148
|
+
const result = spawnSync(cmd, ["--version"], {
|
|
149
|
+
encoding: "utf8",
|
|
150
|
+
timeout: 5_000,
|
|
151
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
152
|
+
shell,
|
|
153
|
+
windowsHide: true,
|
|
154
|
+
});
|
|
155
|
+
if (result.status !== 0) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const stdout = typeof result.stdout === "string" ? result.stdout : "";
|
|
159
|
+
return parseVersionFromOutput(stdout);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Version probe used by the default collector:
|
|
167
|
+
* - precedence 0 (shell-active): package.json first, then `--version` spawn
|
|
168
|
+
* - precedence > 0: package.json only (never execute shadowed PATH entries)
|
|
169
|
+
*/
|
|
170
|
+
export function probeCandidateVersion(executablePath, precedence) {
|
|
171
|
+
if (precedence > 0) {
|
|
172
|
+
return readEngineVersionFromInstallTree(executablePath);
|
|
173
|
+
}
|
|
174
|
+
return probeCliEngineVersion(executablePath);
|
|
175
|
+
}
|
|
176
|
+
function collectCandidates(seams) {
|
|
177
|
+
const whichAll = seams.whichAll ?? ((name) => defaultWhichAll(name));
|
|
178
|
+
const probe = seams.probeVersion ?? probeCandidateVersion;
|
|
179
|
+
const commands = seams.commands ?? ACTIVE_CLI_COMMANDS;
|
|
180
|
+
const out = [];
|
|
181
|
+
for (const command of commands) {
|
|
182
|
+
const paths = whichAll(command);
|
|
183
|
+
paths.forEach((path, precedence) => {
|
|
184
|
+
const version = probe(path, precedence);
|
|
185
|
+
out.push({
|
|
186
|
+
command,
|
|
187
|
+
path,
|
|
188
|
+
version,
|
|
189
|
+
precedence,
|
|
190
|
+
versionSource: version === null ? "none" : precedence > 0 ? "package-json" : "exec",
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
function formatCandidateLine(c) {
|
|
197
|
+
const ver = (c.version ?? "unknown").replace(/\r?\n/g, " ");
|
|
198
|
+
const path = c.path.replace(/\r?\n/g, " ");
|
|
199
|
+
const command = String(c.command).replace(/\r?\n/g, " ");
|
|
200
|
+
const mark = c.precedence === 0 ? " (active)" : "";
|
|
201
|
+
return ` - ${path} → engine ${ver}${mark} [${command}]`;
|
|
202
|
+
}
|
|
203
|
+
function remediationLines(params) {
|
|
204
|
+
const lines = [
|
|
205
|
+
"[deft session] Remediation (higher-precedence CLI shadows the upgraded install):",
|
|
206
|
+
" 1. Align every global prefix to the same engine version, e.g.:",
|
|
207
|
+
` ${CANONICAL_UPGRADE_COMMAND}`,
|
|
208
|
+
" (or `pnpm add -g @deftai/directive@latest` when pnpm owns the active prefix)",
|
|
209
|
+
];
|
|
210
|
+
if (params.active !== null) {
|
|
211
|
+
lines.push(` 2. Confirm the shell-active binary: re-open the shell, then`, ` \`${params.active.command} --version\` must report` +
|
|
212
|
+
(params.targetVersion !== null
|
|
213
|
+
? ` engine @${params.targetVersion}.`
|
|
214
|
+
: " the upgraded engine version."));
|
|
215
|
+
lines.push(` 3. If PATH still prefers a stale prefix, put the upgraded npm/pnpm bin dir`, ` ahead of ${params.active.path}, or uninstall/upgrade that shadowing install.`);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
lines.push(" 2. Ensure the upgraded npm/pnpm global bin directory is on PATH, then re-run", " `deft --version` / `directive --version`.");
|
|
219
|
+
}
|
|
220
|
+
if (params.newerCandidates.length > 0) {
|
|
221
|
+
lines.push(" Competing installs (not shell-active but newer or target-matching):");
|
|
222
|
+
for (const c of params.newerCandidates) {
|
|
223
|
+
lines.push(formatCandidateLine(c));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return lines;
|
|
227
|
+
}
|
|
228
|
+
function maxPublishableVersion(versions) {
|
|
229
|
+
let best = null;
|
|
230
|
+
for (const v of versions) {
|
|
231
|
+
if (!isPublishableSemver(v))
|
|
232
|
+
continue;
|
|
233
|
+
if (best === null || compareSemver(v, best) === 1) {
|
|
234
|
+
best = v;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return best;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Default upgrade target when callers omit `targetEngineVersion` (#3233 P1):
|
|
241
|
+
* the max of (1) publishable in-process engine version and (2) versions from
|
|
242
|
+
* **non-active** PATH candidates only. The shell-active CLI must never become
|
|
243
|
+
* its own expected target (lone stale self-match).
|
|
244
|
+
*/
|
|
245
|
+
export function resolveDefaultActiveCliTarget(candidates, inProcessVersion) {
|
|
246
|
+
let inProcess = inProcessVersion;
|
|
247
|
+
if (inProcess === undefined) {
|
|
248
|
+
try {
|
|
249
|
+
inProcess = readCorePackageVersion();
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
inProcess = null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Independent sources only — exclude precedence-0 (active) versions.
|
|
256
|
+
const fromShadows = candidates.filter((c) => c.precedence > 0).map((c) => c.version);
|
|
257
|
+
return maxPublishableVersion([isPublishableSemver(inProcess) ? inProcess : null, ...fromShadows]);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Fail-closed check: shell-active `deft`/`directive` must match `targetVersion`
|
|
261
|
+
* (or the resolved default target), and must not be older than another PATH
|
|
262
|
+
* candidate (#3233).
|
|
263
|
+
*
|
|
264
|
+
* When no CLI is on PATH, returns ok=true (absence is covered by verify:tools /
|
|
265
|
+
* engine ladder — this gate only catches shadowing after a multi-prefix install).
|
|
266
|
+
*/
|
|
267
|
+
export function checkActiveCliAgainstTarget(targetVersion = null, seams = {}) {
|
|
268
|
+
const candidates = collectCandidates(seams);
|
|
269
|
+
if (candidates.length === 0) {
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
code: 0,
|
|
273
|
+
active: null,
|
|
274
|
+
candidates,
|
|
275
|
+
targetVersion,
|
|
276
|
+
message: "active CLI check skipped (no deft/directive on PATH)",
|
|
277
|
+
lines: [],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const resolveTarget = seams.resolveDefaultTarget ??
|
|
281
|
+
((cs) => resolveDefaultActiveCliTarget(cs, seams.inProcessVersion));
|
|
282
|
+
const effectiveTarget = targetVersion !== null && targetVersion.length > 0 ? targetVersion : resolveTarget(candidates);
|
|
283
|
+
// Group by command; each bare name has its own PATH-active binary.
|
|
284
|
+
const byCommand = new Map();
|
|
285
|
+
for (const c of candidates) {
|
|
286
|
+
const list = byCommand.get(c.command) ?? [];
|
|
287
|
+
list.push(c);
|
|
288
|
+
byCommand.set(c.command, list);
|
|
289
|
+
}
|
|
290
|
+
const failures = [];
|
|
291
|
+
const diagnostic = ["[deft session] CLI candidates on PATH (precedence order):"];
|
|
292
|
+
for (const c of candidates) {
|
|
293
|
+
diagnostic.push(formatCandidateLine(c));
|
|
294
|
+
}
|
|
295
|
+
let primaryActive = null;
|
|
296
|
+
const competing = [];
|
|
297
|
+
for (const [command, list] of byCommand) {
|
|
298
|
+
const active = list.find((c) => c.precedence === 0) ?? list[0];
|
|
299
|
+
if (!active)
|
|
300
|
+
continue;
|
|
301
|
+
if (primaryActive === null) {
|
|
302
|
+
primaryActive = active;
|
|
303
|
+
}
|
|
304
|
+
if (effectiveTarget !== null && effectiveTarget.length > 0) {
|
|
305
|
+
if (active.version === null) {
|
|
306
|
+
failures.push(`shell-active ${command} at ${active.path} did not report an engine version ` +
|
|
307
|
+
`(expected ${effectiveTarget})`);
|
|
308
|
+
}
|
|
309
|
+
else if (active.version !== effectiveTarget) {
|
|
310
|
+
// Fail when active is behind the target (or simply unequal for exact match).
|
|
311
|
+
const cmp = compareSemver(active.version, effectiveTarget);
|
|
312
|
+
if (cmp === -1 || cmp === null) {
|
|
313
|
+
failures.push(`shell-active ${command} is engine ${active.version} at ${active.path}, ` +
|
|
314
|
+
`but upgrade target is ${effectiveTarget}`);
|
|
315
|
+
for (const other of list) {
|
|
316
|
+
if (other.precedence === 0)
|
|
317
|
+
continue;
|
|
318
|
+
if (other.version === effectiveTarget ||
|
|
319
|
+
compareSemver(other.version, active.version) === 1) {
|
|
320
|
+
competing.push(other);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Multi-prefix skew: a lower-precedence candidate is strictly newer than active.
|
|
327
|
+
for (const other of list) {
|
|
328
|
+
if (other.precedence === 0)
|
|
329
|
+
continue;
|
|
330
|
+
const cmp = compareSemver(other.version, active.version);
|
|
331
|
+
if (cmp === 1) {
|
|
332
|
+
failures.push(`shell-active ${command} is engine ${active.version ?? "unknown"} at ${active.path}, ` +
|
|
333
|
+
`but a lower-precedence install is newer (${other.version ?? "unknown"} at ${other.path})`);
|
|
334
|
+
competing.push(other);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (failures.length === 0) {
|
|
339
|
+
const activeVer = primaryActive?.version ?? "unknown";
|
|
340
|
+
const activePath = primaryActive?.path ?? "(none)";
|
|
341
|
+
return {
|
|
342
|
+
ok: true,
|
|
343
|
+
code: 0,
|
|
344
|
+
active: primaryActive,
|
|
345
|
+
candidates,
|
|
346
|
+
targetVersion: effectiveTarget,
|
|
347
|
+
message: effectiveTarget !== null
|
|
348
|
+
? `active CLI engine ${activeVer} matches target ${effectiveTarget} (${activePath})`
|
|
349
|
+
: `active CLI engine ${activeVer} is not shadowed (${activePath})`,
|
|
350
|
+
lines: [],
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
// Dedup competing by path.
|
|
354
|
+
const seenPath = new Set();
|
|
355
|
+
const uniqueCompeting = competing.filter((c) => {
|
|
356
|
+
if (seenPath.has(c.path))
|
|
357
|
+
return false;
|
|
358
|
+
seenPath.add(c.path);
|
|
359
|
+
return true;
|
|
360
|
+
});
|
|
361
|
+
const summary = `stale higher-precedence CLI after upgrade: ${failures[0]}` +
|
|
362
|
+
(failures.length > 1 ? ` (+${failures.length - 1} more)` : "");
|
|
363
|
+
const lines = [
|
|
364
|
+
`[deft session] FAIL: ${summary}`,
|
|
365
|
+
...diagnostic,
|
|
366
|
+
...remediationLines({
|
|
367
|
+
active: primaryActive,
|
|
368
|
+
targetVersion: effectiveTarget,
|
|
369
|
+
newerCandidates: uniqueCompeting,
|
|
370
|
+
}),
|
|
371
|
+
];
|
|
372
|
+
return {
|
|
373
|
+
ok: false,
|
|
374
|
+
code: 1,
|
|
375
|
+
active: primaryActive,
|
|
376
|
+
candidates,
|
|
377
|
+
targetVersion: effectiveTarget,
|
|
378
|
+
message: summary,
|
|
379
|
+
lines,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
//# sourceMappingURL=active-cli.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deposit fingerprint for orientation fast-path (#3286).
|
|
3
|
+
*
|
|
4
|
+
* Covers all refresh inputs named by the dual-path Now AC: payload, templates,
|
|
5
|
+
* and engine version. When this fingerprint is unchanged, agents:refresh and
|
|
6
|
+
* verify:cache-fresh may print a one-line sha-match no-op.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEPOSIT_SHA_HEX_LEN = 12;
|
|
9
|
+
/** Canonical one-line no-op message for deposit-sha fast-path (#3286). */
|
|
10
|
+
export declare const DEPOSIT_SHA_MATCH_NOOP = "unchanged - sha match";
|
|
11
|
+
export interface DepositShaInputs {
|
|
12
|
+
readonly engineVersion: string;
|
|
13
|
+
readonly payloadVersion: string;
|
|
14
|
+
readonly templatesHash: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ComputeDepositShaOptions {
|
|
17
|
+
readonly projectRoot?: string;
|
|
18
|
+
readonly frameworkRoot?: string;
|
|
19
|
+
/** Inject inputs for hermetic tests (skips filesystem/version probes). */
|
|
20
|
+
readonly inputs?: Partial<DepositShaInputs>;
|
|
21
|
+
/** Override engine version probe. */
|
|
22
|
+
readonly readEngineVersion?: () => string;
|
|
23
|
+
/** Override payload version probe. */
|
|
24
|
+
readonly readPayloadVersion?: (projectRoot: string, frameworkRoot: string) => string;
|
|
25
|
+
/** Override templates hash probe. */
|
|
26
|
+
readonly readTemplatesHash?: (frameworkRoot: string) => string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Hash templates that feed agents:refresh (agents-entry + related entry points).
|
|
30
|
+
* Missing templates yield a stable "missing" sentinel so the fingerprint still
|
|
31
|
+
* changes when templates appear or disappear.
|
|
32
|
+
*/
|
|
33
|
+
export declare function hashDepositTemplates(frameworkRoot: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve payload / content version for the deposit fingerprint.
|
|
36
|
+
* Prefer live generation token, then VERSION files under framework root.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveDepositPayloadVersion(projectRoot: string, frameworkRoot: string): string;
|
|
39
|
+
/** Resolve the three deposit-sha inputs (payload, templates, engine). */
|
|
40
|
+
export declare function resolveDepositShaInputs(options?: ComputeDepositShaOptions): DepositShaInputs;
|
|
41
|
+
/**
|
|
42
|
+
* Compute the 12-hex deposit fingerprint covering payload + templates + engine.
|
|
43
|
+
*/
|
|
44
|
+
export declare function computeDepositSha(options?: ComputeDepositShaOptions): string;
|
|
45
|
+
/** True when two deposit fingerprints match (case-insensitive, trimmed). */
|
|
46
|
+
export declare function depositShaMatches(a: string | null | undefined, b: string | null | undefined): boolean;
|
|
47
|
+
/** One-line status for orientation / CLI stdout. */
|
|
48
|
+
export declare function formatDepositShaMatchLine(surface: string): string;
|
|
49
|
+
//# sourceMappingURL=deposit-sha.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deposit fingerprint for orientation fast-path (#3286).
|
|
3
|
+
*
|
|
4
|
+
* Covers all refresh inputs named by the dual-path Now AC: payload, templates,
|
|
5
|
+
* and engine version. When this fingerprint is unchanged, agents:refresh and
|
|
6
|
+
* verify:cache-fresh may print a one-line sha-match no-op.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { contentRoot } from "../content-root.js";
|
|
12
|
+
import { readCorePackageVersion } from "../engine-version.js";
|
|
13
|
+
import { readLiveGeneration } from "../freshness/generation.js";
|
|
14
|
+
import { frameworkRoot as resolveAgentsFrameworkRoot } from "../platform/agents-md.js";
|
|
15
|
+
export const DEPOSIT_SHA_HEX_LEN = 12;
|
|
16
|
+
/** Canonical one-line no-op message for deposit-sha fast-path (#3286). */
|
|
17
|
+
export const DEPOSIT_SHA_MATCH_NOOP = "unchanged - sha match";
|
|
18
|
+
function sha12(material) {
|
|
19
|
+
return createHash("sha256").update(material, "utf8").digest("hex").slice(0, DEPOSIT_SHA_HEX_LEN);
|
|
20
|
+
}
|
|
21
|
+
function hashText(text) {
|
|
22
|
+
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Hash templates that feed agents:refresh (agents-entry + related entry points).
|
|
26
|
+
* Missing templates yield a stable "missing" sentinel so the fingerprint still
|
|
27
|
+
* changes when templates appear or disappear.
|
|
28
|
+
*/
|
|
29
|
+
export function hashDepositTemplates(frameworkRoot) {
|
|
30
|
+
const root = contentRoot(frameworkRoot);
|
|
31
|
+
const candidates = [
|
|
32
|
+
join(root, "templates", "agents-entry.md"),
|
|
33
|
+
join(root, "templates", "agent-prompt-preamble.md"),
|
|
34
|
+
];
|
|
35
|
+
const parts = [];
|
|
36
|
+
for (const path of candidates) {
|
|
37
|
+
try {
|
|
38
|
+
if (!existsSync(path)) {
|
|
39
|
+
parts.push(`${path}:missing`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
parts.push(`${path}:${hashText(readFileSync(path, "utf8"))}`);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
parts.push(`${path}:unreadable`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return sha12(parts.join("\n"));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve payload / content version for the deposit fingerprint.
|
|
52
|
+
* Prefer live generation token, then VERSION files under framework root.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveDepositPayloadVersion(projectRoot, frameworkRoot) {
|
|
55
|
+
try {
|
|
56
|
+
const live = readLiveGeneration(projectRoot);
|
|
57
|
+
if (live?.contentVersion) {
|
|
58
|
+
return live.contentVersion;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// fall through
|
|
63
|
+
}
|
|
64
|
+
for (const cand of [
|
|
65
|
+
join(frameworkRoot, "VERSION"),
|
|
66
|
+
join(frameworkRoot, "scripts", "VERSION"),
|
|
67
|
+
join(projectRoot, ".deft-version"),
|
|
68
|
+
]) {
|
|
69
|
+
try {
|
|
70
|
+
if (existsSync(cand)) {
|
|
71
|
+
const v = readFileSync(cand, "utf8").trim();
|
|
72
|
+
if (v.length > 0)
|
|
73
|
+
return v;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// continue
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return "unknown-payload";
|
|
81
|
+
}
|
|
82
|
+
/** Resolve the three deposit-sha inputs (payload, templates, engine). */
|
|
83
|
+
export function resolveDepositShaInputs(options = {}) {
|
|
84
|
+
const frameworkRoot = resolveAgentsFrameworkRoot({
|
|
85
|
+
frameworkRoot: options.frameworkRoot,
|
|
86
|
+
});
|
|
87
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
88
|
+
const engineVersion = options.inputs?.engineVersion ?? (options.readEngineVersion ?? readCorePackageVersion)();
|
|
89
|
+
const payloadVersion = options.inputs?.payloadVersion ??
|
|
90
|
+
(options.readPayloadVersion ?? resolveDepositPayloadVersion)(projectRoot, frameworkRoot);
|
|
91
|
+
const templatesHash = options.inputs?.templatesHash ??
|
|
92
|
+
(options.readTemplatesHash ?? hashDepositTemplates)(frameworkRoot);
|
|
93
|
+
return { engineVersion, payloadVersion, templatesHash };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Compute the 12-hex deposit fingerprint covering payload + templates + engine.
|
|
97
|
+
*/
|
|
98
|
+
export function computeDepositSha(options = {}) {
|
|
99
|
+
const inputs = resolveDepositShaInputs(options);
|
|
100
|
+
const material = [
|
|
101
|
+
`engine=${inputs.engineVersion}`,
|
|
102
|
+
`payload=${inputs.payloadVersion}`,
|
|
103
|
+
`templates=${inputs.templatesHash}`,
|
|
104
|
+
].join("\n");
|
|
105
|
+
return sha12(material);
|
|
106
|
+
}
|
|
107
|
+
/** True when two deposit fingerprints match (case-insensitive, trimmed). */
|
|
108
|
+
export function depositShaMatches(a, b) {
|
|
109
|
+
if (a == null || b == null)
|
|
110
|
+
return false;
|
|
111
|
+
const left = a.trim().toLowerCase();
|
|
112
|
+
const right = b.trim().toLowerCase();
|
|
113
|
+
if (left.length === 0 || right.length === 0)
|
|
114
|
+
return false;
|
|
115
|
+
return left === right;
|
|
116
|
+
}
|
|
117
|
+
/** One-line status for orientation / CLI stdout. */
|
|
118
|
+
export function formatDepositShaMatchLine(surface) {
|
|
119
|
+
return `${surface}: ${DEPOSIT_SHA_MATCH_NOOP}`;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=deposit-sha.js.map
|