@deftai/directive-core 0.72.0 → 0.73.1
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/branch/evaluate.js +2 -1
- package/dist/category-b-namespace/index.js +16 -2
- package/dist/codebase/default-extractor.js +9 -1
- package/dist/codebase/map.js +1 -1
- package/dist/deposit/copy-tree.js +31 -7
- package/dist/doctor/checks.d.ts +9 -0
- package/dist/doctor/checks.js +85 -5
- package/dist/doctor/main.js +2 -1
- package/dist/eval/health.js +7 -1
- package/dist/eval/readback.js +7 -1
- package/dist/init-deposit/gitignore.js +6 -0
- package/dist/init-deposit/hygiene.d.ts +30 -0
- package/dist/init-deposit/hygiene.js +60 -1
- package/dist/init-deposit/refresh.d.ts +16 -2
- package/dist/init-deposit/refresh.js +162 -52
- package/dist/init-deposit/scaffold.d.ts +11 -1
- package/dist/init-deposit/scaffold.js +9 -8
- package/dist/integration-e2e/helpers.js +7 -7
- package/dist/layout/index.d.ts +1 -1
- package/dist/layout/index.js +1 -1
- package/dist/layout/resolve.d.ts +47 -31
- package/dist/layout/resolve.js +93 -36
- package/dist/lifecycle/lifecycle-hygiene.d.ts +1 -1
- package/dist/lifecycle/lifecycle-hygiene.js +1 -1
- package/dist/migrate-preflight/index.js +10 -10
- package/dist/policy/disclosure.d.ts +1 -0
- package/dist/policy/disclosure.js +1 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/policy-invocation.d.ts +5 -0
- package/dist/policy/policy-invocation.js +9 -0
- package/dist/policy/resolve.d.ts +2 -2
- package/dist/policy/resolve.js +15 -5
- package/dist/policy/value-feedback.js +6 -3
- package/dist/policy/wip.d.ts +1 -1
- package/dist/policy/wip.js +15 -3
- package/dist/preflight-cache/evaluate.js +1 -1
- package/dist/release/native-steps.js +14 -2
- package/dist/render/export-spec.js +2 -2
- package/dist/render/project-render.js +40 -9
- package/dist/scope/decompose.js +12 -2
- package/dist/session/resume-conditions.js +2 -2
- package/dist/session/ritual-sentinel.d.ts +1 -1
- package/dist/session/ritual-sentinel.js +6 -4
- package/dist/slice/constants.d.ts +1 -1
- package/dist/slice/constants.js +1 -1
- package/dist/slice/project-context.js +1 -1
- package/dist/spec-authority/resolver.js +9 -2
- package/dist/swarm/complete-cohort.js +14 -1
- package/dist/swarm/constants.d.ts +1 -1
- package/dist/swarm/constants.js +2 -1
- package/dist/swarm/readiness.js +33 -3
- package/dist/swarm/subagent-backend.js +2 -1
- package/dist/task-surface/index.js +3 -3
- package/dist/triage/actions/candidates-log.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +22 -10
- package/dist/triage/bootstrap/index.d.ts +1 -1
- package/dist/triage/bootstrap/index.js +9 -3
- package/dist/triage/cache-path.d.ts +1 -1
- package/dist/triage/cache-path.js +32 -6
- package/dist/triage/classify/index.d.ts +1 -1
- package/dist/triage/classify/index.js +11 -4
- package/dist/triage/help/registry-data.d.ts +2 -2
- package/dist/triage/help/registry-data.js +2 -2
- package/dist/triage/queue/constants.d.ts +4 -4
- package/dist/triage/queue/constants.js +4 -4
- package/dist/triage/queue/scope-walk.js +7 -1
- package/dist/triage/reconcile/reconcile.js +23 -3
- package/dist/triage/reconcile/types.d.ts +1 -1
- package/dist/triage/reconcile/types.js +1 -1
- package/dist/triage/refresh/refresh.js +9 -2
- package/dist/triage/scope/constants.d.ts +2 -2
- package/dist/triage/scope/constants.js +2 -2
- package/dist/triage/scope/renderers.js +7 -1
- package/dist/triage/smoketest/index.js +9 -6
- package/dist/triage/subscribe/index.d.ts +2 -2
- package/dist/triage/subscribe/index.js +2 -2
- package/dist/triage/summary/index.d.ts +2 -2
- package/dist/triage/summary/index.js +1 -1
- package/dist/triage/summary/reconcilable.d.ts +1 -1
- package/dist/triage/welcome/constants.d.ts +2 -2
- package/dist/triage/welcome/constants.js +2 -2
- package/dist/triage/welcome/onboard.js +8 -1
- package/dist/triage/welcome/prior-state.js +14 -2
- package/dist/triage/welcome/summary.js +14 -2
- package/dist/triage/welcome/writers.js +7 -1
- package/dist/ts-check-lane/run-lane.d.ts +4 -0
- package/dist/ts-check-lane/run-lane.js +20 -6
- package/dist/validate-content/validate-strategy-output.js +11 -1
- package/dist/value/feedback-file.js +3 -2
- package/dist/value/readback.js +3 -2
- package/dist/vbrief-build/parity-scenarios.js +3 -3
- package/dist/vbrief-reconcile/labels.js +16 -1
- package/dist/vbrief-reconcile/parity-scenarios.js +10 -10
- package/dist/vbrief-reconcile/umbrellas.js +16 -1
- package/dist/vbrief-validate/main.js +13 -1
- package/dist/vbrief-validate/precutover.js +33 -5
- package/dist/vbrief-validate/staleness.d.ts +1 -1
- package/dist/vbrief-validate/staleness.js +5 -2
- package/dist/verify-source/code-structure-validate.js +7 -1
- package/dist/xbrief-migrate/signpost.d.ts +4 -4
- package/dist/xbrief-migrate/signpost.js +11 -15
- package/package.json +3 -3
package/dist/branch/evaluate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { recordBypassSignal, recordGateCatch } from "../events/attribution-ledger.js";
|
|
2
2
|
import { disclosureLine } from "../policy/disclosure.js";
|
|
3
|
+
import { policyColonInvocation } from "../policy/policy-invocation.js";
|
|
3
4
|
import { ENV_BYPASS, resolvePolicy } from "../policy/resolve.js";
|
|
4
5
|
import { currentBranch, GitNotFoundError } from "./git.js";
|
|
5
6
|
export const DEFAULT_BRANCHES = new Set(["master", "main"]);
|
|
@@ -32,7 +33,7 @@ function buildBlockMessage(branch, result) {
|
|
|
32
33
|
if (result.deprecationWarning !== null) {
|
|
33
34
|
parts.push(` Note: ${result.deprecationWarning}`);
|
|
34
35
|
}
|
|
35
|
-
parts.push("", " How to proceed:", " • Create a feature branch: git switch -c feat/<name>", " • Or opt out via the typed surface:", "
|
|
36
|
+
parts.push("", " How to proceed:", " • Create a feature branch: git switch -c feat/<name>", " • Or opt out via the typed surface:", ` ${policyColonInvocation("allow-direct-commits", " -- --confirm")}`, ` • Or set the emergency-escape env-var: ${ENV_BYPASS}=1`, "", " See README.md (Branch policy) and skills/deft-directive-setup/", " Phase 2 Step 9 (capability-cost disclosure).");
|
|
36
37
|
return parts.join("\n");
|
|
37
38
|
}
|
|
38
39
|
/**
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* order-preserving (the namespaced key takes the legacy key's slot, keeping
|
|
10
10
|
* artifact diffs minimal).
|
|
11
11
|
*/
|
|
12
|
-
import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync, } from "node:fs";
|
|
13
13
|
import { join, relative, resolve } from "node:path";
|
|
14
14
|
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
15
15
|
import { LEGACY_PLAN_COMPLETED_NOTE_KEY, LEGACY_PLAN_POLICY_KEY, PLAN_COMPLETED_NOTE_KEY, PLAN_POLICY_KEY, } from "../policy/plan-extensions.js";
|
|
@@ -85,7 +85,21 @@ function collectVbriefFiles(dir, acc = []) {
|
|
|
85
85
|
*/
|
|
86
86
|
export function migrateCategoryBCorpus(projectRoot) {
|
|
87
87
|
const root = resolve(projectRoot);
|
|
88
|
-
|
|
88
|
+
let vbriefDir;
|
|
89
|
+
try {
|
|
90
|
+
vbriefDir = resolveLifecycleRoot(root);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// If xbrief/ layout is absent, fall back to vbrief/ for this pre-migration tool
|
|
94
|
+
// (category-b migration rewrites policy keys regardless of layout stage).
|
|
95
|
+
const legacyDir = join(root, "vbrief");
|
|
96
|
+
if (existsSync(legacyDir)) {
|
|
97
|
+
vbriefDir = legacyDir;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return { scanned: 0, changed: [], conflicts: [] };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
89
103
|
let scanned = 0;
|
|
90
104
|
const changed = [];
|
|
91
105
|
const conflicts = [];
|
|
@@ -101,7 +101,15 @@ function sourcePathRel(projectRoot, sourcePath) {
|
|
|
101
101
|
}
|
|
102
102
|
/** Return the authored codeStructure source path used by the default extractor. */
|
|
103
103
|
export function defaultCodeStructurePath(projectRoot, codeStructurePath) {
|
|
104
|
-
|
|
104
|
+
if (codeStructurePath !== undefined)
|
|
105
|
+
return codeStructurePath;
|
|
106
|
+
try {
|
|
107
|
+
return resolveProjectDefinitionPath(projectRoot);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// No xbrief/ layout; return canonical path so callers get a predictable "not found" result.
|
|
111
|
+
return join(projectRoot, "xbrief", "PROJECT-DEFINITION.xbrief.json");
|
|
112
|
+
}
|
|
105
113
|
}
|
|
106
114
|
function stableId(value) {
|
|
107
115
|
let slug = "";
|
package/dist/codebase/map.js
CHANGED
|
@@ -10,7 +10,7 @@ export const GENERATED_SENTINEL = "AUTO-GENERATED by task codebase:map";
|
|
|
10
10
|
export const DEFAULT_OUTPUT_PATH = ".planning/codebase/MAP.md";
|
|
11
11
|
export const BANNER_TEMPLATE = "<!-- AUTO-GENERATED by task codebase:map -- DO NOT EDIT MANUALLY -->\n" +
|
|
12
12
|
"<!-- Purpose: generated codebase MAP projection -->\n" +
|
|
13
|
-
"<!-- Source of truth:
|
|
13
|
+
"<!-- Source of truth: xbrief/PROJECT-DEFINITION.xbrief.json " +
|
|
14
14
|
"plan.architecture.codeStructure -->\n" +
|
|
15
15
|
"<!-- Regenerate with: task codebase:map -->\n";
|
|
16
16
|
export function markdownText(value) {
|
|
@@ -7,12 +7,29 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Refs #1942 S1, #1477.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { constants } from "node:fs";
|
|
11
|
+
import { lstat, mkdir, open, readdir, readFile, stat } from "node:fs/promises";
|
|
12
12
|
import { dirname, join } from "node:path";
|
|
13
|
-
import { pipeline } from "node:stream/promises";
|
|
14
13
|
const DEFAULT_FILE_MODE = 0o644;
|
|
15
14
|
const DEFAULT_DIR_MODE = 0o755;
|
|
15
|
+
async function assertDestinationIsNotSymlink(path) {
|
|
16
|
+
try {
|
|
17
|
+
const info = await lstat(path);
|
|
18
|
+
if (info.isSymbolicLink()) {
|
|
19
|
+
throw new Error(`copyTree: refusing to write through destination symlink ${path}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
if (err instanceof Error && err.message.startsWith("copyTree: refusing")) {
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
if (err.code === "ENOENT") {
|
|
27
|
+
// Missing paths are created by the caller; only pre-existing symlinks matter.
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
16
33
|
async function copyFilePreserveMode(src, dst) {
|
|
17
34
|
let mode = DEFAULT_FILE_MODE;
|
|
18
35
|
try {
|
|
@@ -23,12 +40,19 @@ async function copyFilePreserveMode(src, dst) {
|
|
|
23
40
|
// Stat failure is non-fatal — fall back to 0o644 (mirrors Go copyFile).
|
|
24
41
|
}
|
|
25
42
|
await mkdir(dirname(dst), { recursive: true, mode: DEFAULT_DIR_MODE });
|
|
26
|
-
await
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
await assertDestinationIsNotSymlink(dst);
|
|
44
|
+
const handle = await open(dst, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, mode);
|
|
45
|
+
try {
|
|
46
|
+
await handle.writeFile(await readFile(src));
|
|
47
|
+
// Use the opened handle, not the path, so chmod cannot follow a swapped symlink.
|
|
48
|
+
await handle.chmod(mode);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
await handle.close();
|
|
52
|
+
}
|
|
30
53
|
}
|
|
31
54
|
async function copyDirContents(src, dst) {
|
|
55
|
+
await assertDestinationIsNotSymlink(dst);
|
|
32
56
|
await mkdir(dst, { recursive: true, mode: DEFAULT_DIR_MODE });
|
|
33
57
|
const entries = await readdir(src, { withFileTypes: true });
|
|
34
58
|
for (const entry of entries) {
|
package/dist/doctor/checks.d.ts
CHANGED
|
@@ -30,6 +30,15 @@ export declare function checkCanonicalVendoredNpmSignpost(projectRoot: string, s
|
|
|
30
30
|
* deposit is still a working install, just an unpinned one.
|
|
31
31
|
*/
|
|
32
32
|
export declare function checkManifestVersionReportable(projectRoot: string, installRoot: string | null, seams?: CheckSeams): CheckResult;
|
|
33
|
+
/**
|
|
34
|
+
* #2206: check that the consumer `.gitignore` carries the canonical Deft baseline
|
|
35
|
+
* entries. Advisory (exit-exempt) because missing entries are an adoption risk, not
|
|
36
|
+
* a broken install.
|
|
37
|
+
*
|
|
38
|
+
* Skips when `.gitignore` is absent (greenfield project that has not yet run
|
|
39
|
+
* `directive init` or `directive update`).
|
|
40
|
+
*/
|
|
41
|
+
export declare function checkGitignoreCoverage(projectRoot: string, seams?: CheckSeams): CheckResult;
|
|
33
42
|
export declare function deriveExitCode(checks: readonly CheckResult[], errors: readonly string[]): number;
|
|
34
43
|
export declare function runChecksImpl(projectRoot: string, seams?: CheckSeams & {
|
|
35
44
|
isDir?: (p: string) => boolean;
|
package/dist/doctor/checks.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
+
import { CANONICAL_GITIGNORE_BASELINE } from "../init-deposit/gitignore.js";
|
|
2
3
|
import { detectLegacyLayout, legacyLayoutSignpostLine, } from "../init-deposit/legacy-detect.js";
|
|
3
4
|
import { detectCanonicalVendoredManifest, isNpmManaged, NPM_MANAGED_SENTINEL_KEY, NPM_MANAGED_SENTINEL_VALUE, } from "../init-deposit/migrate.js";
|
|
4
5
|
import { resolveLifecycleRoot } from "../layout/resolve.js";
|
|
5
6
|
import { findSkillPathsInText } from "../text/redos-safe.js";
|
|
7
|
+
import { stripGitignoreInlineComment } from "../triage/bootstrap/gitignore.js";
|
|
6
8
|
import { CANONICAL_UPGRADE_COMMAND, GO_BRIDGE_RELEASES_URL, UPGRADING_DOC_URL, } from "./constants.js";
|
|
7
9
|
import { isDeprecationRedirectStub, locateManifest, manifestCandidatePaths, manifestReportableVersion, manifestTagToVersion, parseInstallManifest, parseInstallRootFromAgentsMd, parseManifest, } from "./manifest.js";
|
|
8
10
|
import { readTextSafe } from "./paths.js";
|
|
@@ -127,10 +129,14 @@ export function checkManifestAgreement(projectRoot, installRoot, seams = {}) {
|
|
|
127
129
|
const isFile = seams.isFile ?? ((p) => readText(p, seams) !== null);
|
|
128
130
|
const manifestPath = locateManifest(projectRoot, installRoot, isFile);
|
|
129
131
|
const expectedManifestPath = manifestPath ?? manifestCandidatePaths(projectRoot, installRoot)[0];
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
let layoutRoot;
|
|
133
|
+
try {
|
|
134
|
+
layoutRoot = resolveLifecycleRoot(projectRoot);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
layoutRoot = projectRoot; // No xbrief/ layout; fall back to project root for bare version check.
|
|
138
|
+
}
|
|
139
|
+
const bareCandidates = [join(layoutRoot, ".deft-version"), join(projectRoot, ".deft-version")];
|
|
134
140
|
const barePath = bareCandidates.find((p) => isFile(p)) ?? null;
|
|
135
141
|
const manifestText = manifestPath ? readText(manifestPath, seams) : null;
|
|
136
142
|
const bareText = barePath ? readText(barePath, seams) : null;
|
|
@@ -436,8 +442,80 @@ export function checkManifestVersionReportable(projectRoot, installRoot, seams =
|
|
|
436
442
|
data: { manifest_path: manifestPath, version: null, sha: null, source: reportable.source },
|
|
437
443
|
};
|
|
438
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* Collect the normalised (inline-comment-stripped) gitignore patterns from a
|
|
447
|
+
* `.gitignore` file text. Uses the shared `stripGitignoreInlineComment` helper
|
|
448
|
+
* so coverage checks stay consistent with `ensureInitGitignoreLines`.
|
|
449
|
+
*/
|
|
450
|
+
function collectGitignorePresent(text) {
|
|
451
|
+
const present = new Set();
|
|
452
|
+
for (const raw of text.split("\n")) {
|
|
453
|
+
const line = stripGitignoreInlineComment(raw);
|
|
454
|
+
if (line)
|
|
455
|
+
present.add(line);
|
|
456
|
+
}
|
|
457
|
+
return present;
|
|
458
|
+
}
|
|
459
|
+
function gitignoreLineIsCovered(present, line) {
|
|
460
|
+
return present.has(line);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* #2206: check that the consumer `.gitignore` carries the canonical Deft baseline
|
|
464
|
+
* entries. Advisory (exit-exempt) because missing entries are an adoption risk, not
|
|
465
|
+
* a broken install.
|
|
466
|
+
*
|
|
467
|
+
* Skips when `.gitignore` is absent (greenfield project that has not yet run
|
|
468
|
+
* `directive init` or `directive update`).
|
|
469
|
+
*/
|
|
470
|
+
export function checkGitignoreCoverage(projectRoot, seams = {}) {
|
|
471
|
+
const gitignorePath = join(projectRoot, ".gitignore");
|
|
472
|
+
const text = readText(gitignorePath, seams);
|
|
473
|
+
if (text === null) {
|
|
474
|
+
return {
|
|
475
|
+
name: "gitignore-coverage",
|
|
476
|
+
status: "skip",
|
|
477
|
+
detail: ".gitignore not found; run `directive init` or `directive update` to create it.",
|
|
478
|
+
data: { gitignore_path: gitignorePath, missing: [] },
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const present = collectGitignorePresent(text);
|
|
482
|
+
const missing = [];
|
|
483
|
+
for (const line of CANONICAL_GITIGNORE_BASELINE) {
|
|
484
|
+
if (!gitignoreLineIsCovered(present, line)) {
|
|
485
|
+
missing.push(line);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// Check .deft/core/ separately — only required for hybrid/greenfield installs.
|
|
489
|
+
// We conservatively omit it from the missing list here (tracked-deposit projects
|
|
490
|
+
// legitimately omit it). The update path handles this correctly per layout.
|
|
491
|
+
if (missing.length === 0) {
|
|
492
|
+
return {
|
|
493
|
+
name: "gitignore-coverage",
|
|
494
|
+
status: "pass",
|
|
495
|
+
detail: "All canonical Deft .gitignore entries are present.",
|
|
496
|
+
data: { gitignore_path: gitignorePath, missing: [] },
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
name: "gitignore-coverage",
|
|
501
|
+
status: "fail",
|
|
502
|
+
detail: `Deft .gitignore coverage incomplete: ${missing.length} canonical entr${missing.length === 1 ? "y" : "ies"} ` +
|
|
503
|
+
`missing (${missing.slice(0, 3).join(", ")}${missing.length > 3 ? ", …" : ""}). ` +
|
|
504
|
+
"Run `directive update` to add the missing entries (idempotent, safe on re-run). " +
|
|
505
|
+
`Full missing list: ${JSON.stringify(missing)}.`,
|
|
506
|
+
data: {
|
|
507
|
+
gitignore_path: gitignorePath,
|
|
508
|
+
missing,
|
|
509
|
+
suggested_fix: "directive update",
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
}
|
|
439
513
|
export function deriveExitCode(checks, errors) {
|
|
440
|
-
const exitExempt = new Set([
|
|
514
|
+
const exitExempt = new Set([
|
|
515
|
+
"canonical-vendored-npm-signpost",
|
|
516
|
+
"manifest-version-reportable",
|
|
517
|
+
"gitignore-coverage",
|
|
518
|
+
]);
|
|
441
519
|
if (errors.length > 0 || checks.some((c) => c.status === "error")) {
|
|
442
520
|
return 2;
|
|
443
521
|
}
|
|
@@ -476,6 +554,7 @@ export function runChecksImpl(projectRoot, seams = {}) {
|
|
|
476
554
|
checks.push(checkManifestVersionReportable(projectRoot, null, seams));
|
|
477
555
|
checks.push(checkLegacyLayout(projectRoot, seams));
|
|
478
556
|
checks.push(checkCanonicalVendoredNpmSignpost(projectRoot, seams));
|
|
557
|
+
checks.push(checkGitignoreCoverage(projectRoot, seams));
|
|
479
558
|
return {
|
|
480
559
|
projectRoot,
|
|
481
560
|
installRoot: null,
|
|
@@ -491,6 +570,7 @@ export function runChecksImpl(projectRoot, seams = {}) {
|
|
|
491
570
|
checks.push(checkInstallPathConsistency(projectRoot, installRoot, seams));
|
|
492
571
|
checks.push(checkLegacyLayout(projectRoot, seams));
|
|
493
572
|
checks.push(checkCanonicalVendoredNpmSignpost(projectRoot, seams));
|
|
573
|
+
checks.push(checkGitignoreCoverage(projectRoot, seams));
|
|
494
574
|
return {
|
|
495
575
|
projectRoot,
|
|
496
576
|
installRoot,
|
package/dist/doctor/main.js
CHANGED
|
@@ -373,7 +373,8 @@ function runInstallIntegrityChecks(projectRoot, sink, addFinding, seams) {
|
|
|
373
373
|
}
|
|
374
374
|
if ((name === "legacy-layout" ||
|
|
375
375
|
name === "canonical-vendored-npm-signpost" ||
|
|
376
|
-
name === "manifest-version-reportable"
|
|
376
|
+
name === "manifest-version-reportable" ||
|
|
377
|
+
name === "gitignore-coverage") &&
|
|
377
378
|
status === "fail") {
|
|
378
379
|
sink.warn(`${name}: ${detail}`);
|
|
379
380
|
addFinding({
|
package/dist/eval/health.js
CHANGED
|
@@ -21,7 +21,13 @@ function isFrameworkSourceCheckout(projectRoot) {
|
|
|
21
21
|
return existsSync(manifest) && existsSync(cliPkg);
|
|
22
22
|
}
|
|
23
23
|
function loadPlan(projectRoot) {
|
|
24
|
-
|
|
24
|
+
let filepath;
|
|
25
|
+
try {
|
|
26
|
+
filepath = resolveProjectDefinitionPath(projectRoot);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null; // No xbrief/ layout; no project definition.
|
|
30
|
+
}
|
|
25
31
|
if (!existsSync(filepath)) {
|
|
26
32
|
return null;
|
|
27
33
|
}
|
package/dist/eval/readback.js
CHANGED
|
@@ -28,7 +28,13 @@ function parseJsonObjectOrNull(text) {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
function readHealthHistoryRows(projectRoot) {
|
|
31
|
-
|
|
31
|
+
let path;
|
|
32
|
+
try {
|
|
33
|
+
path = healthHistoryPath(projectRoot);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return []; // No xbrief/ layout; no health history.
|
|
37
|
+
}
|
|
32
38
|
if (!existsSync(path)) {
|
|
33
39
|
return [];
|
|
34
40
|
}
|
|
@@ -44,7 +44,13 @@ export const CANONICAL_GITIGNORE_BASELINE = [
|
|
|
44
44
|
"vbrief/*.lock",
|
|
45
45
|
".deft/core.bak-*/",
|
|
46
46
|
".deft/*.bak-*",
|
|
47
|
+
// xBRIEF-era migration-backup directories created by `deft migrate:xbrief` (#2206).
|
|
48
|
+
".deft/xbrief-migrate-backup-*/",
|
|
47
49
|
"*.premigrate.*",
|
|
50
|
+
// Generated version-eval results live under `.eval/results/` (NOT triage-cache)
|
|
51
|
+
// for both the legacy `vbrief/` tree and the post-#2034 `xbrief/` tree (#2206).
|
|
52
|
+
"vbrief/.eval/results/",
|
|
53
|
+
"xbrief/.eval/results/",
|
|
48
54
|
];
|
|
49
55
|
const DEFT_FRAMEWORK_GITIGNORE_HEADER = "# Deft framework: ignore local-only caches and scratch directories\n";
|
|
50
56
|
const DEFT_CORE_GITIGNORE_RATIONALE = "# Hybrid deposit (#1942): reconstituted by `directive init` like node_modules.\n" +
|
|
@@ -17,6 +17,12 @@ export declare function installerManagedMatchers(): InstallerManagedMatcher[];
|
|
|
17
17
|
export declare function installerManagedGuardEre(): string;
|
|
18
18
|
export declare function isInstallerManagedPath(path: string): boolean;
|
|
19
19
|
export interface FrameworkStagePathsOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Include the vendored `.deft/core` payload in the stage set. Defaults to
|
|
22
|
+
* `true` for init and real payload swaps. No-op updates set this to `false`
|
|
23
|
+
* so CRLF-only core noise cannot be staged while safe projections are repaired.
|
|
24
|
+
*/
|
|
25
|
+
readonly includeCore?: boolean;
|
|
20
26
|
/**
|
|
21
27
|
* Include the project-root ``Taskfile.yml`` in the stage set. Defaults to
|
|
22
28
|
* ``false``: unlike the other allowlisted paths, ``Taskfile.yml`` is a
|
|
@@ -37,6 +43,30 @@ export declare function stageFrameworkPaths(projectDir: string, paths: readonly
|
|
|
37
43
|
staged: boolean;
|
|
38
44
|
error: Error | null;
|
|
39
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Framework-internal subdirectories that are shipped as part of the source
|
|
48
|
+
* repository but are NOT included in the `@deftai/directive-content` npm
|
|
49
|
+
* package deposited into `.deft/core/`. If one of these paths survives from
|
|
50
|
+
* a prior git-vendored install, `deft update`'s additive file-swap will never
|
|
51
|
+
* remove it — causing the deposit-hygiene advisory to persist indefinitely.
|
|
52
|
+
*
|
|
53
|
+
* Each entry is a relative path within `.deft/core/`; presence in the CONTENT
|
|
54
|
+
* root overrides the prune (i.e. if the content package ever ships `packages/`
|
|
55
|
+
* again, we won't prune it).
|
|
56
|
+
*
|
|
57
|
+
* Refs #2347.
|
|
58
|
+
*/
|
|
59
|
+
export declare const STRAY_DEPOSIT_FRAMEWORK_PATHS: readonly ["packages"];
|
|
60
|
+
export interface PruneStrayDepositPathsResult {
|
|
61
|
+
readonly pruned: string[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Remove framework-source subdirectories from `.deft/core/` that are not
|
|
65
|
+
* present in the deposited content package (#2347). Silently skips any path
|
|
66
|
+
* that is also present in `contentRoot` (future-safe: if the content package
|
|
67
|
+
* ever ships `packages/` we stop pruning it).
|
|
68
|
+
*/
|
|
69
|
+
export declare function pruneStrayDepositPaths(deftDir: string, contentRoot: string, io: InitDepositIo): Promise<PruneStrayDepositPathsResult>;
|
|
40
70
|
export declare const COMMIT_HYGIENE_BRANCH_NAME = "chore/deft-framework-upgrade";
|
|
41
71
|
export declare function printCommitGuidance(io: InitDepositIo, paths: readonly string[], staged: boolean): void;
|
|
42
72
|
export interface DepositStagePathsOptions extends StageFrameworkPathsSeams, FrameworkStagePathsOptions {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
|
+
import { rm, stat } from "node:fs/promises";
|
|
9
10
|
import { join, relative } from "node:path";
|
|
10
11
|
import { gitPorcelain } from "../story-ready/git.js";
|
|
11
12
|
import { CANONICAL_INSTALL_ROOT } from "./constants.js";
|
|
@@ -81,7 +82,10 @@ export function frameworkStagePaths(projectDir, deftDir, options = {}) {
|
|
|
81
82
|
paths.push(normalized);
|
|
82
83
|
};
|
|
83
84
|
const relDeft = relative(projectDir, deftDir);
|
|
84
|
-
if (
|
|
85
|
+
if (options.includeCore !== false &&
|
|
86
|
+
relDeft &&
|
|
87
|
+
!relDeft.startsWith("..") &&
|
|
88
|
+
!relDeft.startsWith("/")) {
|
|
85
89
|
add(relDeft);
|
|
86
90
|
}
|
|
87
91
|
for (const matcher of installerManagedMatchers()) {
|
|
@@ -120,6 +124,60 @@ export function stageFrameworkPaths(projectDir, paths, seams = {}) {
|
|
|
120
124
|
return { staged: false, error };
|
|
121
125
|
}
|
|
122
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Framework-internal subdirectories that are shipped as part of the source
|
|
129
|
+
* repository but are NOT included in the `@deftai/directive-content` npm
|
|
130
|
+
* package deposited into `.deft/core/`. If one of these paths survives from
|
|
131
|
+
* a prior git-vendored install, `deft update`'s additive file-swap will never
|
|
132
|
+
* remove it — causing the deposit-hygiene advisory to persist indefinitely.
|
|
133
|
+
*
|
|
134
|
+
* Each entry is a relative path within `.deft/core/`; presence in the CONTENT
|
|
135
|
+
* root overrides the prune (i.e. if the content package ever ships `packages/`
|
|
136
|
+
* again, we won't prune it).
|
|
137
|
+
*
|
|
138
|
+
* Refs #2347.
|
|
139
|
+
*/
|
|
140
|
+
export const STRAY_DEPOSIT_FRAMEWORK_PATHS = ["packages"];
|
|
141
|
+
/**
|
|
142
|
+
* Remove framework-source subdirectories from `.deft/core/` that are not
|
|
143
|
+
* present in the deposited content package (#2347). Silently skips any path
|
|
144
|
+
* that is also present in `contentRoot` (future-safe: if the content package
|
|
145
|
+
* ever ships `packages/` we stop pruning it).
|
|
146
|
+
*/
|
|
147
|
+
export async function pruneStrayDepositPaths(deftDir, contentRoot, io) {
|
|
148
|
+
const pruned = [];
|
|
149
|
+
for (const rel of STRAY_DEPOSIT_FRAMEWORK_PATHS) {
|
|
150
|
+
const depositPath = join(deftDir, rel);
|
|
151
|
+
const contentPath = join(contentRoot, rel);
|
|
152
|
+
let isInDeposit = false;
|
|
153
|
+
let isInContent = false;
|
|
154
|
+
try {
|
|
155
|
+
isInDeposit = (await stat(depositPath)).isDirectory();
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// absent — nothing to prune
|
|
159
|
+
}
|
|
160
|
+
if (!isInDeposit)
|
|
161
|
+
continue;
|
|
162
|
+
try {
|
|
163
|
+
isInContent = (await stat(contentPath)).isDirectory();
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// not in content package — safe to prune
|
|
167
|
+
}
|
|
168
|
+
if (isInContent)
|
|
169
|
+
continue;
|
|
170
|
+
try {
|
|
171
|
+
await rm(depositPath, { recursive: true, force: true });
|
|
172
|
+
pruned.push(rel);
|
|
173
|
+
io.printf(`Pruned stray framework-source tree .deft/core/${rel}/ — not shipped by @deftai/directive-content (#2347).\n`);
|
|
174
|
+
}
|
|
175
|
+
catch (cause) {
|
|
176
|
+
io.printf(`Warning: could not prune .deft/core/${rel}/: ${String(cause)}\n`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return { pruned };
|
|
180
|
+
}
|
|
123
181
|
export const COMMIT_HYGIENE_BRANCH_NAME = "chore/deft-framework-upgrade";
|
|
124
182
|
export function printCommitGuidance(io, paths, staged) {
|
|
125
183
|
if (paths.length === 0)
|
|
@@ -173,6 +231,7 @@ function actuallyStagedPaths(stagePaths, cachedNames) {
|
|
|
173
231
|
export function depositStagePaths(projectDir, options = {}) {
|
|
174
232
|
const deftDir = join(projectDir, CANONICAL_INSTALL_ROOT);
|
|
175
233
|
const stagePaths = frameworkStagePaths(projectDir, deftDir, {
|
|
234
|
+
includeCore: options.includeCore,
|
|
176
235
|
includeTaskfile: options.includeTaskfile ?? false,
|
|
177
236
|
});
|
|
178
237
|
const { staged } = stageFrameworkPaths(projectDir, stagePaths, options);
|
|
@@ -22,12 +22,15 @@ export interface RefreshDepositResult {
|
|
|
22
22
|
readonly contentVersion: string;
|
|
23
23
|
readonly engineVersion: string;
|
|
24
24
|
readonly previousDepositVersion: string | null;
|
|
25
|
+
readonly alreadyCurrent: boolean;
|
|
26
|
+
readonly strategy: RefreshDepositStrategy;
|
|
25
27
|
readonly agentsMdUpdated: boolean;
|
|
26
28
|
readonly versionSkewNotice: string | null;
|
|
27
29
|
readonly legacyLayout: boolean;
|
|
28
30
|
readonly taskfileWired: boolean;
|
|
29
31
|
readonly stagedPaths: string[];
|
|
30
32
|
}
|
|
33
|
+
export type RefreshDepositStrategy = "file-swap" | "no-op";
|
|
31
34
|
export interface RefreshDepositSeams {
|
|
32
35
|
resolveContentRoot?: () => Promise<string>;
|
|
33
36
|
copyContent?: (src: string, dst: string) => Promise<void>;
|
|
@@ -35,6 +38,7 @@ export interface RefreshDepositSeams {
|
|
|
35
38
|
readEngineVersion?: () => string;
|
|
36
39
|
nowIso?: () => string;
|
|
37
40
|
gitPorcelain?: (projectRoot: string) => string | null;
|
|
41
|
+
gitSemanticDiffNames?: GitSemanticDiffNames;
|
|
38
42
|
detectLegacy?: (projectDir: string) => LegacyLayoutDetection;
|
|
39
43
|
/**
|
|
40
44
|
* #2266: `git ls-files` probe threaded into the non-destructive `.gitignore`
|
|
@@ -101,9 +105,19 @@ export declare function buildDefaultLadderFacts(projectDir: string, facts: Resol
|
|
|
101
105
|
* manual npm/PATH steps (#2266 a3).
|
|
102
106
|
*/
|
|
103
107
|
export declare function selfHealEngine(projectDir: string, facts: ResolutionFacts, io: InitDepositIo, seams?: SelfHealSeams): EngineResolution;
|
|
108
|
+
export type GitSemanticDiffNames = (projectRoot: string, paths: readonly string[]) => readonly string[] | null;
|
|
109
|
+
export interface FrameworkRefreshSideEffectsOptions {
|
|
110
|
+
readonly readPorcelain?: (root: string) => string | null;
|
|
111
|
+
readonly readSemanticDiffNames?: GitSemanticDiffNames;
|
|
112
|
+
}
|
|
113
|
+
export interface RefreshSideEffects {
|
|
114
|
+
readonly files: string[];
|
|
115
|
+
readonly crlfOnlyCoreFiles: string[];
|
|
116
|
+
readonly payloadSwapped?: boolean;
|
|
117
|
+
}
|
|
104
118
|
/** Framework-managed uncommitted paths after refresh (#1671). */
|
|
105
|
-
export declare function frameworkRefreshSideEffects(projectDir: string,
|
|
106
|
-
export declare function printRefreshSideEffects(io: InitDepositIo,
|
|
119
|
+
export declare function frameworkRefreshSideEffects(projectDir: string, options?: FrameworkRefreshSideEffectsOptions): RefreshSideEffects;
|
|
120
|
+
export declare function printRefreshSideEffects(io: InitDepositIo, effects: RefreshSideEffects): void;
|
|
107
121
|
export declare function buildVersionSkewNotice(engineVersion: string, contentVersion: string, previousDepositVersion: string | null): string | null;
|
|
108
122
|
export declare function buildUpdateSummaryJson(result: RefreshDepositResult, options: RefreshDepositArgs, updateState?: UpdateState): Record<string, unknown>;
|
|
109
123
|
export declare function printUpdateComplete(result: RefreshDepositResult, io: InitDepositIo, updateState?: UpdateState): void;
|