@deftai/directive-core 0.68.0 → 0.69.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/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 +352 -2
- package/dist/doctor/payload-staleness.d.ts +8 -0
- package/dist/doctor/payload-staleness.js +11 -8
- package/dist/doctor/types.d.ts +48 -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/hygiene.js +11 -0
- package/dist/init-deposit/index.d.ts +3 -0
- package/dist/init-deposit/index.js +3 -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 +177 -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/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/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/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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PackageManager } from "../resolution/package-manager.js";
|
|
1
2
|
export declare const UV_INSTALL_URL = "https://docs.astral.sh/uv/";
|
|
2
3
|
export declare const UPGRADING_DOC_URL = "https://github.com/deftai/directive/blob/master/content/UPGRADING.md";
|
|
3
4
|
export declare const GO_BRIDGE_RELEASES_URL = "https://github.com/deftai/directive/releases";
|
|
@@ -18,6 +19,14 @@ export declare const DEFT_REPO_POSITIVE_MARKERS: readonly ["content/templates/ag
|
|
|
18
19
|
export declare const EXPECTED_CONTENT_DIRS: readonly ["languages", "strategies", "skills", "templates"];
|
|
19
20
|
/** Post-freeze canonical upgrade path (#1997 / #2003 / #1912). */
|
|
20
21
|
export declare const CANONICAL_UPGRADE_COMMAND = "npm i -g @deftai/directive@latest";
|
|
22
|
+
/**
|
|
23
|
+
* Render the canonical upgrade one-liner for the active package manager (#2197).
|
|
24
|
+
* Defaults to npm (`CANONICAL_UPGRADE_COMMAND`) so existing callers are
|
|
25
|
+
* unchanged; pass `pnpm` to emit the pnpm form
|
|
26
|
+
* (`pnpm add -g @deftai/directive@latest`). Consumed by the doctor
|
|
27
|
+
* payload-staleness recommendation (`payload-staleness.ts`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function upgradeCommandFor(pm?: PackageManager): string;
|
|
21
30
|
/** Vendored npm-managed deposit: global bump plus in-place `.deft/core/` refresh (#2115). */
|
|
22
31
|
export declare const VENDORED_NPM_DEPOSIT_UPGRADE_COMMAND = "npm i -g @deftai/directive@latest && deft update";
|
|
23
32
|
export declare const CLEAN_WINDOW_HOURS = 24;
|
package/dist/doctor/constants.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ENGINE_PACKAGE, renderGlobalInstall, } from "../resolution/package-manager.js";
|
|
1
2
|
export const UV_INSTALL_URL = "https://docs.astral.sh/uv/";
|
|
2
3
|
// Stable, version-neutral upgrade signposts (#1912). Core principle: never bake
|
|
3
4
|
// the upgrade command/version into the artifact being upgraded -- bake in a
|
|
@@ -60,6 +61,16 @@ export const DEFT_REPO_POSITIVE_MARKERS = [
|
|
|
60
61
|
export const EXPECTED_CONTENT_DIRS = ["languages", "strategies", "skills", "templates"];
|
|
61
62
|
/** Post-freeze canonical upgrade path (#1997 / #2003 / #1912). */
|
|
62
63
|
export const CANONICAL_UPGRADE_COMMAND = "npm i -g @deftai/directive@latest";
|
|
64
|
+
/**
|
|
65
|
+
* Render the canonical upgrade one-liner for the active package manager (#2197).
|
|
66
|
+
* Defaults to npm (`CANONICAL_UPGRADE_COMMAND`) so existing callers are
|
|
67
|
+
* unchanged; pass `pnpm` to emit the pnpm form
|
|
68
|
+
* (`pnpm add -g @deftai/directive@latest`). Consumed by the doctor
|
|
69
|
+
* payload-staleness recommendation (`payload-staleness.ts`).
|
|
70
|
+
*/
|
|
71
|
+
export function upgradeCommandFor(pm = "npm") {
|
|
72
|
+
return renderGlobalInstall(pm, `${ENGINE_PACKAGE}@latest`);
|
|
73
|
+
}
|
|
63
74
|
/** Vendored npm-managed deposit: global bump plus in-place `.deft/core/` refresh (#2115). */
|
|
64
75
|
export const VENDORED_NPM_DEPOSIT_UPGRADE_COMMAND = `${CANONICAL_UPGRADE_COMMAND} && deft update`;
|
|
65
76
|
export const CLEAN_WINDOW_HOURS = 24;
|
package/dist/doctor/main.d.ts
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ResolutionFacts } from "../resolution/index.js";
|
|
2
|
+
import { createPlainSink } from "./output.js";
|
|
3
|
+
import type { DoctorSeams, Finding, ResolutionSummary } from "./types.js";
|
|
2
4
|
export declare function cmdDoctor(args: readonly string[], seams?: DoctorSeams): number;
|
|
5
|
+
/**
|
|
6
|
+
* Never emit a bare `task ...` remediation in a project without Taskfile wiring
|
|
7
|
+
* (#2267). The `directive` surface always works; `task deft:X` is optional and
|
|
8
|
+
* only present when the consumer wired the include. `plan()` already emits the
|
|
9
|
+
* `directive` / `npx` / `npm` surface, so this guard is a defensive invariant:
|
|
10
|
+
* any `task`-prefixed command is rewritten to the `directive` surface unless the
|
|
11
|
+
* project actually has the include.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Detect Taskfile wiring through the injected seam (#2267). Mirrors
|
|
15
|
+
* `classifyTaskfileInclude` but routes the filesystem read through
|
|
16
|
+
* `seams.readText` so the resolution decision stays deterministic + injectable
|
|
17
|
+
* in tests -- unlike a direct `classifyTaskfileInclude(projectRoot)` call, which
|
|
18
|
+
* bypasses every other seam-flowed probe in `runResolutionDecision`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveTaskfileWiring(projectRoot: string, seams: DoctorSeams): boolean;
|
|
21
|
+
export declare function enforceDirectiveSurface(command: string | null, hasTaskfileWiring: boolean): string | null;
|
|
22
|
+
/** Human-facing operating mode derived from the orthogonal fact-set. */
|
|
23
|
+
export declare function resolveOperatingMode(facts: ResolutionFacts): string;
|
|
24
|
+
/**
|
|
25
|
+
* Single engine/pin/VERSION reconciliation line. `reconcileVersions` flags
|
|
26
|
+
* content-behind/ahead and engine-behind; engine-ahead skew is folded in here so
|
|
27
|
+
* the line is the one version-state summary the operator reads.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveReconciliationLine(facts: ResolutionFacts): string;
|
|
30
|
+
/**
|
|
31
|
+
* Probe `.deft/.cli/<platform>` across platforms and report presence + skew. A
|
|
32
|
+
* partial install, or a mix of present + absent platforms, is engine skew: a
|
|
33
|
+
* gate can pass on one platform and fail on another. Read-only.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolvePlatformSkew(projectRoot: string, seams: DoctorSeams): {
|
|
36
|
+
line: string;
|
|
37
|
+
skewDetected: boolean;
|
|
38
|
+
findings: Finding[];
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The single read-only decision surface (#2267). Reuses the shared keystone
|
|
42
|
+
* `classify()` + `plan()` (the ONE classifier) to emit the operating mode, the
|
|
43
|
+
* engine/pin/VERSION reconciliation, cross-platform skew, and exactly ONE
|
|
44
|
+
* primary `Next command:` with a root-cause + remediation rationale. Secondary
|
|
45
|
+
* migration advice (`plan()` warnings) is suppressed until the primary blocker
|
|
46
|
+
* clears. Mutates nothing.
|
|
47
|
+
*/
|
|
48
|
+
export declare function runResolutionDecision(projectRoot: string, jsonMode: boolean, sink: ReturnType<typeof createPlainSink>, addFinding: (f: Finding) => void, seams: DoctorSeams): ResolutionSummary;
|
|
3
49
|
//# sourceMappingURL=main.d.ts.map
|
package/dist/doctor/main.js
CHANGED
|
@@ -2,6 +2,8 @@ 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 { checkLocalEngineIntegrity, classify, detectPackageManager, evaluateSkew, reconcileVersions, plan as resolvePlan, } from "../resolution/index.js";
|
|
6
|
+
import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
|
|
5
7
|
import { agentsRefreshPlan, hasV3ManagedMarker } from "./agents-md.js";
|
|
6
8
|
import { runChecks } from "./checks.js";
|
|
7
9
|
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 +12,33 @@ import { formatAllowedFlagsHint, formatUnknownFlagsError, parseDoctorFlags } fro
|
|
|
10
12
|
import { pythonJsonDump } from "./json.js";
|
|
11
13
|
import { parseInstallRootFromAgentsMd } from "./manifest.js";
|
|
12
14
|
import { createPlainSink } from "./output.js";
|
|
13
|
-
import { resolveDefaultFrameworkRoot, resolvePath, resolveVersion, runningInsideDeftRepo, } from "./paths.js";
|
|
15
|
+
import { readTextSafe, resolveDefaultFrameworkRoot, resolvePath, resolveVersion, runningInsideDeftRepo, } from "./paths.js";
|
|
14
16
|
import { runPayloadStalenessCheck } from "./payload-staleness.js";
|
|
15
17
|
import { runLocalSignpostChecks } from "./signpost-checks.js";
|
|
16
|
-
import { classifyTaskfileInclude, formatMissingIncludeSnippet, resolveConsumerTaskfile, } from "./taskfile.js";
|
|
18
|
+
import { classifyTaskfileInclude, formatMissingIncludeSnippet, includesBlockHasDeftTaskfile, resolveConsumerTaskfile, } from "./taskfile.js";
|
|
17
19
|
import { defaultWhich } from "./which.js";
|
|
20
|
+
const DEFAULT_RESOLUTION_PLATFORMS = ["linux", "darwin", "win32"];
|
|
21
|
+
/**
|
|
22
|
+
* Read the `packageManager` field (Corepack) from a project package.json, or
|
|
23
|
+
* null when absent/unreadable. Lets the doctor detect a pnpm project that has
|
|
24
|
+
* no `pnpm-lock.yaml` yet (fresh Corepack clone) for the #2197 upgrade hint.
|
|
25
|
+
*/
|
|
26
|
+
function readPackageManagerField(packageJsonPath, readTextFn) {
|
|
27
|
+
const raw = readTextFn(packageJsonPath);
|
|
28
|
+
if (raw === null)
|
|
29
|
+
return null;
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(raw);
|
|
32
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
33
|
+
const field = parsed.packageManager;
|
|
34
|
+
return typeof field === "string" ? field : null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Malformed package.json -- fall through to null (npm default).
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
18
42
|
export function cmdDoctor(args, seams = {}) {
|
|
19
43
|
const flags = parseDoctorFlags(args);
|
|
20
44
|
if (flags.unknown.length > 0) {
|
|
@@ -145,12 +169,27 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
145
169
|
if (flags.network) {
|
|
146
170
|
sink.info(NETWORK_DISCLOSURE_LINE);
|
|
147
171
|
sink.info("Checking payload staleness from install manifest...");
|
|
172
|
+
// Detect the package manager for the upgrade recommendation from signals
|
|
173
|
+
// that are meaningful for a globally-installed CLI: the explicit
|
|
174
|
+
// DEFT_PACKAGE_MANAGER override, the project's `packageManager` field
|
|
175
|
+
// (Corepack), and the project's pnpm-lock.yaml. We deliberately do NOT
|
|
176
|
+
// consult the ambient npm_config_user_agent here (it is set by whatever
|
|
177
|
+
// shell/script spawned the process, not by the consumer's project) so the
|
|
178
|
+
// recommendation is a stable property of the project (#2197).
|
|
179
|
+
const readTextForPm = seams.readText ?? readTextSafe;
|
|
180
|
+
const isFileForPm = seams.isFile ?? ((p) => existsSync(p));
|
|
181
|
+
const packageManager = detectPackageManager({
|
|
182
|
+
env: { DEFT_PACKAGE_MANAGER: process.env.DEFT_PACKAGE_MANAGER },
|
|
183
|
+
packageManagerField: readPackageManagerField(join(projectRoot, "package.json"), readTextForPm),
|
|
184
|
+
pnpmLockPresent: isFileForPm(join(projectRoot, "pnpm-lock.yaml")),
|
|
185
|
+
});
|
|
148
186
|
runPayloadStalenessCheck(projectRoot, sink, addFinding, {
|
|
149
187
|
frameworkRoot,
|
|
150
188
|
readText: seams.readText,
|
|
151
189
|
isFile: seams.isFile,
|
|
152
190
|
runGitLsRemote: seams.runGitLsRemote,
|
|
153
191
|
runNpmViewVersion: seams.runNpmViewVersion,
|
|
192
|
+
packageManager,
|
|
154
193
|
});
|
|
155
194
|
}
|
|
156
195
|
else {
|
|
@@ -213,8 +252,21 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
213
252
|
if (!jsonMode) {
|
|
214
253
|
sink.blank();
|
|
215
254
|
}
|
|
255
|
+
sink.info("Checking USER.md resolution...");
|
|
256
|
+
const userMd = runUserMdResolutionCheck(projectRoot, sink, addFinding, seams);
|
|
257
|
+
if (!jsonMode) {
|
|
258
|
+
sink.blank();
|
|
259
|
+
}
|
|
216
260
|
sink.info("Checking optional root Taskfile.yml include...");
|
|
217
261
|
runTaskfileIncludeCheck(projectRoot, fixMode, jsonMode, sink, addFinding, seams);
|
|
262
|
+
let resolution = null;
|
|
263
|
+
if (!runningInsideDeftRepo(projectRoot, seams)) {
|
|
264
|
+
if (!jsonMode) {
|
|
265
|
+
sink.blank();
|
|
266
|
+
sink.info("Resolution -- operating mode + single next action (from shared plan())...");
|
|
267
|
+
}
|
|
268
|
+
resolution = runResolutionDecision(projectRoot, jsonMode, sink, addFinding, seams);
|
|
269
|
+
}
|
|
218
270
|
const errorCount = findings.filter((f) => f.severity === "error").length;
|
|
219
271
|
const warningCount = findings.filter((f) => f.severity === "warning").length;
|
|
220
272
|
const exitCode = errorCount > 0 ? 1 : 0;
|
|
@@ -232,6 +284,28 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
232
284
|
findings,
|
|
233
285
|
summary: { errors: errorCount, warnings: warningCount },
|
|
234
286
|
project_root: projectRoot,
|
|
287
|
+
user_md: {
|
|
288
|
+
path: userMd.path,
|
|
289
|
+
rung: userMd.rung,
|
|
290
|
+
found: userMd.found,
|
|
291
|
+
diagnostic: userMd.diagnostic,
|
|
292
|
+
},
|
|
293
|
+
...(resolution
|
|
294
|
+
? {
|
|
295
|
+
resolution: {
|
|
296
|
+
mode: resolution.mode,
|
|
297
|
+
operating_mode: resolution.operatingMode,
|
|
298
|
+
reconciliation: resolution.reconciliation,
|
|
299
|
+
platform_skew: resolution.platformSkew,
|
|
300
|
+
platform_skew_detected: resolution.platformSkewDetected,
|
|
301
|
+
action_required: resolution.actionRequired,
|
|
302
|
+
next_command: resolution.nextCommand,
|
|
303
|
+
root_cause: resolution.rootCause,
|
|
304
|
+
remediation: resolution.remediation,
|
|
305
|
+
warnings: resolution.warnings,
|
|
306
|
+
},
|
|
307
|
+
}
|
|
308
|
+
: {}),
|
|
235
309
|
};
|
|
236
310
|
process.stdout.write(`${pythonJsonDump(payload)}\n`);
|
|
237
311
|
return exitCode;
|
|
@@ -513,4 +587,280 @@ function runTaskfileIncludeCheck(projectRoot, fixMode, jsonMode, sink, addFindin
|
|
|
513
587
|
file: taskfilePath,
|
|
514
588
|
});
|
|
515
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Surface the resolved USER.md path + which search rung matched (#2271) so the
|
|
592
|
+
* resolution boundary is visible in doctor output. Uses the shared first-hit-
|
|
593
|
+
* wins resolver, which never throws: an absent USER.md degrades to a `no
|
|
594
|
+
* USER.md found; using defaults` diagnostic. This check never fails the doctor
|
|
595
|
+
* — it emits a `skip`-severity finding (informational) in both states.
|
|
596
|
+
*/
|
|
597
|
+
function runUserMdResolutionCheck(projectRoot, sink, addFinding, seams) {
|
|
598
|
+
const resolveFn = seams.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
|
|
599
|
+
const result = resolveFn(projectRoot);
|
|
600
|
+
// Sanitize newlines on the data-derived path/diagnostic before it lands in a
|
|
601
|
+
// rendered bullet (CWE-116).
|
|
602
|
+
const safePath = result.path.replace(/\r?\n/g, " ");
|
|
603
|
+
const safeDiagnostic = result.diagnostic.replace(/\r?\n/g, " ");
|
|
604
|
+
if (result.found) {
|
|
605
|
+
sink.success(`USER.md resolved (${result.rung}): ${safePath}`);
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
sink.info(`USER.md: ${safeDiagnostic}`);
|
|
609
|
+
}
|
|
610
|
+
addFinding({
|
|
611
|
+
severity: "skip",
|
|
612
|
+
message: result.found ? `USER.md resolved (${result.rung}): ${safePath}` : safeDiagnostic,
|
|
613
|
+
check: "user-md-resolution",
|
|
614
|
+
status: result.found ? "resolved" : "defaulted",
|
|
615
|
+
path: safePath,
|
|
616
|
+
rung: result.rung,
|
|
617
|
+
found: result.found,
|
|
618
|
+
});
|
|
619
|
+
return result;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Never emit a bare `task ...` remediation in a project without Taskfile wiring
|
|
623
|
+
* (#2267). The `directive` surface always works; `task deft:X` is optional and
|
|
624
|
+
* only present when the consumer wired the include. `plan()` already emits the
|
|
625
|
+
* `directive` / `npx` / `npm` surface, so this guard is a defensive invariant:
|
|
626
|
+
* any `task`-prefixed command is rewritten to the `directive` surface unless the
|
|
627
|
+
* project actually has the include.
|
|
628
|
+
*/
|
|
629
|
+
/**
|
|
630
|
+
* Detect Taskfile wiring through the injected seam (#2267). Mirrors
|
|
631
|
+
* `classifyTaskfileInclude` but routes the filesystem read through
|
|
632
|
+
* `seams.readText` so the resolution decision stays deterministic + injectable
|
|
633
|
+
* in tests -- unlike a direct `classifyTaskfileInclude(projectRoot)` call, which
|
|
634
|
+
* bypasses every other seam-flowed probe in `runResolutionDecision`.
|
|
635
|
+
*/
|
|
636
|
+
export function resolveTaskfileWiring(projectRoot, seams) {
|
|
637
|
+
const readText = seams.readText ??
|
|
638
|
+
((path) => {
|
|
639
|
+
try {
|
|
640
|
+
return readFileSync(path, "utf8");
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
for (const name of ["Taskfile.yml", "Taskfile.yaml"]) {
|
|
647
|
+
const text = readText(join(projectRoot, name));
|
|
648
|
+
if (text !== null) {
|
|
649
|
+
return includesBlockHasDeftTaskfile(text.replace(/^\uFEFF/, ""));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
export function enforceDirectiveSurface(command, hasTaskfileWiring) {
|
|
655
|
+
if (command === null) {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
if (!hasTaskfileWiring && /^\s*task\s+/.test(command)) {
|
|
659
|
+
return command.replace(/^\s*task\s+/, "directive ");
|
|
660
|
+
}
|
|
661
|
+
return command;
|
|
662
|
+
}
|
|
663
|
+
/** Human-facing operating mode derived from the orthogonal fact-set. */
|
|
664
|
+
export function resolveOperatingMode(facts) {
|
|
665
|
+
if (facts.preCutoverArtifacts) {
|
|
666
|
+
return "pre-cutover (pre-v0.20 document model -- migrate first)";
|
|
667
|
+
}
|
|
668
|
+
if (!facts.hasDeftCore) {
|
|
669
|
+
if (facts.hasManagedSection) {
|
|
670
|
+
return "hybrid (managed AGENTS.md present; .deft/core/ payload not reconstituted)";
|
|
671
|
+
}
|
|
672
|
+
return facts.hasAppCode || facts.hasGit
|
|
673
|
+
? "brownfield (existing project, no Deft deposit)"
|
|
674
|
+
: "greenfield (no Deft deposit)";
|
|
675
|
+
}
|
|
676
|
+
return facts.hasManagedSection
|
|
677
|
+
? "hybrid (vendored .deft/core/ + managed AGENTS.md)"
|
|
678
|
+
: "vendored (.deft/core/ deposit, no managed AGENTS.md section)";
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Single engine/pin/VERSION reconciliation line. `reconcileVersions` flags
|
|
682
|
+
* content-behind/ahead and engine-behind; engine-ahead skew is folded in here so
|
|
683
|
+
* the line is the one version-state summary the operator reads.
|
|
684
|
+
*/
|
|
685
|
+
export function resolveReconciliationLine(facts) {
|
|
686
|
+
const engine = facts.engineVersion ?? "unreachable";
|
|
687
|
+
const content = facts.deftCorePayloadVersion ?? "none";
|
|
688
|
+
const pin = facts.pinVersion ?? "none";
|
|
689
|
+
const recon = reconcileVersions({
|
|
690
|
+
pinVersion: facts.pinVersion,
|
|
691
|
+
engineVersion: facts.engineVersion,
|
|
692
|
+
contentVersion: facts.deftCorePayloadVersion,
|
|
693
|
+
managedSectionSha: facts.managedSectionSha,
|
|
694
|
+
});
|
|
695
|
+
const notes = [...recon.mismatches];
|
|
696
|
+
if (facts.engineVersion !== null && facts.pinVersion !== null) {
|
|
697
|
+
const skew = evaluateSkew(facts.engineVersion, facts.pinVersion, {});
|
|
698
|
+
if (skew.band === "within-window" || skew.band === "beyond-window") {
|
|
699
|
+
notes.push(skew.message ?? `engine ${facts.engineVersion} ahead of pin ${facts.pinVersion}`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
// Without a committed pin there is nothing to reconcile against, so never
|
|
703
|
+
// claim "aligned" — that would contradict a Next command that tells the
|
|
704
|
+
// operator to init / commit a pin (Greptile #2283 display-accuracy).
|
|
705
|
+
let rawVerdict;
|
|
706
|
+
if (facts.pinVersion === null) {
|
|
707
|
+
rawVerdict =
|
|
708
|
+
facts.engineVersion === null && facts.deftCorePayloadVersion === null
|
|
709
|
+
? "nothing to reconcile yet (no engine, pin, or deposited content)"
|
|
710
|
+
: "no committed package.json pin to reconcile against";
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
rawVerdict = notes.length === 0 ? "current (engine/pin/content aligned)" : notes.join("; ");
|
|
714
|
+
}
|
|
715
|
+
// Sanitize newlines so a data-derived version/mismatch string can't break out
|
|
716
|
+
// of the rendered bullet (CWE-116).
|
|
717
|
+
const verdict = rawVerdict.replace(/\r?\n/g, " ");
|
|
718
|
+
return `engine ${engine}, content ${content}, pin ${pin} -- ${verdict}`;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Probe `.deft/.cli/<platform>` across platforms and report presence + skew. A
|
|
722
|
+
* partial install, or a mix of present + absent platforms, is engine skew: a
|
|
723
|
+
* gate can pass on one platform and fail on another. Read-only.
|
|
724
|
+
*/
|
|
725
|
+
export function resolvePlatformSkew(projectRoot, seams) {
|
|
726
|
+
const platforms = seams.resolutionPlatforms ?? DEFAULT_RESOLUTION_PLATFORMS;
|
|
727
|
+
const isFile = seams.isFile ?? ((p) => existsSync(p));
|
|
728
|
+
const isDir = seams.isDir ??
|
|
729
|
+
((p) => {
|
|
730
|
+
try {
|
|
731
|
+
return statSync(p).isDirectory();
|
|
732
|
+
}
|
|
733
|
+
catch {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
const states = [];
|
|
738
|
+
let anyPresent = false;
|
|
739
|
+
let anyAbsent = false;
|
|
740
|
+
let anyPartial = false;
|
|
741
|
+
for (const platform of platforms) {
|
|
742
|
+
const result = checkLocalEngineIntegrity(projectRoot, { platform, isFile, isDir });
|
|
743
|
+
if (result.usable) {
|
|
744
|
+
states.push(`${platform} intact`);
|
|
745
|
+
anyPresent = true;
|
|
746
|
+
}
|
|
747
|
+
else if (result.partial) {
|
|
748
|
+
states.push(`${platform} partial (missing ${result.missingMarkers.join(", ")})`);
|
|
749
|
+
anyPresent = true;
|
|
750
|
+
anyPartial = true;
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
states.push(`${platform} absent`);
|
|
754
|
+
anyAbsent = true;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const skewDetected = anyPartial || (anyPresent && anyAbsent);
|
|
758
|
+
const findings = [];
|
|
759
|
+
if (!anyPresent) {
|
|
760
|
+
return {
|
|
761
|
+
line: "no sandbox-local engines (.deft/.cli/<platform> absent on all probed platforms)",
|
|
762
|
+
skewDetected: false,
|
|
763
|
+
findings,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
let line = `.deft/.cli -> ${states.join("; ")}`;
|
|
767
|
+
if (skewDetected) {
|
|
768
|
+
line += " -- cross-platform engine skew detected";
|
|
769
|
+
findings.push({
|
|
770
|
+
severity: "warning",
|
|
771
|
+
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.`,
|
|
772
|
+
check: "resolution:platform-skew",
|
|
773
|
+
status: "skew",
|
|
774
|
+
platforms: [...platforms],
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
return { line, skewDetected, findings };
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* The single read-only decision surface (#2267). Reuses the shared keystone
|
|
781
|
+
* `classify()` + `plan()` (the ONE classifier) to emit the operating mode, the
|
|
782
|
+
* engine/pin/VERSION reconciliation, cross-platform skew, and exactly ONE
|
|
783
|
+
* primary `Next command:` with a root-cause + remediation rationale. Secondary
|
|
784
|
+
* migration advice (`plan()` warnings) is suppressed until the primary blocker
|
|
785
|
+
* clears. Mutates nothing.
|
|
786
|
+
*/
|
|
787
|
+
export function runResolutionDecision(projectRoot, jsonMode, sink, addFinding, seams) {
|
|
788
|
+
const facts = classify(projectRoot, {
|
|
789
|
+
...(seams.isFile ? { isFile: seams.isFile } : {}),
|
|
790
|
+
...(seams.isDir ? { isDir: seams.isDir } : {}),
|
|
791
|
+
...(seams.readText ? { readText: seams.readText } : {}),
|
|
792
|
+
...(seams.engineProbe ? { engineProbe: seams.engineProbe } : {}),
|
|
793
|
+
});
|
|
794
|
+
const plan = resolvePlan(facts, {}, { platform: process.platform, interactive: false });
|
|
795
|
+
const operatingMode = resolveOperatingMode(facts);
|
|
796
|
+
const reconciliation = resolveReconciliationLine(facts);
|
|
797
|
+
const skew = resolvePlatformSkew(projectRoot, seams);
|
|
798
|
+
const hasTaskfileWiring = resolveTaskfileWiring(projectRoot, seams);
|
|
799
|
+
const nextCommand = enforceDirectiveSurface(plan.nextAction.command, hasTaskfileWiring);
|
|
800
|
+
const actionRequired = plan.mode !== "proceed";
|
|
801
|
+
// Sanitize newlines on data-derived strings before they land in a rendered
|
|
802
|
+
// bullet (CWE-116); root cause / remediation can carry version substrings.
|
|
803
|
+
const rootCauseLine = plan.nextAction.rootCause.replace(/\r?\n/g, " ");
|
|
804
|
+
const remediationLine = plan.nextAction.remediation.replace(/\r?\n/g, " ");
|
|
805
|
+
if (!jsonMode) {
|
|
806
|
+
sink.raw(`Operating mode: ${operatingMode}`);
|
|
807
|
+
sink.raw(`Version reconciliation: ${reconciliation}`);
|
|
808
|
+
sink.raw(`Cross-platform engine: ${skew.line}`);
|
|
809
|
+
if (actionRequired) {
|
|
810
|
+
sink.blank();
|
|
811
|
+
sink.warn("Primary next action (resolve this first; secondary migration advice is deferred until it clears):");
|
|
812
|
+
sink.raw(` Root cause: ${rootCauseLine}`);
|
|
813
|
+
sink.raw(nextCommand
|
|
814
|
+
? `Next command: ${nextCommand}`
|
|
815
|
+
: "Next command: (manual -- no single command)");
|
|
816
|
+
sink.raw(` Does / why safe: ${remediationLine}`);
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
// Derive the proceed line from plan() so it never over-claims "aligned"
|
|
820
|
+
// in the no-pin case (Greptile #2283).
|
|
821
|
+
sink.success(`Resolution: proceed -- ${rootCauseLine}.`);
|
|
822
|
+
// Only once the primary blocker has cleared do we surface the ordered
|
|
823
|
+
// secondary notes (legacy vbrief migrate, no-pin advisory, ...).
|
|
824
|
+
for (const warning of plan.warnings) {
|
|
825
|
+
sink.info(`note: ${warning.replace(/\r?\n/g, " ")}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
for (const finding of skew.findings) {
|
|
830
|
+
addFinding(finding);
|
|
831
|
+
}
|
|
832
|
+
if (actionRequired) {
|
|
833
|
+
addFinding({
|
|
834
|
+
severity: "warning",
|
|
835
|
+
message: `Next command: ${nextCommand ?? "(manual)"} -- ${rootCauseLine}`,
|
|
836
|
+
check: "resolution",
|
|
837
|
+
status: plan.mode,
|
|
838
|
+
mode: plan.mode,
|
|
839
|
+
next_command: nextCommand,
|
|
840
|
+
root_cause: rootCauseLine,
|
|
841
|
+
remediation: remediationLine,
|
|
842
|
+
operating_mode: operatingMode,
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
addFinding({
|
|
847
|
+
severity: "skip",
|
|
848
|
+
message: "resolution: proceed -- no primary action required",
|
|
849
|
+
check: "resolution",
|
|
850
|
+
status: "proceed",
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
operatingMode,
|
|
855
|
+
reconciliation,
|
|
856
|
+
platformSkew: skew.line,
|
|
857
|
+
platformSkewDetected: skew.skewDetected,
|
|
858
|
+
mode: plan.mode,
|
|
859
|
+
actionRequired,
|
|
860
|
+
nextCommand,
|
|
861
|
+
rootCause: plan.nextAction.rootCause,
|
|
862
|
+
remediation: plan.nextAction.remediation,
|
|
863
|
+
warnings: plan.warnings,
|
|
864
|
+
};
|
|
865
|
+
}
|
|
516
866
|
//# sourceMappingURL=main.js.map
|
|
@@ -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,7 @@
|
|
|
1
1
|
import type { AdvisoryEvaluateResult } from "../agents-md-advisory/evaluate.js";
|
|
2
|
+
import type { EngineProbeResult } from "../resolution/classify.js";
|
|
3
|
+
import type { ResolutionMode } from "../resolution/index.js";
|
|
4
|
+
import type { ResolveUserMdResult } from "../user-config/resolve-user-md.js";
|
|
2
5
|
export declare const EXIT_CLEAN = 0;
|
|
3
6
|
export declare const EXIT_DRIFT = 1;
|
|
4
7
|
export declare const EXIT_CONFIG_ERROR = 2;
|
|
@@ -25,6 +28,34 @@ export interface Finding {
|
|
|
25
28
|
readonly status?: string;
|
|
26
29
|
readonly [key: string]: unknown;
|
|
27
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The single read-only decision surface derived from the shared keystone
|
|
33
|
+
* `plan()` (#2267 / epic #2203). `plan()` is the ONE classifier: doctor never
|
|
34
|
+
* re-derives the mode here, it only renders the mode + the single ordered next
|
|
35
|
+
* action plus the operating-mode / reconciliation / cross-platform-skew context.
|
|
36
|
+
*/
|
|
37
|
+
export interface ResolutionSummary {
|
|
38
|
+
/** Human-facing operating mode (hybrid / vendored / greenfield / ...). */
|
|
39
|
+
readonly operatingMode: string;
|
|
40
|
+
/** engine/pin/VERSION reconciliation verdict line. */
|
|
41
|
+
readonly reconciliation: string;
|
|
42
|
+
/** Cross-platform `.deft/.cli/<platform>` engine presence + skew line. */
|
|
43
|
+
readonly platformSkew: string;
|
|
44
|
+
/** True when the platform installs diverge (present/partial/absent mix). */
|
|
45
|
+
readonly platformSkewDetected: boolean;
|
|
46
|
+
/** Resolved mode from `plan()` (the single classifier). */
|
|
47
|
+
readonly mode: ResolutionMode;
|
|
48
|
+
/** True when the resolved mode requires operator action (mode !== "proceed"). */
|
|
49
|
+
readonly actionRequired: boolean;
|
|
50
|
+
/** The ONE primary next command, directive-surfaced; null for manual actions. */
|
|
51
|
+
readonly nextCommand: string | null;
|
|
52
|
+
/** Why the primary action is recommended. */
|
|
53
|
+
readonly rootCause: string;
|
|
54
|
+
/** What the remediation does + why it is safe. */
|
|
55
|
+
readonly remediation: string;
|
|
56
|
+
/** Ordered secondary warnings from `plan()` (informational, not directives). */
|
|
57
|
+
readonly warnings: readonly string[];
|
|
58
|
+
}
|
|
28
59
|
export interface DoctorFlags {
|
|
29
60
|
readonly session: boolean;
|
|
30
61
|
readonly fix: boolean;
|
|
@@ -79,5 +110,22 @@ export interface DoctorSeams {
|
|
|
79
110
|
readonly readYn?: (prompt: string, defaultYes: boolean) => boolean;
|
|
80
111
|
readonly writeText?: (path: string, content: string) => void;
|
|
81
112
|
readonly now?: () => Date;
|
|
113
|
+
/**
|
|
114
|
+
* Engine-reachability probe threaded into the shared `classify()`. Injected so
|
|
115
|
+
* the resolution decision stays deterministic + offline in tests; the default
|
|
116
|
+
* shells out to `directive --version` / `deft --version`.
|
|
117
|
+
*/
|
|
118
|
+
readonly engineProbe?: () => EngineProbeResult;
|
|
119
|
+
/**
|
|
120
|
+
* Platform ids probed for cross-platform `.deft/.cli/<platform>` engine skew.
|
|
121
|
+
* Defaults to `["linux", "darwin", "win32"]`.
|
|
122
|
+
*/
|
|
123
|
+
readonly resolutionPlatforms?: readonly string[];
|
|
124
|
+
/**
|
|
125
|
+
* USER.md resolver seam (#2271). Injected so the doctor USER.md-resolution
|
|
126
|
+
* surface stays deterministic + offline in tests. Defaults to the shared
|
|
127
|
+
* first-hit-wins resolver scoped to the project root.
|
|
128
|
+
*/
|
|
129
|
+
readonly resolveUserMd?: (projectRoot: string) => ResolveUserMdResult;
|
|
82
130
|
}
|
|
83
131
|
//# 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";
|
package/dist/index.js
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";
|