@deftai/directive-core 0.66.2 → 0.68.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/agents-md-advisory/evaluate.d.ts +44 -0
- package/dist/agents-md-advisory/evaluate.js +115 -0
- package/dist/agents-md-advisory/index.d.ts +2 -0
- package/dist/agents-md-advisory/index.js +2 -0
- package/dist/agents-md-budget/evaluate.d.ts +38 -0
- package/dist/agents-md-budget/evaluate.js +152 -0
- package/dist/agents-md-budget/index.d.ts +2 -0
- package/dist/agents-md-budget/index.js +2 -0
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +1 -1
- package/dist/check/orchestrator.d.ts +14 -4
- package/dist/check/orchestrator.js +30 -6
- package/dist/codebase/map.d.ts +1 -0
- package/dist/codebase/map.js +5 -2
- package/dist/doctor/checks.js +11 -11
- package/dist/doctor/constants.d.ts +7 -2
- package/dist/doctor/constants.js +17 -1
- package/dist/doctor/flags.js +5 -1
- package/dist/doctor/main.js +99 -8
- package/dist/doctor/payload-staleness.js +15 -6
- package/dist/doctor/types.d.ts +7 -0
- package/dist/forward-coverage/evaluate.d.ts +55 -0
- package/dist/forward-coverage/evaluate.js +254 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/constants.d.ts +14 -0
- package/dist/init-deposit/constants.js +10 -0
- package/dist/init-deposit/hygiene.d.ts +50 -0
- package/dist/init-deposit/hygiene.js +176 -0
- package/dist/init-deposit/init-deposit.d.ts +1 -0
- package/dist/init-deposit/init-deposit.js +7 -1
- package/dist/init-deposit/refresh.d.ts +2 -0
- package/dist/init-deposit/refresh.js +48 -30
- package/dist/init-deposit/scaffold.d.ts +3 -4
- package/dist/init-deposit/scaffold.js +3 -24
- package/dist/intake/github-auth-modes.d.ts +1 -0
- package/dist/intake/github-auth-modes.js +1 -0
- package/dist/intake/issue-ingest.d.ts +6 -2
- package/dist/intake/issue-ingest.js +65 -9
- package/dist/integration-e2e/helpers.d.ts +0 -1
- package/dist/integration-e2e/helpers.js +3 -7
- package/dist/layout/lifecycle-ref.d.ts +15 -0
- package/dist/layout/lifecycle-ref.js +55 -0
- package/dist/platform/agents-consumer-header.d.ts +13 -0
- package/dist/platform/agents-consumer-header.js +57 -0
- package/dist/platform/agents-md.d.ts +28 -0
- package/dist/platform/agents-md.js +38 -1
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/policy/agents-md-advisory.d.ts +52 -0
- package/dist/policy/agents-md-advisory.js +63 -0
- package/dist/policy/agents-md-budget.d.ts +24 -0
- package/dist/policy/agents-md-budget.js +89 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/resolve.js +56 -46
- package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
- package/dist/pr-merge-readiness/ci-gate.js +79 -0
- package/dist/pr-merge-readiness/compute.d.ts +15 -1
- package/dist/pr-merge-readiness/compute.js +129 -9
- package/dist/pr-merge-readiness/gh.d.ts +8 -0
- package/dist/pr-merge-readiness/gh.js +30 -5
- package/dist/pr-merge-readiness/index.d.ts +5 -1
- package/dist/pr-merge-readiness/index.js +3 -0
- package/dist/pr-merge-readiness/main.d.ts +3 -0
- package/dist/pr-merge-readiness/main.js +73 -6
- package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
- package/dist/pr-merge-readiness/mergeability.js +105 -0
- package/dist/pr-merge-readiness/output.js +20 -0
- package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
- package/dist/pr-merge-readiness/slizard-gate.js +119 -0
- package/dist/pr-monitor/index.d.ts +1 -1
- package/dist/pr-monitor/index.js +1 -1
- package/dist/pr-monitor/monitor.d.ts +8 -2
- package/dist/pr-monitor/monitor.js +35 -6
- package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
- package/dist/pr-wait-mergeable/wrappers.js +13 -5
- package/dist/pr-watch/constants.d.ts +33 -0
- package/dist/pr-watch/constants.js +33 -0
- package/dist/pr-watch/index.d.ts +6 -0
- package/dist/pr-watch/index.js +6 -0
- package/dist/pr-watch/main.d.ts +21 -0
- package/dist/pr-watch/main.js +206 -0
- package/dist/pr-watch/probe.d.ts +13 -0
- package/dist/pr-watch/probe.js +100 -0
- package/dist/pr-watch/types.d.ts +47 -0
- package/dist/pr-watch/types.js +2 -0
- package/dist/pr-watch/watch.d.ts +12 -0
- package/dist/pr-watch/watch.js +122 -0
- package/dist/preflight-cache/evaluate.d.ts +1 -0
- package/dist/preflight-cache/evaluate.js +17 -10
- package/dist/render/project-render.d.ts +16 -2
- package/dist/render/project-render.js +155 -97
- package/dist/scope/capacity-stamp.d.ts +24 -2
- package/dist/scope/capacity-stamp.js +80 -28
- package/dist/scope/decomposed-refs.js +2 -2
- package/dist/scope/project-definition-sync.js +3 -3
- package/dist/scope/vbrief-ref.d.ts +2 -1
- package/dist/scope/vbrief-ref.js +3 -2
- package/dist/spec-authority/constants.d.ts +6 -0
- package/dist/spec-authority/constants.js +11 -0
- package/dist/spec-authority/resolver.js +3 -3
- package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
- package/dist/swarm/finalize-cohort-cli.js +100 -0
- package/dist/swarm/finalize-cohort.d.ts +39 -0
- package/dist/swarm/finalize-cohort.js +589 -0
- package/dist/swarm/index.d.ts +2 -0
- package/dist/swarm/index.js +2 -0
- package/dist/swarm/launch.d.ts +13 -0
- package/dist/swarm/launch.js +19 -7
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +11 -10
- package/dist/swarm/routing.d.ts +9 -0
- package/dist/swarm/routing.js +46 -0
- package/dist/swarm/verify-review-clean-cli.js +25 -1
- package/dist/swarm/verify-review-clean.d.ts +9 -1
- package/dist/swarm/verify-review-clean.js +76 -3
- package/dist/triage/queue/index.d.ts +1 -0
- package/dist/triage/queue/index.js +1 -0
- package/dist/triage/queue/project.d.ts +6 -1
- package/dist/triage/queue/project.js +9 -4
- package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
- package/dist/triage/queue/reconcile-live-state.js +54 -0
- package/dist/triage/scope/mutations-core.js +91 -81
- package/dist/triage/scope/resolve.js +4 -3
- package/dist/triage/scope-drift/add-ignore.js +33 -32
- package/dist/triage/scope-drift/scope-rules.js +3 -3
- package/dist/triage/subscribe/index.js +51 -45
- package/dist/triage/welcome/writers.js +79 -64
- package/dist/vbrief-build/project-definition-io.js +8 -3
- package/dist/vbrief-validate/paths.js +8 -4
- package/dist/vbrief-validate/precutover.d.ts +1 -1
- package/dist/vbrief-validate/precutover.js +12 -10
- package/dist/verify-source/biome-config.d.ts +41 -0
- package/dist/verify-source/biome-config.js +97 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/xbrief-migrate/agents-header.d.ts +69 -0
- package/dist/xbrief-migrate/agents-header.js +179 -0
- package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
- package/dist/xbrief-migrate/fs-helpers.js +11 -0
- package/dist/xbrief-migrate/index.d.ts +1 -0
- package/dist/xbrief-migrate/index.js +1 -0
- package/dist/xbrief-migrate/migrate-project.js +29 -10
- package/package.json +15 -7
- package/dist/install-upgrade/index.d.ts +0 -13
- package/dist/install-upgrade/index.js +0 -251
package/dist/doctor/flags.js
CHANGED
|
@@ -5,6 +5,7 @@ export function parseDoctorFlags(args) {
|
|
|
5
5
|
let json = false;
|
|
6
6
|
let quiet = false;
|
|
7
7
|
let full = false;
|
|
8
|
+
let network = false;
|
|
8
9
|
let help = false;
|
|
9
10
|
let projectRoot = null;
|
|
10
11
|
const unknown = [];
|
|
@@ -26,6 +27,9 @@ export function parseDoctorFlags(args) {
|
|
|
26
27
|
else if (token === "--full") {
|
|
27
28
|
full = true;
|
|
28
29
|
}
|
|
30
|
+
else if (token === "--network") {
|
|
31
|
+
network = true;
|
|
32
|
+
}
|
|
29
33
|
else if (token === "-h" || token === "--help") {
|
|
30
34
|
help = true;
|
|
31
35
|
}
|
|
@@ -52,7 +56,7 @@ export function parseDoctorFlags(args) {
|
|
|
52
56
|
}
|
|
53
57
|
i += 1;
|
|
54
58
|
}
|
|
55
|
-
return { session, fix, json, quiet, full, help, projectRoot, unknown };
|
|
59
|
+
return { session, fix, json, quiet, full, network, help, projectRoot, unknown };
|
|
56
60
|
}
|
|
57
61
|
export function formatUnknownFlagsError(unknown) {
|
|
58
62
|
return `Unknown flag(s): ${unknown.join(", ")}`;
|
package/dist/doctor/main.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { evaluate as evaluateAgentsMdAdvisory } from "../agents-md-advisory/evaluate.js";
|
|
3
4
|
import { contentRoot } from "../content-root.js";
|
|
4
5
|
import { agentsRefreshPlan, hasV3ManagedMarker } from "./agents-md.js";
|
|
5
6
|
import { runChecks } from "./checks.js";
|
|
6
|
-
import { CONSUMER_FRAMEWORK_DIRS, EXPECTED_CONTENT_DIRS, EXPECTED_FRAMEWORK_DIRS, TASKFILE_INCLUDE_SNIPPET, UV_INSTALL_URL, } from "./constants.js";
|
|
7
|
+
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";
|
|
7
8
|
import { decideThrottle, formatIsoZ, readState, renderDoctorStatusLine, writeState, } from "./doctor-state.js";
|
|
8
9
|
import { formatAllowedFlagsHint, formatUnknownFlagsError, parseDoctorFlags } from "./flags.js";
|
|
9
10
|
import { pythonJsonDump } from "./json.js";
|
|
@@ -130,13 +131,38 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
130
131
|
if (!jsonMode) {
|
|
131
132
|
sink.blank();
|
|
132
133
|
}
|
|
133
|
-
sink.info("Checking
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
sink.info("Checking AGENTS.md legibility (advisory)...");
|
|
135
|
+
runAgentsMdAdvisoryCheck(projectRoot, sink, addFinding, seams);
|
|
136
|
+
if (!jsonMode) {
|
|
137
|
+
sink.blank();
|
|
138
|
+
}
|
|
139
|
+
// #2182: payload-staleness is the only doctor check that can reach a
|
|
140
|
+
// registry (git remote, then npm as a fallback). It stays in the OFFLINE
|
|
141
|
+
// tier (skipped) unless the operator explicitly opts into the NETWORK tier
|
|
142
|
+
// via `--network`, and the tool + registry class is disclosed BEFORE the
|
|
143
|
+
// check runs -- never silently, never as a side effect of a read-only
|
|
144
|
+
// `deft doctor` invocation (e.g. from the gated session-ritual step).
|
|
145
|
+
if (flags.network) {
|
|
146
|
+
sink.info(NETWORK_DISCLOSURE_LINE);
|
|
147
|
+
sink.info("Checking payload staleness from install manifest...");
|
|
148
|
+
runPayloadStalenessCheck(projectRoot, sink, addFinding, {
|
|
149
|
+
frameworkRoot,
|
|
150
|
+
readText: seams.readText,
|
|
151
|
+
isFile: seams.isFile,
|
|
152
|
+
runGitLsRemote: seams.runGitLsRemote,
|
|
153
|
+
runNpmViewVersion: seams.runNpmViewVersion,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
sink.info(`payload-staleness: ${PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE}`);
|
|
158
|
+
addFinding({
|
|
159
|
+
severity: "skip",
|
|
160
|
+
message: PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE,
|
|
161
|
+
check: "payload-staleness",
|
|
162
|
+
status: "skip",
|
|
163
|
+
reason: "offline-tier",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
140
166
|
if (!jsonMode) {
|
|
141
167
|
sink.blank();
|
|
142
168
|
}
|
|
@@ -346,6 +372,71 @@ function runAgentsMdFreshnessCheck(projectRoot, sink, addFinding, seams) {
|
|
|
346
372
|
addFinding({ severity: "warning", message, check: checkName });
|
|
347
373
|
}
|
|
348
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Advisory (never-failing) consumer AGENTS.md legibility signal (#2155).
|
|
377
|
+
*
|
|
378
|
+
* Reports the unmanaged (project-authored) region size against the soft budget
|
|
379
|
+
* (`plan.policy.agentsMdAdvisory.unmanagedSoftMaxLines`, generous by default).
|
|
380
|
+
* The managed section is EXCLUDED. This is the consumer companion to the
|
|
381
|
+
* maintainer-only #645 ratchet and follows the advise -> observe -> enforce
|
|
382
|
+
* posture (#1419): it emits at most a `warning` finding, NEVER an `error`, so
|
|
383
|
+
* `deft doctor` (and the `check:consumer` aggregate that depends on it) can
|
|
384
|
+
* never fail-close on a judgment call about the consumer's own file.
|
|
385
|
+
*/
|
|
386
|
+
function runAgentsMdAdvisoryCheck(projectRoot, sink, addFinding, seams) {
|
|
387
|
+
const checkName = "agents-md-advisory";
|
|
388
|
+
// Only meaningful for consumer installs (managed markers present). In the
|
|
389
|
+
// maintainer repo the #645 ratchet owns this file, so skip -- mirrors the
|
|
390
|
+
// freshness check's guard above.
|
|
391
|
+
if (runningInsideDeftRepo(projectRoot, seams) ||
|
|
392
|
+
!hasV3ManagedMarker(projectRoot, seams.readText)) {
|
|
393
|
+
const skipReason = "no managed-section markers (likely maintainer repo)";
|
|
394
|
+
sink.info(`${checkName}: skip -- ${skipReason}`);
|
|
395
|
+
addFinding({ severity: "skip", message: skipReason, check: checkName, status: "skip" });
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
const evalFn = seams.agentsMdAdvisoryEvaluate ?? ((root) => evaluateAgentsMdAdvisory(root));
|
|
400
|
+
const result = evalFn(projectRoot);
|
|
401
|
+
if (result.over && result.counts !== null) {
|
|
402
|
+
const message = `AGENTS.md unmanaged (project-authored) region is ${result.counts.unmanaged} lines, ` +
|
|
403
|
+
`over the soft budget of ${result.softMaxLines} (advisory only -- your build is NOT ` +
|
|
404
|
+
"affected). AGENTS.md is a map, not a manual (#1882): push detail into a reference doc " +
|
|
405
|
+
"and leave a pointer (see content/docs/good-agents-md.md). Raise " +
|
|
406
|
+
"plan.policy.agentsMdAdvisory.unmanagedSoftMaxLines to accept the growth and silence this.";
|
|
407
|
+
sink.warn(message);
|
|
408
|
+
addFinding({
|
|
409
|
+
severity: "warning",
|
|
410
|
+
message,
|
|
411
|
+
check: checkName,
|
|
412
|
+
status: "over-soft-budget",
|
|
413
|
+
suggestion: "content/docs/good-agents-md.md",
|
|
414
|
+
});
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (result.counts !== null) {
|
|
418
|
+
sink.success(`${checkName}: unmanaged region ${result.counts.unmanaged}/${result.softMaxLines} lines (within soft budget)`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// No counts (missing / unreadable / malformed AGENTS.md): advisory stays
|
|
422
|
+
// silent-but-informational; never a doctor error.
|
|
423
|
+
sink.info(`${checkName}: skip -- AGENTS.md not measurable`);
|
|
424
|
+
addFinding({
|
|
425
|
+
severity: "skip",
|
|
426
|
+
message: "AGENTS.md not measurable",
|
|
427
|
+
check: checkName,
|
|
428
|
+
status: "skip",
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
catch (exc) {
|
|
432
|
+
// Sanitize newlines so an error string can't break out of the markdown
|
|
433
|
+
// bullet when findings are rendered (CWE-116).
|
|
434
|
+
const detail = `${exc instanceof Error ? exc.name : "Error"}: ${exc}`.replace(/\r?\n/g, " ");
|
|
435
|
+
const message = `${checkName}: probe failed -- ${detail}`;
|
|
436
|
+
sink.warn(message);
|
|
437
|
+
addFinding({ severity: "warning", message, check: checkName });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
349
440
|
function runTaskfileIncludeCheck(projectRoot, fixMode, jsonMode, sink, addFinding, seams) {
|
|
350
441
|
if (runningInsideDeftRepo(projectRoot, seams)) {
|
|
351
442
|
sink.info("Skipping Taskfile include check -- running inside the deft framework repo (the repo's own Taskfile.yml is the surface).");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import {
|
|
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
5
|
import { locateManifest, parseInstallManifest } from "./manifest.js";
|
|
5
6
|
import { readTextSafe, resolveDefaultFrameworkRoot } from "./paths.js";
|
|
6
7
|
function isDeftFrameworkRepo(projectRoot, readText = readTextSafe) {
|
|
@@ -91,9 +92,16 @@ function emitUnverified(checkName, reason, sink, addFinding) {
|
|
|
91
92
|
status: "unverified",
|
|
92
93
|
});
|
|
93
94
|
}
|
|
94
|
-
function
|
|
95
|
+
function resolveUpgradeCommand(projectRoot, manifest, isFile) {
|
|
96
|
+
const vendoredManifest = detectCanonicalVendoredManifest(projectRoot, isFile);
|
|
97
|
+
if (vendoredManifest !== null && isNpmManaged(manifest)) {
|
|
98
|
+
return VENDORED_NPM_DEPOSIT_UPGRADE_COMMAND;
|
|
99
|
+
}
|
|
100
|
+
return CANONICAL_UPGRADE_COMMAND;
|
|
101
|
+
}
|
|
102
|
+
function emitStale(checkName, installedLabel, remoteLabel, ref, upgradeCommand, sink, addFinding, extras = {}, behindWord = "remote") {
|
|
95
103
|
const msg = `Framework payload is stale (installed ${installedLabel} behind ${behindWord} ${remoteLabel} for ref '${ref}'). ` +
|
|
96
|
-
`Recommendation: run \`${
|
|
104
|
+
`Recommendation: run \`${upgradeCommand}\` from any shell with Node ≥ 20.`;
|
|
97
105
|
sink.warn(msg);
|
|
98
106
|
addFinding({
|
|
99
107
|
severity: "warning",
|
|
@@ -101,7 +109,7 @@ function emitStale(checkName, installedLabel, remoteLabel, ref, sink, addFinding
|
|
|
101
109
|
check: checkName,
|
|
102
110
|
status: "stale",
|
|
103
111
|
ref,
|
|
104
|
-
suggestion:
|
|
112
|
+
suggestion: upgradeCommand,
|
|
105
113
|
...extras,
|
|
106
114
|
});
|
|
107
115
|
}
|
|
@@ -155,6 +163,7 @@ export function runPayloadStalenessCheck(projectRoot, sink, addFinding, seams =
|
|
|
155
163
|
return;
|
|
156
164
|
}
|
|
157
165
|
const manifest = parseInstallManifest(text);
|
|
166
|
+
const upgradeCommand = resolveUpgradeCommand(projectRoot, manifest, isFile);
|
|
158
167
|
const installedSha = (manifest.sha ?? "").trim();
|
|
159
168
|
const ref = (manifest.ref ?? manifest.tag ?? "").trim();
|
|
160
169
|
const tag = (manifest.tag ?? "").trim();
|
|
@@ -204,7 +213,7 @@ export function runPayloadStalenessCheck(projectRoot, sink, addFinding, seams =
|
|
|
204
213
|
sink.info(`${checkName}: current (sha matches remote)`);
|
|
205
214
|
return;
|
|
206
215
|
}
|
|
207
|
-
emitStale(checkName, `sha ${installedSha.slice(0, 8)}...`, `sha ${remoteSha.slice(0, 8)}...`, ref, sink, addFinding, { installed_sha: installedSha, remote_sha: remoteSha, resolver: "git-ls-remote" });
|
|
216
|
+
emitStale(checkName, `sha ${installedSha.slice(0, 8)}...`, `sha ${remoteSha.slice(0, 8)}...`, ref, upgradeCommand, sink, addFinding, { installed_sha: installedSha, remote_sha: remoteSha, resolver: "git-ls-remote" });
|
|
208
217
|
return;
|
|
209
218
|
}
|
|
210
219
|
}
|
|
@@ -212,7 +221,7 @@ export function runPayloadStalenessCheck(projectRoot, sink, addFinding, seams =
|
|
|
212
221
|
const installedVersion = manifestVersion(ref, tag);
|
|
213
222
|
if (npmResult.ok && installedVersion) {
|
|
214
223
|
if (semverLessThan(installedVersion, npmResult.version)) {
|
|
215
|
-
emitStale(checkName, `v${installedVersion}`, `v${npmResult.version}`, ref, sink, addFinding, {
|
|
224
|
+
emitStale(checkName, `v${installedVersion}`, `v${npmResult.version}`, ref, upgradeCommand, sink, addFinding, {
|
|
216
225
|
installed_version: installedVersion,
|
|
217
226
|
remote_version: npmResult.version,
|
|
218
227
|
resolver: "npm-view",
|
package/dist/doctor/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AdvisoryEvaluateResult } from "../agents-md-advisory/evaluate.js";
|
|
1
2
|
export declare const EXIT_CLEAN = 0;
|
|
2
3
|
export declare const EXIT_DRIFT = 1;
|
|
3
4
|
export declare const EXIT_CONFIG_ERROR = 2;
|
|
@@ -30,6 +31,7 @@ export interface DoctorFlags {
|
|
|
30
31
|
readonly json: boolean;
|
|
31
32
|
readonly quiet: boolean;
|
|
32
33
|
readonly full: boolean;
|
|
34
|
+
readonly network: boolean;
|
|
33
35
|
readonly help: boolean;
|
|
34
36
|
readonly projectRoot: string | null;
|
|
35
37
|
readonly unknown: readonly string[];
|
|
@@ -60,7 +62,12 @@ export interface DoctorSeams {
|
|
|
60
62
|
ok: boolean;
|
|
61
63
|
stdout: string;
|
|
62
64
|
};
|
|
65
|
+
readonly runNpmViewVersion?: () => {
|
|
66
|
+
ok: boolean;
|
|
67
|
+
version: string;
|
|
68
|
+
};
|
|
63
69
|
readonly agentsRefreshPlan?: (projectRoot: string) => Record<string, unknown>;
|
|
70
|
+
readonly agentsMdAdvisoryEvaluate?: (projectRoot: string) => AdvisoryEvaluateResult;
|
|
64
71
|
readonly readState?: (projectRoot: string) => DoctorState | null;
|
|
65
72
|
readonly writeState?: (projectRoot: string, payload: {
|
|
66
73
|
exitCode: number;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forward-coverage/evaluate.ts -- deterministic forward-coverage gate (#1310).
|
|
3
|
+
*
|
|
4
|
+
* Migrates the prose-tier "new source files MUST include corresponding test
|
|
5
|
+
* files in the same PR" rule (AGENTS.md) to deterministic enforcement,
|
|
6
|
+
* mirroring `verify:encoding` (#798) and `verify:branch` (#747).
|
|
7
|
+
*
|
|
8
|
+
* Behaviour (v1 = NEW-file granularity only, to keep false positives near
|
|
9
|
+
* zero): read the staged (`--staged`) or HEAD-relative diff; for each NEW
|
|
10
|
+
* source file (`*.py` / `*.go` / `*.ts` / `*.tsx`, excluding test files and
|
|
11
|
+
* `*.d.ts`), assert a corresponding test file was added in the SAME diff.
|
|
12
|
+
* Three-state exit: 0 clean / 1 missing forward coverage / 2 config error.
|
|
13
|
+
*/
|
|
14
|
+
/** Diff scope: `staged` = index vs HEAD; `head` = working tree + index vs HEAD. */
|
|
15
|
+
export type ForwardCoverageMode = "head" | "staged";
|
|
16
|
+
/** A new source file that has no corresponding test file in the same diff. */
|
|
17
|
+
export interface MissingCoverage {
|
|
18
|
+
/** POSIX-form repo-relative path of the uncovered new source file. */
|
|
19
|
+
readonly path: string;
|
|
20
|
+
/** Candidate test-file basenames the gate searched the diff for. */
|
|
21
|
+
readonly expectedTests: string[];
|
|
22
|
+
}
|
|
23
|
+
/** Result of a forward-coverage evaluation; mirrors the encoding-gate tuple. */
|
|
24
|
+
export interface ForwardCoverageResult {
|
|
25
|
+
readonly exitCode: 0 | 1 | 2;
|
|
26
|
+
readonly missing: MissingCoverage[];
|
|
27
|
+
readonly message: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ForwardCoverageOptions {
|
|
30
|
+
readonly mode?: ForwardCoverageMode;
|
|
31
|
+
readonly allowListPath?: string | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* True when `relPath` is a test file (excluded from the "needs coverage" set
|
|
35
|
+
* AND counted as available coverage). Covers the co-located TS/TSX
|
|
36
|
+
* `.test` / `.spec` convention, Go `*_test.go`, and Python `test_*.py` /
|
|
37
|
+
* `*_test.py`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isTestFile(relPath: string): boolean;
|
|
40
|
+
/** True when `relPath` is an in-scope, non-test, non-`.d.ts` source file. */
|
|
41
|
+
export declare function isSourceFile(relPath: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Candidate test-file basenames that would satisfy forward coverage for a
|
|
44
|
+
* given source file, keyed on its extension + stem. Matched by basename
|
|
45
|
+
* (directory-agnostic) so co-located, `tests/`, and `__tests__/` layouts all
|
|
46
|
+
* count -- keeping the false-positive rate near zero for v1.
|
|
47
|
+
*/
|
|
48
|
+
export declare function expectedTestBasenames(sourcePath: string): string[];
|
|
49
|
+
/**
|
|
50
|
+
* Pure evaluation returning `{ exitCode, missing, message }`. Three-state exit
|
|
51
|
+
* (0 clean / 1 missing forward coverage / 2 config error). Mirrors the shape of
|
|
52
|
+
* `encoding.evaluate` (#798) so both gates read the same way.
|
|
53
|
+
*/
|
|
54
|
+
export declare function evaluateForwardCoverage(projectRoot: string, options?: ForwardCoverageOptions): ForwardCoverageResult;
|
|
55
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forward-coverage/evaluate.ts -- deterministic forward-coverage gate (#1310).
|
|
3
|
+
*
|
|
4
|
+
* Migrates the prose-tier "new source files MUST include corresponding test
|
|
5
|
+
* files in the same PR" rule (AGENTS.md) to deterministic enforcement,
|
|
6
|
+
* mirroring `verify:encoding` (#798) and `verify:branch` (#747).
|
|
7
|
+
*
|
|
8
|
+
* Behaviour (v1 = NEW-file granularity only, to keep false positives near
|
|
9
|
+
* zero): read the staged (`--staged`) or HEAD-relative diff; for each NEW
|
|
10
|
+
* source file (`*.py` / `*.go` / `*.ts` / `*.tsx`, excluding test files and
|
|
11
|
+
* `*.d.ts`), assert a corresponding test file was added in the SAME diff.
|
|
12
|
+
* Three-state exit: 0 clean / 1 missing forward coverage / 2 config error.
|
|
13
|
+
*/
|
|
14
|
+
import { spawnSync } from "node:child_process";
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { basename } from "node:path";
|
|
17
|
+
import { GitCommandError, GitNotFoundError } from "../encoding/git.js";
|
|
18
|
+
import { fnmatchCase } from "../encoding/text.js";
|
|
19
|
+
/** Source-file extensions in scope for v1. */
|
|
20
|
+
const SOURCE_EXTENSIONS = new Set([".py", ".go", ".ts", ".tsx"]);
|
|
21
|
+
/** Raised by `loadAllowList` when the path does not exist. */
|
|
22
|
+
class AllowListNotFoundError extends Error {
|
|
23
|
+
}
|
|
24
|
+
/** Return the final `.ext` (lowercased) of a path, or "" when none. */
|
|
25
|
+
function extOf(pathStr) {
|
|
26
|
+
const b = basename(pathStr);
|
|
27
|
+
const dot = b.lastIndexOf(".");
|
|
28
|
+
return dot > 0 ? b.slice(dot).toLowerCase() : "";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* True when `relPath` is a test file (excluded from the "needs coverage" set
|
|
32
|
+
* AND counted as available coverage). Covers the co-located TS/TSX
|
|
33
|
+
* `.test` / `.spec` convention, Go `*_test.go`, and Python `test_*.py` /
|
|
34
|
+
* `*_test.py`.
|
|
35
|
+
*/
|
|
36
|
+
export function isTestFile(relPath) {
|
|
37
|
+
const b = basename(relPath).toLowerCase();
|
|
38
|
+
if (b.endsWith(".test.ts") ||
|
|
39
|
+
b.endsWith(".test.tsx") ||
|
|
40
|
+
b.endsWith(".spec.ts") ||
|
|
41
|
+
b.endsWith(".spec.tsx")) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (b.endsWith("_test.go")) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (b.endsWith(".py") && (b.startsWith("test_") || b.endsWith("_test.py"))) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
/** True when `relPath` is an in-scope, non-test, non-`.d.ts` source file. */
|
|
53
|
+
export function isSourceFile(relPath) {
|
|
54
|
+
const b = basename(relPath).toLowerCase();
|
|
55
|
+
if (b.endsWith(".d.ts")) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (!SOURCE_EXTENSIONS.has(extOf(b))) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return !isTestFile(relPath);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Candidate test-file basenames that would satisfy forward coverage for a
|
|
65
|
+
* given source file, keyed on its extension + stem. Matched by basename
|
|
66
|
+
* (directory-agnostic) so co-located, `tests/`, and `__tests__/` layouts all
|
|
67
|
+
* count -- keeping the false-positive rate near zero for v1.
|
|
68
|
+
*/
|
|
69
|
+
export function expectedTestBasenames(sourcePath) {
|
|
70
|
+
const b = basename(sourcePath);
|
|
71
|
+
const ext = extOf(b);
|
|
72
|
+
const stem = b.slice(0, b.length - ext.length);
|
|
73
|
+
switch (ext) {
|
|
74
|
+
case ".ts":
|
|
75
|
+
return [`${stem}.test.ts`, `${stem}.spec.ts`];
|
|
76
|
+
case ".tsx":
|
|
77
|
+
return [`${stem}.test.tsx`, `${stem}.spec.tsx`, `${stem}.test.ts`, `${stem}.spec.ts`];
|
|
78
|
+
case ".py":
|
|
79
|
+
return [`test_${stem}.py`, `${stem}_test.py`];
|
|
80
|
+
case ".go":
|
|
81
|
+
return [`${stem}_test.go`];
|
|
82
|
+
default:
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function loadAllowList(path) {
|
|
87
|
+
if (path === null || path === undefined) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
let raw;
|
|
91
|
+
try {
|
|
92
|
+
raw = readFileSync(path, "utf8");
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
const e = err;
|
|
96
|
+
if (e.code === "ENOENT") {
|
|
97
|
+
throw new AllowListNotFoundError(path);
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
const out = [];
|
|
102
|
+
for (const line of raw.split(/\r\n|[\n\r]/)) {
|
|
103
|
+
const stripped = line.trim();
|
|
104
|
+
if (stripped.length === 0 || stripped.startsWith("#")) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
out.push(stripped);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
function isAllowListed(relPath, patterns) {
|
|
112
|
+
return patterns.some((pat) => fnmatchCase(relPath, pat));
|
|
113
|
+
}
|
|
114
|
+
/** Run `git` and return its status + stdout, mapping ENOENT to GitNotFoundError. */
|
|
115
|
+
function git(args, projectRoot) {
|
|
116
|
+
const result = spawnSync("git", args, {
|
|
117
|
+
cwd: projectRoot,
|
|
118
|
+
encoding: "utf8",
|
|
119
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
120
|
+
});
|
|
121
|
+
if (result.error !== undefined) {
|
|
122
|
+
const e = result.error;
|
|
123
|
+
if (e.code === "ENOENT") {
|
|
124
|
+
throw new GitNotFoundError("'git' executable not found on PATH");
|
|
125
|
+
}
|
|
126
|
+
throw new GitCommandError(`git ${args.join(" ")} failed: ${String(e.message)}`);
|
|
127
|
+
}
|
|
128
|
+
return { status: result.status ?? 1, stdout: result.stdout ?? "" };
|
|
129
|
+
}
|
|
130
|
+
/** Run `git`, throwing GitCommandError on any non-zero exit; returns stdout. */
|
|
131
|
+
function gitOrThrow(args, projectRoot) {
|
|
132
|
+
const { status, stdout } = git(args, projectRoot);
|
|
133
|
+
if (status !== 0) {
|
|
134
|
+
throw new GitCommandError(`git ${args.join(" ")} exited ${status}`);
|
|
135
|
+
}
|
|
136
|
+
return stdout;
|
|
137
|
+
}
|
|
138
|
+
function toLines(stdout) {
|
|
139
|
+
return stdout
|
|
140
|
+
.split("\n")
|
|
141
|
+
.map((l) => l.replace(/\r$/, ""))
|
|
142
|
+
.filter((l) => l.trim().length > 0);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Enumerate files ADDED in the diff scope, as POSIX-form rel paths.
|
|
146
|
+
*
|
|
147
|
+
* - `staged`: `git diff --cached --diff-filter=A` (index vs HEAD).
|
|
148
|
+
* - `head`: files added vs HEAD (`git diff --diff-filter=A HEAD`) plus
|
|
149
|
+
* untracked-but-not-ignored files (`git ls-files --others`). When the repo
|
|
150
|
+
* has no commits yet, every tracked file counts as added.
|
|
151
|
+
*/
|
|
152
|
+
function addedFiles(projectRoot, mode) {
|
|
153
|
+
// Confirm we are inside a work tree; a non-repo path raises exit 2 upstream.
|
|
154
|
+
const inside = git(["rev-parse", "--is-inside-work-tree"], projectRoot);
|
|
155
|
+
if (inside.status !== 0) {
|
|
156
|
+
throw new GitCommandError("not a git working tree");
|
|
157
|
+
}
|
|
158
|
+
if (mode === "staged") {
|
|
159
|
+
return toLines(gitOrThrow(["diff", "--cached", "--name-only", "--diff-filter=A"], projectRoot));
|
|
160
|
+
}
|
|
161
|
+
const added = new Set();
|
|
162
|
+
const hasHead = git(["rev-parse", "--verify", "-q", "HEAD"], projectRoot).status === 0;
|
|
163
|
+
if (hasHead) {
|
|
164
|
+
for (const f of toLines(gitOrThrow(["diff", "--name-only", "--diff-filter=A", "HEAD"], projectRoot))) {
|
|
165
|
+
added.add(f);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
for (const f of toLines(gitOrThrow(["ls-files"], projectRoot))) {
|
|
170
|
+
added.add(f);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const f of toLines(gitOrThrow(["ls-files", "--others", "--exclude-standard"], projectRoot))) {
|
|
174
|
+
added.add(f);
|
|
175
|
+
}
|
|
176
|
+
return [...added];
|
|
177
|
+
}
|
|
178
|
+
function configError(message) {
|
|
179
|
+
return { exitCode: 2, missing: [], message };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Pure evaluation returning `{ exitCode, missing, message }`. Three-state exit
|
|
183
|
+
* (0 clean / 1 missing forward coverage / 2 config error). Mirrors the shape of
|
|
184
|
+
* `encoding.evaluate` (#798) so both gates read the same way.
|
|
185
|
+
*/
|
|
186
|
+
export function evaluateForwardCoverage(projectRoot, options = {}) {
|
|
187
|
+
const mode = options.mode ?? "head";
|
|
188
|
+
if (mode !== "head" && mode !== "staged") {
|
|
189
|
+
return configError(`verify_forward_coverage: unrecognised mode '${mode}' (expected 'head' or 'staged').`);
|
|
190
|
+
}
|
|
191
|
+
let allowGlobs;
|
|
192
|
+
try {
|
|
193
|
+
allowGlobs = loadAllowList(options.allowListPath);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
if (err instanceof AllowListNotFoundError) {
|
|
197
|
+
return configError(`verify_forward_coverage: --allow-list file not found: ${err.message}\n` +
|
|
198
|
+
" Recovery: pass an existing path or omit the flag.");
|
|
199
|
+
}
|
|
200
|
+
return configError(`verify_forward_coverage: --allow-list unreadable: ${String(err.message)}\n` +
|
|
201
|
+
" Recovery: check file permissions.");
|
|
202
|
+
}
|
|
203
|
+
let added;
|
|
204
|
+
try {
|
|
205
|
+
added = addedFiles(projectRoot, mode);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
if (err instanceof GitNotFoundError) {
|
|
209
|
+
return configError("verify_forward_coverage: 'git' executable not found on PATH.\n" +
|
|
210
|
+
" Recovery: install git or run inside a git working tree.");
|
|
211
|
+
}
|
|
212
|
+
if (err instanceof GitCommandError) {
|
|
213
|
+
return configError(`verify_forward_coverage: git failed -- ${err.message}\n` +
|
|
214
|
+
" Recovery: ensure --project-root points at a git working tree.");
|
|
215
|
+
}
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
const posixAdded = added.map((p) => p.replace(/\\/g, "/"));
|
|
219
|
+
const testBasenames = new Set(posixAdded.filter((p) => isTestFile(p)).map((p) => basename(p)));
|
|
220
|
+
const missing = [];
|
|
221
|
+
let checked = 0;
|
|
222
|
+
for (const rel of posixAdded) {
|
|
223
|
+
if (!isSourceFile(rel)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (isAllowListed(rel, allowGlobs)) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
checked += 1;
|
|
230
|
+
const expected = expectedTestBasenames(rel);
|
|
231
|
+
const covered = expected.some((name) => testBasenames.has(name));
|
|
232
|
+
if (!covered) {
|
|
233
|
+
missing.push({ path: rel, expectedTests: expected });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (missing.length > 0) {
|
|
237
|
+
const header = `verify_forward_coverage: ${missing.length} new source file(s) added without a ` +
|
|
238
|
+
"corresponding test in the same diff (#1310).\n" +
|
|
239
|
+
" Rule: a new source file MUST ship with a test in the SAME diff -- running existing\n" +
|
|
240
|
+
" tests alone does not satisfy forward coverage. Add a co-located test (e.g. <name>.test.ts,\n" +
|
|
241
|
+
" <name>_test.go, test_<name>.py) or allow-list a documented exception via --allow-list <path>.";
|
|
242
|
+
const body = missing
|
|
243
|
+
.map((m) => ` ${m.path}\n expected one of: ${m.expectedTests.join(", ")}`)
|
|
244
|
+
.join("\n");
|
|
245
|
+
return { exitCode: 1, missing, message: `${header}\n${body}` };
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
exitCode: 0,
|
|
249
|
+
missing,
|
|
250
|
+
message: `verify_forward_coverage: ${checked} new source file(s) checked -- ` +
|
|
251
|
+
"all have forward coverage in the diff (#1310).",
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=evaluate.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * as capacity from "./capacity/index.js";
|
|
|
15
15
|
export * as codebase from "./codebase/index.js";
|
|
16
16
|
export * as doctor from "./doctor/index.js";
|
|
17
17
|
export * from "./encoding/index.js";
|
|
18
|
+
export * from "./forward-coverage/evaluate.js";
|
|
18
19
|
export * as intake from "./intake/index.js";
|
|
19
20
|
export * as layout from "./layout/index.js";
|
|
20
21
|
export * as legacyBridge from "./legacy-bridge/index.js";
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * as capacity from "./capacity/index.js";
|
|
|
15
15
|
export * as codebase from "./codebase/index.js";
|
|
16
16
|
export * as doctor from "./doctor/index.js";
|
|
17
17
|
export * from "./encoding/index.js";
|
|
18
|
+
export * from "./forward-coverage/evaluate.js";
|
|
18
19
|
export * as intake from "./intake/index.js";
|
|
19
20
|
export * as layout from "./layout/index.js";
|
|
20
21
|
export * as legacyBridge from "./legacy-bridge/index.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutral leaf constants for the init-deposit surface.
|
|
3
|
+
*
|
|
4
|
+
* Kept dependency-free so both `scaffold.ts` and `hygiene.ts` can import the
|
|
5
|
+
* canonical install root without completing a module dependency cycle
|
|
6
|
+
* (scaffold needs hygiene's allowlist ERE; hygiene needs the install root).
|
|
7
|
+
*/
|
|
8
|
+
/** Canonical vendored framework install root, relative to the project root. */
|
|
9
|
+
export declare const CANONICAL_INSTALL_ROOT = ".deft/core";
|
|
10
|
+
/** Minimal output sink shared by the init-deposit surface. */
|
|
11
|
+
export interface InitDepositIo {
|
|
12
|
+
printf: (text: string) => void;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutral leaf constants for the init-deposit surface.
|
|
3
|
+
*
|
|
4
|
+
* Kept dependency-free so both `scaffold.ts` and `hygiene.ts` can import the
|
|
5
|
+
* canonical install root without completing a module dependency cycle
|
|
6
|
+
* (scaffold needs hygiene's allowlist ERE; hygiene needs the install root).
|
|
7
|
+
*/
|
|
8
|
+
/** Canonical vendored framework install root, relative to the project root. */
|
|
9
|
+
export const CANONICAL_INSTALL_ROOT = ".deft/core";
|
|
10
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoped staging + installer-managed allowlist for TS-native init/update (#1453).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors cmd/deft-install/hygiene.go + deposit.go installerManagedMatchers.
|
|
5
|
+
* Refs #1576, #1453, #1430.
|
|
6
|
+
*/
|
|
7
|
+
import { type InitDepositIo } from "./constants.js";
|
|
8
|
+
export declare const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
|
|
9
|
+
export declare const CORE_GUARD_WORKFLOW_REL = ".github/workflows/deft-core-guard.yml";
|
|
10
|
+
export interface InstallerManagedMatcher {
|
|
11
|
+
readonly exact?: string;
|
|
12
|
+
readonly prefix?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Single source of truth for installer-managed paths (#1440 / #1576). */
|
|
15
|
+
export declare function installerManagedMatchers(): InstallerManagedMatcher[];
|
|
16
|
+
/** POSIX ERE alternation embedded in the deposited deft-core-guard workflow. */
|
|
17
|
+
export declare function installerManagedGuardEre(): string;
|
|
18
|
+
export declare function isInstallerManagedPath(path: string): boolean;
|
|
19
|
+
export interface FrameworkStagePathsOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Include the project-root ``Taskfile.yml`` in the stage set. Defaults to
|
|
22
|
+
* ``false``: unlike the other allowlisted paths, ``Taskfile.yml`` is a
|
|
23
|
+
* consumer-owned file that merely *contains* an installer-managed include
|
|
24
|
+
* block, so it must only be staged when the installer actually wired that
|
|
25
|
+
* block this run -- otherwise an unrelated user edit would be silently
|
|
26
|
+
* ``git add``ed (#1576 review).
|
|
27
|
+
*/
|
|
28
|
+
readonly includeTaskfile?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare function frameworkStagePaths(projectDir: string, deftDir: string, options?: FrameworkStagePathsOptions): string[];
|
|
31
|
+
export interface StageFrameworkPathsSeams {
|
|
32
|
+
gitPorcelain?: (projectRoot: string) => string | null;
|
|
33
|
+
runGitAdd?: (projectDir: string, paths: readonly string[]) => void;
|
|
34
|
+
}
|
|
35
|
+
/** Best-effort scoped `git add` — never fails the install/update (#1453 Layer 2b). */
|
|
36
|
+
export declare function stageFrameworkPaths(projectDir: string, paths: readonly string[], seams?: StageFrameworkPathsSeams): {
|
|
37
|
+
staged: boolean;
|
|
38
|
+
error: Error | null;
|
|
39
|
+
};
|
|
40
|
+
export declare const COMMIT_HYGIENE_BRANCH_NAME = "chore/deft-framework-upgrade";
|
|
41
|
+
export declare function printCommitGuidance(io: InitDepositIo, paths: readonly string[], staged: boolean): void;
|
|
42
|
+
export interface DepositStagePathsOptions extends StageFrameworkPathsSeams, FrameworkStagePathsOptions {
|
|
43
|
+
readCachedNames?: (projectDir: string) => string[];
|
|
44
|
+
}
|
|
45
|
+
export declare function depositStagePaths(projectDir: string, options?: DepositStagePathsOptions): {
|
|
46
|
+
stagePaths: string[];
|
|
47
|
+
staged: boolean;
|
|
48
|
+
stagedPaths: string[];
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=hygiene.d.ts.map
|