@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
package/dist/doctor/main.js
CHANGED
|
@@ -2,6 +2,10 @@ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { evaluate as evaluateAgentsMdAdvisory } from "../agents-md-advisory/evaluate.js";
|
|
4
4
|
import { contentRoot } from "../content-root.js";
|
|
5
|
+
import { describeShadowedPlanExtension, detectShadowedPlanExtensions, } from "../policy/plan-extensions.js";
|
|
6
|
+
import { loadProjectDefinition } from "../policy/resolve.js";
|
|
7
|
+
import { checkLocalEngineIntegrity, classify, detectPackageManager, evaluateSkew, reconcileVersions, plan as resolvePlan, } from "../resolution/index.js";
|
|
8
|
+
import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
|
|
5
9
|
import { agentsRefreshPlan, hasV3ManagedMarker } from "./agents-md.js";
|
|
6
10
|
import { runChecks } from "./checks.js";
|
|
7
11
|
import { CONSUMER_FRAMEWORK_DIRS, EXPECTED_CONTENT_DIRS, EXPECTED_FRAMEWORK_DIRS, NETWORK_DISCLOSURE_LINE, PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE, TASKFILE_INCLUDE_SNIPPET, UV_INSTALL_URL, } from "./constants.js";
|
|
@@ -10,11 +14,33 @@ import { formatAllowedFlagsHint, formatUnknownFlagsError, parseDoctorFlags } fro
|
|
|
10
14
|
import { pythonJsonDump } from "./json.js";
|
|
11
15
|
import { parseInstallRootFromAgentsMd } from "./manifest.js";
|
|
12
16
|
import { createPlainSink } from "./output.js";
|
|
13
|
-
import { resolveDefaultFrameworkRoot, resolvePath, resolveVersion, runningInsideDeftRepo, } from "./paths.js";
|
|
17
|
+
import { readTextSafe, resolveDefaultFrameworkRoot, resolvePath, resolveVersion, runningInsideDeftRepo, } from "./paths.js";
|
|
14
18
|
import { runPayloadStalenessCheck } from "./payload-staleness.js";
|
|
15
19
|
import { runLocalSignpostChecks } from "./signpost-checks.js";
|
|
16
|
-
import { classifyTaskfileInclude, formatMissingIncludeSnippet, resolveConsumerTaskfile, } from "./taskfile.js";
|
|
20
|
+
import { classifyTaskfileInclude, formatMissingIncludeSnippet, includesBlockHasDeftTaskfile, resolveConsumerTaskfile, } from "./taskfile.js";
|
|
17
21
|
import { defaultWhich } from "./which.js";
|
|
22
|
+
const DEFAULT_RESOLUTION_PLATFORMS = ["linux", "darwin", "win32"];
|
|
23
|
+
/**
|
|
24
|
+
* Read the `packageManager` field (Corepack) from a project package.json, or
|
|
25
|
+
* null when absent/unreadable. Lets the doctor detect a pnpm project that has
|
|
26
|
+
* no `pnpm-lock.yaml` yet (fresh Corepack clone) for the #2197 upgrade hint.
|
|
27
|
+
*/
|
|
28
|
+
function readPackageManagerField(packageJsonPath, readTextFn) {
|
|
29
|
+
const raw = readTextFn(packageJsonPath);
|
|
30
|
+
if (raw === null)
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
35
|
+
const field = parsed.packageManager;
|
|
36
|
+
return typeof field === "string" ? field : null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Malformed package.json -- fall through to null (npm default).
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
18
44
|
export function cmdDoctor(args, seams = {}) {
|
|
19
45
|
const flags = parseDoctorFlags(args);
|
|
20
46
|
if (flags.unknown.length > 0) {
|
|
@@ -145,12 +171,27 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
145
171
|
if (flags.network) {
|
|
146
172
|
sink.info(NETWORK_DISCLOSURE_LINE);
|
|
147
173
|
sink.info("Checking payload staleness from install manifest...");
|
|
174
|
+
// Detect the package manager for the upgrade recommendation from signals
|
|
175
|
+
// that are meaningful for a globally-installed CLI: the explicit
|
|
176
|
+
// DEFT_PACKAGE_MANAGER override, the project's `packageManager` field
|
|
177
|
+
// (Corepack), and the project's pnpm-lock.yaml. We deliberately do NOT
|
|
178
|
+
// consult the ambient npm_config_user_agent here (it is set by whatever
|
|
179
|
+
// shell/script spawned the process, not by the consumer's project) so the
|
|
180
|
+
// recommendation is a stable property of the project (#2197).
|
|
181
|
+
const readTextForPm = seams.readText ?? readTextSafe;
|
|
182
|
+
const isFileForPm = seams.isFile ?? ((p) => existsSync(p));
|
|
183
|
+
const packageManager = detectPackageManager({
|
|
184
|
+
env: { DEFT_PACKAGE_MANAGER: process.env.DEFT_PACKAGE_MANAGER },
|
|
185
|
+
packageManagerField: readPackageManagerField(join(projectRoot, "package.json"), readTextForPm),
|
|
186
|
+
pnpmLockPresent: isFileForPm(join(projectRoot, "pnpm-lock.yaml")),
|
|
187
|
+
});
|
|
148
188
|
runPayloadStalenessCheck(projectRoot, sink, addFinding, {
|
|
149
189
|
frameworkRoot,
|
|
150
190
|
readText: seams.readText,
|
|
151
191
|
isFile: seams.isFile,
|
|
152
192
|
runGitLsRemote: seams.runGitLsRemote,
|
|
153
193
|
runNpmViewVersion: seams.runNpmViewVersion,
|
|
194
|
+
packageManager,
|
|
154
195
|
});
|
|
155
196
|
}
|
|
156
197
|
else {
|
|
@@ -213,8 +254,26 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
213
254
|
if (!jsonMode) {
|
|
214
255
|
sink.blank();
|
|
215
256
|
}
|
|
257
|
+
sink.info("Checking USER.md resolution...");
|
|
258
|
+
const userMd = runUserMdResolutionCheck(projectRoot, sink, addFinding, seams);
|
|
259
|
+
if (!jsonMode) {
|
|
260
|
+
sink.blank();
|
|
261
|
+
}
|
|
262
|
+
sink.info("Checking plan.policy namespacing (shadowed bare keys)...");
|
|
263
|
+
runPlanExtensionShadowCheck(projectRoot, sink, addFinding, seams);
|
|
264
|
+
if (!jsonMode) {
|
|
265
|
+
sink.blank();
|
|
266
|
+
}
|
|
216
267
|
sink.info("Checking optional root Taskfile.yml include...");
|
|
217
268
|
runTaskfileIncludeCheck(projectRoot, fixMode, jsonMode, sink, addFinding, seams);
|
|
269
|
+
let resolution = null;
|
|
270
|
+
if (!runningInsideDeftRepo(projectRoot, seams)) {
|
|
271
|
+
if (!jsonMode) {
|
|
272
|
+
sink.blank();
|
|
273
|
+
sink.info("Resolution -- operating mode + single next action (from shared plan())...");
|
|
274
|
+
}
|
|
275
|
+
resolution = runResolutionDecision(projectRoot, jsonMode, sink, addFinding, seams);
|
|
276
|
+
}
|
|
218
277
|
const errorCount = findings.filter((f) => f.severity === "error").length;
|
|
219
278
|
const warningCount = findings.filter((f) => f.severity === "warning").length;
|
|
220
279
|
const exitCode = errorCount > 0 ? 1 : 0;
|
|
@@ -232,6 +291,28 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
232
291
|
findings,
|
|
233
292
|
summary: { errors: errorCount, warnings: warningCount },
|
|
234
293
|
project_root: projectRoot,
|
|
294
|
+
user_md: {
|
|
295
|
+
path: userMd.path,
|
|
296
|
+
rung: userMd.rung,
|
|
297
|
+
found: userMd.found,
|
|
298
|
+
diagnostic: userMd.diagnostic,
|
|
299
|
+
},
|
|
300
|
+
...(resolution
|
|
301
|
+
? {
|
|
302
|
+
resolution: {
|
|
303
|
+
mode: resolution.mode,
|
|
304
|
+
operating_mode: resolution.operatingMode,
|
|
305
|
+
reconciliation: resolution.reconciliation,
|
|
306
|
+
platform_skew: resolution.platformSkew,
|
|
307
|
+
platform_skew_detected: resolution.platformSkewDetected,
|
|
308
|
+
action_required: resolution.actionRequired,
|
|
309
|
+
next_command: resolution.nextCommand,
|
|
310
|
+
root_cause: resolution.rootCause,
|
|
311
|
+
remediation: resolution.remediation,
|
|
312
|
+
warnings: resolution.warnings,
|
|
313
|
+
},
|
|
314
|
+
}
|
|
315
|
+
: {}),
|
|
235
316
|
};
|
|
236
317
|
process.stdout.write(`${pythonJsonDump(payload)}\n`);
|
|
237
318
|
return exitCode;
|
|
@@ -290,7 +371,9 @@ function runInstallIntegrityChecks(projectRoot, sink, addFinding, seams) {
|
|
|
290
371
|
sink.info(`${name}: skip -- ${detail}`);
|
|
291
372
|
continue;
|
|
292
373
|
}
|
|
293
|
-
if ((name === "legacy-layout" ||
|
|
374
|
+
if ((name === "legacy-layout" ||
|
|
375
|
+
name === "canonical-vendored-npm-signpost" ||
|
|
376
|
+
name === "manifest-version-reportable") &&
|
|
294
377
|
status === "fail") {
|
|
295
378
|
sink.warn(`${name}: ${detail}`);
|
|
296
379
|
addFinding({
|
|
@@ -513,4 +596,324 @@ function runTaskfileIncludeCheck(projectRoot, fixMode, jsonMode, sink, addFindin
|
|
|
513
596
|
file: taskfilePath,
|
|
514
597
|
});
|
|
515
598
|
}
|
|
599
|
+
/**
|
|
600
|
+
* Surface the resolved USER.md path + which search rung matched (#2271) so the
|
|
601
|
+
* resolution boundary is visible in doctor output. Uses the shared first-hit-
|
|
602
|
+
* wins resolver, which never throws: an absent USER.md degrades to a `no
|
|
603
|
+
* USER.md found; using defaults` diagnostic. This check never fails the doctor
|
|
604
|
+
* — it emits a `skip`-severity finding (informational) in both states.
|
|
605
|
+
*/
|
|
606
|
+
function runUserMdResolutionCheck(projectRoot, sink, addFinding, seams) {
|
|
607
|
+
const resolveFn = seams.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
|
|
608
|
+
const result = resolveFn(projectRoot);
|
|
609
|
+
// Sanitize newlines on the data-derived path/diagnostic before it lands in a
|
|
610
|
+
// rendered bullet (CWE-116).
|
|
611
|
+
const safePath = result.path.replace(/\r?\n/g, " ");
|
|
612
|
+
const safeDiagnostic = result.diagnostic.replace(/\r?\n/g, " ");
|
|
613
|
+
if (result.found) {
|
|
614
|
+
sink.success(`USER.md resolved (${result.rung}): ${safePath}`);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
sink.info(`USER.md: ${safeDiagnostic}`);
|
|
618
|
+
}
|
|
619
|
+
addFinding({
|
|
620
|
+
severity: "skip",
|
|
621
|
+
message: result.found ? `USER.md resolved (${result.rung}): ${safePath}` : safeDiagnostic,
|
|
622
|
+
check: "user-md-resolution",
|
|
623
|
+
status: result.found ? "resolved" : "defaulted",
|
|
624
|
+
path: safePath,
|
|
625
|
+
rung: result.rung,
|
|
626
|
+
found: result.found,
|
|
627
|
+
});
|
|
628
|
+
return result;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Loud shadow diagnostic (#2301): flag every plan-extension key whose bare form
|
|
632
|
+
* (e.g. `plan.policy`) coexists with the namespaced form (`plan.x-directive/policy`).
|
|
633
|
+
* Because the reader is namespace-first, the bare block is silently ignored --
|
|
634
|
+
* the exact "edit takes no effect, no warning" trap behind #2295. Emits a
|
|
635
|
+
* `warning` finding per shadowed key; never an `error`, so `deft doctor` stays
|
|
636
|
+
* green on a merely-legibility issue. A clean project reports a `skip` finding.
|
|
637
|
+
*/
|
|
638
|
+
function runPlanExtensionShadowCheck(projectRoot, sink, addFinding, seams) {
|
|
639
|
+
const checkName = "plan-extension-shadow";
|
|
640
|
+
const detect = seams.detectPlanExtensionShadows ??
|
|
641
|
+
((root) => {
|
|
642
|
+
const [data] = loadProjectDefinition(root);
|
|
643
|
+
return data === null ? [] : detectShadowedPlanExtensions(data.plan);
|
|
644
|
+
});
|
|
645
|
+
try {
|
|
646
|
+
const shadows = detect(projectRoot);
|
|
647
|
+
if (shadows.length === 0) {
|
|
648
|
+
const cleanMessage = `${checkName}: no shadowed bare plan keys`;
|
|
649
|
+
sink.success(cleanMessage);
|
|
650
|
+
addFinding({ severity: "skip", message: cleanMessage, check: checkName, status: "clean" });
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
for (const shadow of shadows) {
|
|
654
|
+
const message = `plan.policy shadow -- ${describeShadowedPlanExtension(shadow)}`.replace(/\r?\n/g, " ");
|
|
655
|
+
sink.warn(message);
|
|
656
|
+
addFinding({
|
|
657
|
+
severity: "warning",
|
|
658
|
+
message,
|
|
659
|
+
check: checkName,
|
|
660
|
+
status: "shadowed",
|
|
661
|
+
namespaced_key: shadow.namespacedKey,
|
|
662
|
+
legacy_key: shadow.legacyKey,
|
|
663
|
+
shadowed_sub_keys: [...shadow.shadowedSubKeys],
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
catch (exc) {
|
|
668
|
+
const detail = `${exc instanceof Error ? exc.name : "Error"}: ${exc}`.replace(/\r?\n/g, " ");
|
|
669
|
+
const message = `${checkName}: probe failed -- ${detail}`;
|
|
670
|
+
sink.warn(message);
|
|
671
|
+
addFinding({ severity: "warning", message, check: checkName });
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Never emit a bare `task ...` remediation in a project without Taskfile wiring
|
|
676
|
+
* (#2267). The `directive` surface always works; `task deft:X` is optional and
|
|
677
|
+
* only present when the consumer wired the include. `plan()` already emits the
|
|
678
|
+
* `directive` / `npx` / `npm` surface, so this guard is a defensive invariant:
|
|
679
|
+
* any `task`-prefixed command is rewritten to the `directive` surface unless the
|
|
680
|
+
* project actually has the include.
|
|
681
|
+
*/
|
|
682
|
+
/**
|
|
683
|
+
* Detect Taskfile wiring through the injected seam (#2267). Mirrors
|
|
684
|
+
* `classifyTaskfileInclude` but routes the filesystem read through
|
|
685
|
+
* `seams.readText` so the resolution decision stays deterministic + injectable
|
|
686
|
+
* in tests -- unlike a direct `classifyTaskfileInclude(projectRoot)` call, which
|
|
687
|
+
* bypasses every other seam-flowed probe in `runResolutionDecision`.
|
|
688
|
+
*/
|
|
689
|
+
export function resolveTaskfileWiring(projectRoot, seams) {
|
|
690
|
+
const readText = seams.readText ??
|
|
691
|
+
((path) => {
|
|
692
|
+
try {
|
|
693
|
+
return readFileSync(path, "utf8");
|
|
694
|
+
}
|
|
695
|
+
catch {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
for (const name of ["Taskfile.yml", "Taskfile.yaml"]) {
|
|
700
|
+
const text = readText(join(projectRoot, name));
|
|
701
|
+
if (text !== null) {
|
|
702
|
+
return includesBlockHasDeftTaskfile(text.replace(/^\uFEFF/, ""));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
export function enforceDirectiveSurface(command, hasTaskfileWiring) {
|
|
708
|
+
if (command === null) {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
if (!hasTaskfileWiring && /^\s*task\s+/.test(command)) {
|
|
712
|
+
return command.replace(/^\s*task\s+/, "directive ");
|
|
713
|
+
}
|
|
714
|
+
return command;
|
|
715
|
+
}
|
|
716
|
+
/** Human-facing operating mode derived from the orthogonal fact-set. */
|
|
717
|
+
export function resolveOperatingMode(facts) {
|
|
718
|
+
if (facts.preCutoverArtifacts) {
|
|
719
|
+
return "pre-cutover (pre-v0.20 document model -- migrate first)";
|
|
720
|
+
}
|
|
721
|
+
if (!facts.hasDeftCore) {
|
|
722
|
+
if (facts.hasManagedSection) {
|
|
723
|
+
return "hybrid (managed AGENTS.md present; .deft/core/ payload not reconstituted)";
|
|
724
|
+
}
|
|
725
|
+
return facts.hasAppCode || facts.hasGit
|
|
726
|
+
? "brownfield (existing project, no Deft deposit)"
|
|
727
|
+
: "greenfield (no Deft deposit)";
|
|
728
|
+
}
|
|
729
|
+
return facts.hasManagedSection
|
|
730
|
+
? "hybrid (vendored .deft/core/ + managed AGENTS.md)"
|
|
731
|
+
: "vendored (.deft/core/ deposit, no managed AGENTS.md section)";
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Single engine/pin/VERSION reconciliation line. `reconcileVersions` flags
|
|
735
|
+
* content-behind/ahead and engine-behind; engine-ahead skew is folded in here so
|
|
736
|
+
* the line is the one version-state summary the operator reads.
|
|
737
|
+
*/
|
|
738
|
+
export function resolveReconciliationLine(facts) {
|
|
739
|
+
const engine = facts.engineVersion ?? "unreachable";
|
|
740
|
+
const content = facts.deftCorePayloadVersion ?? "none";
|
|
741
|
+
const pin = facts.pinVersion ?? "none";
|
|
742
|
+
const recon = reconcileVersions({
|
|
743
|
+
pinVersion: facts.pinVersion,
|
|
744
|
+
engineVersion: facts.engineVersion,
|
|
745
|
+
contentVersion: facts.deftCorePayloadVersion,
|
|
746
|
+
managedSectionSha: facts.managedSectionSha,
|
|
747
|
+
});
|
|
748
|
+
const notes = [...recon.mismatches];
|
|
749
|
+
if (facts.engineVersion !== null && facts.pinVersion !== null) {
|
|
750
|
+
const skew = evaluateSkew(facts.engineVersion, facts.pinVersion, {});
|
|
751
|
+
if (skew.band === "within-window" || skew.band === "beyond-window") {
|
|
752
|
+
notes.push(skew.message ?? `engine ${facts.engineVersion} ahead of pin ${facts.pinVersion}`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
// Without a committed pin there is nothing to reconcile against, so never
|
|
756
|
+
// claim "aligned" — that would contradict a Next command that tells the
|
|
757
|
+
// operator to init / commit a pin (Greptile #2283 display-accuracy).
|
|
758
|
+
let rawVerdict;
|
|
759
|
+
if (facts.pinVersion === null) {
|
|
760
|
+
rawVerdict =
|
|
761
|
+
facts.engineVersion === null && facts.deftCorePayloadVersion === null
|
|
762
|
+
? "nothing to reconcile yet (no engine, pin, or deposited content)"
|
|
763
|
+
: "no committed package.json pin to reconcile against";
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
rawVerdict = notes.length === 0 ? "current (engine/pin/content aligned)" : notes.join("; ");
|
|
767
|
+
}
|
|
768
|
+
// Sanitize newlines so a data-derived version/mismatch string can't break out
|
|
769
|
+
// of the rendered bullet (CWE-116).
|
|
770
|
+
const verdict = rawVerdict.replace(/\r?\n/g, " ");
|
|
771
|
+
return `engine ${engine}, content ${content}, pin ${pin} -- ${verdict}`;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Probe `.deft/.cli/<platform>` across platforms and report presence + skew. A
|
|
775
|
+
* partial install, or a mix of present + absent platforms, is engine skew: a
|
|
776
|
+
* gate can pass on one platform and fail on another. Read-only.
|
|
777
|
+
*/
|
|
778
|
+
export function resolvePlatformSkew(projectRoot, seams) {
|
|
779
|
+
const platforms = seams.resolutionPlatforms ?? DEFAULT_RESOLUTION_PLATFORMS;
|
|
780
|
+
const isFile = seams.isFile ?? ((p) => existsSync(p));
|
|
781
|
+
const isDir = seams.isDir ??
|
|
782
|
+
((p) => {
|
|
783
|
+
try {
|
|
784
|
+
return statSync(p).isDirectory();
|
|
785
|
+
}
|
|
786
|
+
catch {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
const states = [];
|
|
791
|
+
let anyPresent = false;
|
|
792
|
+
let anyAbsent = false;
|
|
793
|
+
let anyPartial = false;
|
|
794
|
+
for (const platform of platforms) {
|
|
795
|
+
const result = checkLocalEngineIntegrity(projectRoot, { platform, isFile, isDir });
|
|
796
|
+
if (result.usable) {
|
|
797
|
+
states.push(`${platform} intact`);
|
|
798
|
+
anyPresent = true;
|
|
799
|
+
}
|
|
800
|
+
else if (result.partial) {
|
|
801
|
+
states.push(`${platform} partial (missing ${result.missingMarkers.join(", ")})`);
|
|
802
|
+
anyPresent = true;
|
|
803
|
+
anyPartial = true;
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
states.push(`${platform} absent`);
|
|
807
|
+
anyAbsent = true;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
const skewDetected = anyPartial || (anyPresent && anyAbsent);
|
|
811
|
+
const findings = [];
|
|
812
|
+
if (!anyPresent) {
|
|
813
|
+
return {
|
|
814
|
+
line: "no sandbox-local engines (.deft/.cli/<platform> absent on all probed platforms)",
|
|
815
|
+
skewDetected: false,
|
|
816
|
+
findings,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
let line = `.deft/.cli -> ${states.join("; ")}`;
|
|
820
|
+
if (skewDetected) {
|
|
821
|
+
line += " -- cross-platform engine skew detected";
|
|
822
|
+
findings.push({
|
|
823
|
+
severity: "warning",
|
|
824
|
+
message: `Cross-platform .deft/.cli engine skew: ${states.join("; ")}. A partial or platform-divergent local engine can make gates pass on one platform and fail on another; reconcile with a clean per-platform install.`,
|
|
825
|
+
check: "resolution:platform-skew",
|
|
826
|
+
status: "skew",
|
|
827
|
+
platforms: [...platforms],
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
return { line, skewDetected, findings };
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* The single read-only decision surface (#2267). Reuses the shared keystone
|
|
834
|
+
* `classify()` + `plan()` (the ONE classifier) to emit the operating mode, the
|
|
835
|
+
* engine/pin/VERSION reconciliation, cross-platform skew, and exactly ONE
|
|
836
|
+
* primary `Next command:` with a root-cause + remediation rationale. Secondary
|
|
837
|
+
* migration advice (`plan()` warnings) is suppressed until the primary blocker
|
|
838
|
+
* clears. Mutates nothing.
|
|
839
|
+
*/
|
|
840
|
+
export function runResolutionDecision(projectRoot, jsonMode, sink, addFinding, seams) {
|
|
841
|
+
const facts = classify(projectRoot, {
|
|
842
|
+
...(seams.isFile ? { isFile: seams.isFile } : {}),
|
|
843
|
+
...(seams.isDir ? { isDir: seams.isDir } : {}),
|
|
844
|
+
...(seams.readText ? { readText: seams.readText } : {}),
|
|
845
|
+
...(seams.engineProbe ? { engineProbe: seams.engineProbe } : {}),
|
|
846
|
+
});
|
|
847
|
+
const plan = resolvePlan(facts, {}, { platform: process.platform, interactive: false });
|
|
848
|
+
const operatingMode = resolveOperatingMode(facts);
|
|
849
|
+
const reconciliation = resolveReconciliationLine(facts);
|
|
850
|
+
const skew = resolvePlatformSkew(projectRoot, seams);
|
|
851
|
+
const hasTaskfileWiring = resolveTaskfileWiring(projectRoot, seams);
|
|
852
|
+
const nextCommand = enforceDirectiveSurface(plan.nextAction.command, hasTaskfileWiring);
|
|
853
|
+
const actionRequired = plan.mode !== "proceed";
|
|
854
|
+
// Sanitize newlines on data-derived strings before they land in a rendered
|
|
855
|
+
// bullet (CWE-116); root cause / remediation can carry version substrings.
|
|
856
|
+
const rootCauseLine = plan.nextAction.rootCause.replace(/\r?\n/g, " ");
|
|
857
|
+
const remediationLine = plan.nextAction.remediation.replace(/\r?\n/g, " ");
|
|
858
|
+
if (!jsonMode) {
|
|
859
|
+
sink.raw(`Operating mode: ${operatingMode}`);
|
|
860
|
+
sink.raw(`Version reconciliation: ${reconciliation}`);
|
|
861
|
+
sink.raw(`Cross-platform engine: ${skew.line}`);
|
|
862
|
+
if (actionRequired) {
|
|
863
|
+
sink.blank();
|
|
864
|
+
sink.warn("Primary next action (resolve this first; secondary migration advice is deferred until it clears):");
|
|
865
|
+
sink.raw(` Root cause: ${rootCauseLine}`);
|
|
866
|
+
sink.raw(nextCommand
|
|
867
|
+
? `Next command: ${nextCommand}`
|
|
868
|
+
: "Next command: (manual -- no single command)");
|
|
869
|
+
sink.raw(` Does / why safe: ${remediationLine}`);
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
// Derive the proceed line from plan() so it never over-claims "aligned"
|
|
873
|
+
// in the no-pin case (Greptile #2283).
|
|
874
|
+
sink.success(`Resolution: proceed -- ${rootCauseLine}.`);
|
|
875
|
+
// Only once the primary blocker has cleared do we surface the ordered
|
|
876
|
+
// secondary notes (legacy vbrief migrate, no-pin advisory, ...).
|
|
877
|
+
for (const warning of plan.warnings) {
|
|
878
|
+
sink.info(`note: ${warning.replace(/\r?\n/g, " ")}`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
for (const finding of skew.findings) {
|
|
883
|
+
addFinding(finding);
|
|
884
|
+
}
|
|
885
|
+
if (actionRequired) {
|
|
886
|
+
addFinding({
|
|
887
|
+
severity: "warning",
|
|
888
|
+
message: `Next command: ${nextCommand ?? "(manual)"} -- ${rootCauseLine}`,
|
|
889
|
+
check: "resolution",
|
|
890
|
+
status: plan.mode,
|
|
891
|
+
mode: plan.mode,
|
|
892
|
+
next_command: nextCommand,
|
|
893
|
+
root_cause: rootCauseLine,
|
|
894
|
+
remediation: remediationLine,
|
|
895
|
+
operating_mode: operatingMode,
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
addFinding({
|
|
900
|
+
severity: "skip",
|
|
901
|
+
message: "resolution: proceed -- no primary action required",
|
|
902
|
+
check: "resolution",
|
|
903
|
+
status: "proceed",
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
return {
|
|
907
|
+
operatingMode,
|
|
908
|
+
reconciliation,
|
|
909
|
+
platformSkew: skew.line,
|
|
910
|
+
platformSkewDetected: skew.skewDetected,
|
|
911
|
+
mode: plan.mode,
|
|
912
|
+
actionRequired,
|
|
913
|
+
nextCommand,
|
|
914
|
+
rootCause: plan.nextAction.rootCause,
|
|
915
|
+
remediation: plan.nextAction.remediation,
|
|
916
|
+
warnings: plan.warnings,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
516
919
|
//# sourceMappingURL=main.js.map
|
|
@@ -2,6 +2,26 @@ import { readTextSafe } from "./paths.js";
|
|
|
2
2
|
export declare function parseManifest(text: string): Record<string, string>;
|
|
3
3
|
export declare function parseInstallManifest(text: string): Record<string, string>;
|
|
4
4
|
export declare function manifestTagToVersion(manifest: Record<string, string>): string | null;
|
|
5
|
+
/** Reportability classes for an install manifest's version provenance (#2294). */
|
|
6
|
+
export type ManifestVersionSource = "tag" | "ref" | "sha" | "none";
|
|
7
|
+
export interface ReportableVersion {
|
|
8
|
+
/** Semver derived from `tag`/`ref` (leading `v` stripped), else null. */
|
|
9
|
+
readonly version: string | null;
|
|
10
|
+
/** Recorded commit `sha`, trimmed, else null. */
|
|
11
|
+
readonly sha: string | null;
|
|
12
|
+
/** Where a reportable identity was found. */
|
|
13
|
+
readonly source: ManifestVersionSource;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Classify what a manifest can report as its version (#2294). A legacy
|
|
17
|
+
* `deft-install` deposit made without a release pin writes empty `tag`/`ref`
|
|
18
|
+
* and only a short `sha`; `manifestTagToVersion` then returns null and the
|
|
19
|
+
* version is silently unreportable. This helper distinguishes a pinned
|
|
20
|
+
* semver (`tag`/`ref`) from a sha-only deposit from a manifest with no
|
|
21
|
+
* provenance at all, so callers can surface an actionable signal instead of a
|
|
22
|
+
* blank.
|
|
23
|
+
*/
|
|
24
|
+
export declare function manifestReportableVersion(manifest: Record<string, string>): ReportableVersion;
|
|
5
25
|
export declare function manifestCandidatePaths(projectRoot: string, installRoot: string | null): string[];
|
|
6
26
|
export declare function locateManifest(projectRoot: string, installRoot: string | null, isFile?: (p: string) => boolean): string | null;
|
|
7
27
|
export declare function parseInstallRootFromAgentsMd(text: string): string | null;
|
package/dist/doctor/manifest.js
CHANGED
|
@@ -43,6 +43,28 @@ export function manifestTagToVersion(manifest) {
|
|
|
43
43
|
}
|
|
44
44
|
return null;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Classify what a manifest can report as its version (#2294). A legacy
|
|
48
|
+
* `deft-install` deposit made without a release pin writes empty `tag`/`ref`
|
|
49
|
+
* and only a short `sha`; `manifestTagToVersion` then returns null and the
|
|
50
|
+
* version is silently unreportable. This helper distinguishes a pinned
|
|
51
|
+
* semver (`tag`/`ref`) from a sha-only deposit from a manifest with no
|
|
52
|
+
* provenance at all, so callers can surface an actionable signal instead of a
|
|
53
|
+
* blank.
|
|
54
|
+
*/
|
|
55
|
+
export function manifestReportableVersion(manifest) {
|
|
56
|
+
const version = manifestTagToVersion(manifest);
|
|
57
|
+
if (version !== null) {
|
|
58
|
+
const tag = typeof manifest.tag === "string" ? manifest.tag.trim() : "";
|
|
59
|
+
return { version, sha: readSha(manifest), source: tag ? "tag" : "ref" };
|
|
60
|
+
}
|
|
61
|
+
const sha = readSha(manifest);
|
|
62
|
+
return { version: null, sha, source: sha !== null ? "sha" : "none" };
|
|
63
|
+
}
|
|
64
|
+
function readSha(manifest) {
|
|
65
|
+
const raw = typeof manifest.sha === "string" ? manifest.sha.trim() : "";
|
|
66
|
+
return raw || null;
|
|
67
|
+
}
|
|
46
68
|
export function manifestCandidatePaths(projectRoot, installRoot) {
|
|
47
69
|
const raw = [];
|
|
48
70
|
if (installRoot) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PackageManager } from "../resolution/package-manager.js";
|
|
1
2
|
import type { OutputSink } from "./output.js";
|
|
2
3
|
import type { Finding } from "./types.js";
|
|
3
4
|
export interface PayloadStalenessSeams {
|
|
@@ -12,6 +13,13 @@ export interface PayloadStalenessSeams {
|
|
|
12
13
|
ok: boolean;
|
|
13
14
|
version: string;
|
|
14
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Active package manager for rendering the upgrade recommendation (#2197).
|
|
18
|
+
* Defaults to npm so existing behaviour/tests are unchanged; the doctor entry
|
|
19
|
+
* detects the manager and threads it here so a pnpm consumer sees the pnpm
|
|
20
|
+
* upgrade one-liner (`pnpm add -g @deftai/directive@latest`).
|
|
21
|
+
*/
|
|
22
|
+
readonly packageManager?: PackageManager;
|
|
15
23
|
}
|
|
16
24
|
export declare function runPayloadStalenessCheck(projectRoot: string, sink: OutputSink, addFinding: (finding: Finding) => void, seams?: PayloadStalenessSeams): void;
|
|
17
25
|
//# sourceMappingURL=payload-staleness.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { detectCanonicalVendoredManifest, isNpmManaged } from "../init-deposit/migrate.js";
|
|
4
|
-
import { CANONICAL_UPGRADE_COMMAND, NPM_PACKAGE_NAME, VENDORED_NPM_DEPOSIT_UPGRADE_COMMAND, } from "./constants.js";
|
|
4
|
+
import { CANONICAL_UPGRADE_COMMAND, NPM_PACKAGE_NAME, upgradeCommandFor, VENDORED_NPM_DEPOSIT_UPGRADE_COMMAND, } from "./constants.js";
|
|
5
5
|
import { locateManifest, parseInstallManifest } from "./manifest.js";
|
|
6
6
|
import { readTextSafe, resolveDefaultFrameworkRoot } from "./paths.js";
|
|
7
7
|
function isDeftFrameworkRepo(projectRoot, readText = readTextSafe) {
|
|
@@ -92,12 +92,14 @@ function emitUnverified(checkName, reason, sink, addFinding) {
|
|
|
92
92
|
status: "unverified",
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
-
function resolveUpgradeCommand(projectRoot, manifest, isFile) {
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
function resolveUpgradeCommand(projectRoot, manifest, isFile, pm = "npm") {
|
|
96
|
+
const vendored = detectCanonicalVendoredManifest(projectRoot, isFile) !== null && isNpmManaged(manifest);
|
|
97
|
+
if (pm === "pnpm") {
|
|
98
|
+
// Same registry, same tarball -- only the command form differs (#2197).
|
|
99
|
+
const upgrade = upgradeCommandFor("pnpm");
|
|
100
|
+
return vendored ? `${upgrade} && deft update` : upgrade;
|
|
101
|
+
}
|
|
102
|
+
return vendored ? VENDORED_NPM_DEPOSIT_UPGRADE_COMMAND : CANONICAL_UPGRADE_COMMAND;
|
|
101
103
|
}
|
|
102
104
|
function emitStale(checkName, installedLabel, remoteLabel, ref, upgradeCommand, sink, addFinding, extras = {}, behindWord = "remote") {
|
|
103
105
|
const msg = `Framework payload is stale (installed ${installedLabel} behind ${behindWord} ${remoteLabel} for ref '${ref}'). ` +
|
|
@@ -163,7 +165,8 @@ export function runPayloadStalenessCheck(projectRoot, sink, addFinding, seams =
|
|
|
163
165
|
return;
|
|
164
166
|
}
|
|
165
167
|
const manifest = parseInstallManifest(text);
|
|
166
|
-
const
|
|
168
|
+
const activePm = seams.packageManager ?? "npm";
|
|
169
|
+
const upgradeCommand = resolveUpgradeCommand(projectRoot, manifest, isFile, activePm);
|
|
167
170
|
const installedSha = (manifest.sha ?? "").trim();
|
|
168
171
|
const ref = (manifest.ref ?? manifest.tag ?? "").trim();
|
|
169
172
|
const tag = (manifest.tag ?? "").trim();
|
package/dist/doctor/types.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { AdvisoryEvaluateResult } from "../agents-md-advisory/evaluate.js";
|
|
2
|
+
import type { ShadowedPlanExtension } from "../policy/plan-extensions.js";
|
|
3
|
+
import type { EngineProbeResult } from "../resolution/classify.js";
|
|
4
|
+
import type { ResolutionMode } from "../resolution/index.js";
|
|
5
|
+
import type { ResolveUserMdResult } from "../user-config/resolve-user-md.js";
|
|
2
6
|
export declare const EXIT_CLEAN = 0;
|
|
3
7
|
export declare const EXIT_DRIFT = 1;
|
|
4
8
|
export declare const EXIT_CONFIG_ERROR = 2;
|
|
@@ -25,6 +29,34 @@ export interface Finding {
|
|
|
25
29
|
readonly status?: string;
|
|
26
30
|
readonly [key: string]: unknown;
|
|
27
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The single read-only decision surface derived from the shared keystone
|
|
34
|
+
* `plan()` (#2267 / epic #2203). `plan()` is the ONE classifier: doctor never
|
|
35
|
+
* re-derives the mode here, it only renders the mode + the single ordered next
|
|
36
|
+
* action plus the operating-mode / reconciliation / cross-platform-skew context.
|
|
37
|
+
*/
|
|
38
|
+
export interface ResolutionSummary {
|
|
39
|
+
/** Human-facing operating mode (hybrid / vendored / greenfield / ...). */
|
|
40
|
+
readonly operatingMode: string;
|
|
41
|
+
/** engine/pin/VERSION reconciliation verdict line. */
|
|
42
|
+
readonly reconciliation: string;
|
|
43
|
+
/** Cross-platform `.deft/.cli/<platform>` engine presence + skew line. */
|
|
44
|
+
readonly platformSkew: string;
|
|
45
|
+
/** True when the platform installs diverge (present/partial/absent mix). */
|
|
46
|
+
readonly platformSkewDetected: boolean;
|
|
47
|
+
/** Resolved mode from `plan()` (the single classifier). */
|
|
48
|
+
readonly mode: ResolutionMode;
|
|
49
|
+
/** True when the resolved mode requires operator action (mode !== "proceed"). */
|
|
50
|
+
readonly actionRequired: boolean;
|
|
51
|
+
/** The ONE primary next command, directive-surfaced; null for manual actions. */
|
|
52
|
+
readonly nextCommand: string | null;
|
|
53
|
+
/** Why the primary action is recommended. */
|
|
54
|
+
readonly rootCause: string;
|
|
55
|
+
/** What the remediation does + why it is safe. */
|
|
56
|
+
readonly remediation: string;
|
|
57
|
+
/** Ordered secondary warnings from `plan()` (informational, not directives). */
|
|
58
|
+
readonly warnings: readonly string[];
|
|
59
|
+
}
|
|
28
60
|
export interface DoctorFlags {
|
|
29
61
|
readonly session: boolean;
|
|
30
62
|
readonly fix: boolean;
|
|
@@ -79,5 +111,29 @@ export interface DoctorSeams {
|
|
|
79
111
|
readonly readYn?: (prompt: string, defaultYes: boolean) => boolean;
|
|
80
112
|
readonly writeText?: (path: string, content: string) => void;
|
|
81
113
|
readonly now?: () => Date;
|
|
114
|
+
/**
|
|
115
|
+
* Engine-reachability probe threaded into the shared `classify()`. Injected so
|
|
116
|
+
* the resolution decision stays deterministic + offline in tests; the default
|
|
117
|
+
* shells out to `directive --version` / `deft --version`.
|
|
118
|
+
*/
|
|
119
|
+
readonly engineProbe?: () => EngineProbeResult;
|
|
120
|
+
/**
|
|
121
|
+
* Platform ids probed for cross-platform `.deft/.cli/<platform>` engine skew.
|
|
122
|
+
* Defaults to `["linux", "darwin", "win32"]`.
|
|
123
|
+
*/
|
|
124
|
+
readonly resolutionPlatforms?: readonly string[];
|
|
125
|
+
/**
|
|
126
|
+
* USER.md resolver seam (#2271). Injected so the doctor USER.md-resolution
|
|
127
|
+
* surface stays deterministic + offline in tests. Defaults to the shared
|
|
128
|
+
* first-hit-wins resolver scoped to the project root.
|
|
129
|
+
*/
|
|
130
|
+
readonly resolveUserMd?: (projectRoot: string) => ResolveUserMdResult;
|
|
131
|
+
/**
|
|
132
|
+
* Plan-extension shadow detector seam (#2301). Injected so the doctor
|
|
133
|
+
* shadow-diagnostic surface stays deterministic + offline in tests. Defaults
|
|
134
|
+
* to loading PROJECT-DEFINITION and running `detectShadowedPlanExtensions` on
|
|
135
|
+
* its `plan` object; returns [] when no project definition is present.
|
|
136
|
+
*/
|
|
137
|
+
readonly detectPlanExtensionShadows?: (projectRoot: string) => readonly ShadowedPlanExtension[];
|
|
82
138
|
}
|
|
83
139
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export * as releaseE2e from "./release-e2e/index.js";
|
|
|
35
35
|
export * as releasePublish from "./release-publish/index.js";
|
|
36
36
|
export * as releaseRollback from "./release-rollback/index.js";
|
|
37
37
|
export * as render from "./render/index.js";
|
|
38
|
+
export * as resolution from "./resolution/index.js";
|
|
38
39
|
export * as scm from "./scm/index.js";
|
|
39
40
|
export * as scope from "./scope/index.js";
|
|
40
41
|
export * as session from "./session/index.js";
|
|
@@ -42,6 +43,7 @@ export * as slice from "./slice/index.js";
|
|
|
42
43
|
export * as storyReady from "./story-ready/index.js";
|
|
43
44
|
export * as swarm from "./swarm/index.js";
|
|
44
45
|
export * as triage from "./triage/index.js";
|
|
46
|
+
export * as userConfig from "./user-config/index.js";
|
|
45
47
|
export * as validateContent from "./validate-content/index.js";
|
|
46
48
|
export * as vbriefActivate from "./vbrief-activate/index.js";
|
|
47
49
|
export * as vbriefBuild from "./vbrief-build/index.js";
|