@deftai/directive-core 0.55.1 → 0.56.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/cache/errors.d.ts +4 -1
- package/dist/cache/errors.js +8 -2
- package/dist/cache/fetch.d.ts +50 -0
- package/dist/cache/fetch.js +224 -3
- package/dist/cache/main.js +10 -5
- package/dist/cache/operations.js +6 -4
- package/dist/codebase/map-fresh.js +7 -1
- package/dist/deposit/copy-tree.d.ts +16 -0
- package/dist/deposit/copy-tree.js +58 -0
- package/dist/deposit/resolve-content.d.ts +28 -0
- package/dist/deposit/resolve-content.js +70 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +37 -0
- package/dist/doctor/constants.d.ts +2 -0
- package/dist/doctor/constants.js +8 -0
- package/dist/engine-version.d.ts +3 -0
- package/dist/engine-version.js +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/init-deposit/gitignore.d.ts +51 -0
- package/dist/init-deposit/gitignore.js +200 -0
- package/dist/init-deposit/index.d.ts +6 -0
- package/dist/init-deposit/index.js +6 -0
- package/dist/init-deposit/init-deposit.d.ts +44 -0
- package/dist/init-deposit/init-deposit.js +197 -0
- package/dist/init-deposit/legacy-detect.d.ts +61 -0
- package/dist/init-deposit/legacy-detect.js +223 -0
- package/dist/init-deposit/migrate.d.ts +88 -0
- package/dist/init-deposit/migrate.js +196 -0
- package/dist/init-deposit/refresh.d.ts +50 -0
- package/dist/init-deposit/refresh.js +292 -0
- package/dist/init-deposit/scaffold.d.ts +41 -0
- package/dist/init-deposit/scaffold.js +753 -0
- package/dist/legacy-bridge/bridge-drift.d.ts +70 -0
- package/dist/legacy-bridge/bridge-drift.js +171 -0
- package/dist/legacy-bridge/freeze-gate.d.ts +79 -0
- package/dist/legacy-bridge/freeze-gate.js +168 -0
- package/dist/legacy-bridge/index.d.ts +4 -0
- package/dist/legacy-bridge/index.js +4 -0
- package/dist/legacy-bridge/sot.d.ts +45 -0
- package/dist/legacy-bridge/sot.js +49 -0
- package/dist/preflight-cache/evaluate.d.ts +11 -0
- package/dist/preflight-cache/evaluate.js +63 -13
- package/dist/release-e2e/constants.d.ts +2 -0
- package/dist/release-e2e/constants.js +2 -0
- package/dist/release-e2e/flags.js +5 -1
- package/dist/release-e2e/legacy-bridge-leg.d.ts +134 -0
- package/dist/release-e2e/legacy-bridge-leg.js +460 -0
- package/dist/release-e2e/main.d.ts +2 -1
- package/dist/release-e2e/main.js +17 -1
- package/dist/release-e2e/npm-ops.d.ts +3 -1
- package/dist/release-e2e/npm-ops.js +5 -3
- package/dist/release-e2e/types.d.ts +9 -0
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/project-render.js +4 -3
- package/dist/render/roadmap-render.d.ts +4 -2
- package/dist/render/roadmap-render.js +14 -7
- package/dist/swarm/routing-verify.js +2 -2
- package/dist/swarm/subagent-backend.d.ts +16 -0
- package/dist/swarm/subagent-backend.js +14 -0
- package/dist/triage/actions/candidates-log.d.ts +12 -1
- package/dist/triage/actions/candidates-log.js +44 -7
- package/dist/triage/actions/index.d.ts +25 -1
- package/dist/triage/actions/index.js +94 -3
- package/dist/triage/summary/index.d.ts +1 -3
- package/dist/triage/summary/index.js +6 -57
- package/dist/triage/summary/reconcilable.js +4 -33
- package/dist/triage/welcome/default-mode.d.ts +1 -0
- package/dist/triage/welcome/default-mode.js +6 -0
- package/dist/vbrief-validate/project-definition.js +4 -69
- package/dist/vbrief-validate/registry-status.d.ts +20 -0
- package/dist/vbrief-validate/registry-status.js +85 -0
- package/package.json +11 -2
package/dist/doctor/checks.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
+
import { detectLegacyLayout, legacyLayoutSignpostLine, } from "../init-deposit/legacy-detect.js";
|
|
2
3
|
import { findSkillPathsInText } from "../text/redos-safe.js";
|
|
4
|
+
import { GO_BRIDGE_RELEASES_URL, UPGRADING_DOC_URL } from "./constants.js";
|
|
3
5
|
import { isDeprecationRedirectStub, locateManifest, manifestCandidatePaths, manifestTagToVersion, parseInstallRootFromAgentsMd, parseManifest, } from "./manifest.js";
|
|
4
6
|
import { readTextSafe } from "./paths.js";
|
|
5
7
|
function readText(path, seams) {
|
|
@@ -277,6 +279,39 @@ export function checkInstallPathConsistency(projectRoot, installRoot, seams = {}
|
|
|
277
279
|
},
|
|
278
280
|
};
|
|
279
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* #1912: signpost a legacy on-disk layout. Carries the STABLE UPGRADING.md URL
|
|
284
|
+
* only -- no baked Go-installer version or literal upgrade command. Returns
|
|
285
|
+
* `skip` for the canonical / greenfield layout (nothing to signpost).
|
|
286
|
+
*/
|
|
287
|
+
export function checkLegacyLayout(projectRoot, seams = {}) {
|
|
288
|
+
const legacySeams = {
|
|
289
|
+
...(seams.readText ? { readText: seams.readText } : {}),
|
|
290
|
+
...(seams.isFile ? { isFile: seams.isFile } : {}),
|
|
291
|
+
...(seams.isDir ? { isDir: seams.isDir } : {}),
|
|
292
|
+
};
|
|
293
|
+
const detection = detectLegacyLayout(projectRoot, legacySeams);
|
|
294
|
+
if (!detection.legacy) {
|
|
295
|
+
return {
|
|
296
|
+
name: "legacy-layout",
|
|
297
|
+
status: "skip",
|
|
298
|
+
detail: "No legacy Deft layout detected (canonical .deft/core/ or greenfield).",
|
|
299
|
+
data: { legacy_layout: false },
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
name: "legacy-layout",
|
|
304
|
+
status: "fail",
|
|
305
|
+
detail: legacyLayoutSignpostLine(detection),
|
|
306
|
+
data: {
|
|
307
|
+
legacy_layout: true,
|
|
308
|
+
legacy_layout_kind: detection.kind,
|
|
309
|
+
evidence: [...detection.evidence],
|
|
310
|
+
upgrading_doc_url: UPGRADING_DOC_URL,
|
|
311
|
+
go_bridge_releases_url: GO_BRIDGE_RELEASES_URL,
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
280
315
|
export function deriveExitCode(checks, errors) {
|
|
281
316
|
if (errors.length > 0 || checks.some((c) => c.status === "error")) {
|
|
282
317
|
return 2;
|
|
@@ -313,6 +348,7 @@ export function runChecksImpl(projectRoot, seams = {}) {
|
|
|
313
348
|
data: { agents_md_path: agentsMdPath },
|
|
314
349
|
});
|
|
315
350
|
checks.push(checkManifestAgreement(projectRoot, null, seams));
|
|
351
|
+
checks.push(checkLegacyLayout(projectRoot, seams));
|
|
316
352
|
return {
|
|
317
353
|
projectRoot,
|
|
318
354
|
installRoot: null,
|
|
@@ -325,6 +361,7 @@ export function runChecksImpl(projectRoot, seams = {}) {
|
|
|
325
361
|
checks.push(checkSkillPathsResolve(projectRoot, agentsMdText, seams));
|
|
326
362
|
checks.push(checkManifestAgreement(projectRoot, installRoot, seams));
|
|
327
363
|
checks.push(checkInstallPathConsistency(projectRoot, installRoot, seams));
|
|
364
|
+
checks.push(checkLegacyLayout(projectRoot, seams));
|
|
328
365
|
return {
|
|
329
366
|
projectRoot,
|
|
330
367
|
installRoot,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const UV_INSTALL_URL = "https://docs.astral.sh/uv/";
|
|
2
|
+
export declare const UPGRADING_DOC_URL = "https://github.com/deftai/directive/blob/master/content/UPGRADING.md";
|
|
3
|
+
export declare const GO_BRIDGE_RELEASES_URL = "https://github.com/deftai/directive/releases";
|
|
2
4
|
export declare const AGENTS_MANAGED_CLOSE = "<!-- /deft:managed-section -->";
|
|
3
5
|
export declare const DEPRECATED_REDIRECT_SENTINEL = "<!-- deft:deprecated-redirect -->";
|
|
4
6
|
export declare const DEPRECATED_SKILL_REDIRECT_SENTINEL = "<!-- deft:deprecated-skill-redirect -->";
|
package/dist/doctor/constants.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export const UV_INSTALL_URL = "https://docs.astral.sh/uv/";
|
|
2
|
+
// Stable, version-neutral upgrade signposts (#1912). Core principle: never bake
|
|
3
|
+
// the upgrade command/version into the artifact being upgraded -- bake in a
|
|
4
|
+
// stable pointer resolved fresh. These URLs carry NO Go-installer version and
|
|
5
|
+
// NO literal upgrade command; they point at the canonical docs + the frozen
|
|
6
|
+
// final Go bridge release so the npm CLI / doctor can signpost the
|
|
7
|
+
// legacy -> bridge -> npm recovery without going stale.
|
|
8
|
+
export const UPGRADING_DOC_URL = "https://github.com/deftai/directive/blob/master/content/UPGRADING.md";
|
|
9
|
+
export const GO_BRIDGE_RELEASES_URL = "https://github.com/deftai/directive/releases";
|
|
2
10
|
export const AGENTS_MANAGED_CLOSE = "<!-- /deft:managed-section -->";
|
|
3
11
|
export const DEPRECATED_REDIRECT_SENTINEL = "<!-- deft:deprecated-redirect -->";
|
|
4
12
|
export const DEPRECATED_SKILL_REDIRECT_SENTINEL = "<!-- deft:deprecated-skill-redirect -->";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const FALLBACK_VERSION = "0.0.0";
|
|
5
|
+
function parsePackageVersion(raw) {
|
|
6
|
+
const parsed = JSON.parse(raw);
|
|
7
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
8
|
+
return FALLBACK_VERSION;
|
|
9
|
+
}
|
|
10
|
+
const version = parsed.version;
|
|
11
|
+
return typeof version === "string" && version.length > 0 ? version : FALLBACK_VERSION;
|
|
12
|
+
}
|
|
13
|
+
/** Reads `@deftai/directive-core` version from the installed package.json adjacent to dist/ or src/. */
|
|
14
|
+
export function readCorePackageVersion() {
|
|
15
|
+
try {
|
|
16
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
17
|
+
return parsePackageVersion(readFileSync(pkgPath, "utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return FALLBACK_VERSION;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=engine-version.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * as codebase from "./codebase/index.js";
|
|
|
16
16
|
export * as doctor from "./doctor/index.js";
|
|
17
17
|
export * from "./encoding/index.js";
|
|
18
18
|
export * as intake from "./intake/index.js";
|
|
19
|
+
export * as legacyBridge from "./legacy-bridge/index.js";
|
|
19
20
|
export * as lifecycle from "./lifecycle/index.js";
|
|
20
21
|
export * as orchestration from "./orchestration/index.js";
|
|
21
22
|
export * as packs from "./packs/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readCorePackageVersion } from "./engine-version.js";
|
|
1
2
|
/**
|
|
2
3
|
* `@deftai/directive-core` — the deft directive engine core.
|
|
3
4
|
*
|
|
@@ -15,6 +16,7 @@ export * as codebase from "./codebase/index.js";
|
|
|
15
16
|
export * as doctor from "./doctor/index.js";
|
|
16
17
|
export * from "./encoding/index.js";
|
|
17
18
|
export * as intake from "./intake/index.js";
|
|
19
|
+
export * as legacyBridge from "./legacy-bridge/index.js";
|
|
18
20
|
export * as lifecycle from "./lifecycle/index.js";
|
|
19
21
|
export * as orchestration from "./orchestration/index.js";
|
|
20
22
|
export * as packs from "./packs/index.js";
|
|
@@ -50,6 +52,6 @@ export * as wipCap from "./wip-cap/index.js";
|
|
|
50
52
|
export const CORE_PACKAGE = "@deftai/directive-core";
|
|
51
53
|
/** Returns identifying metadata for the core engine package. */
|
|
52
54
|
export function engineInfo() {
|
|
53
|
-
return { name: CORE_PACKAGE, version:
|
|
55
|
+
return { name: CORE_PACKAGE, version: readCorePackageVersion() };
|
|
54
56
|
}
|
|
55
57
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Greenfield init `.gitignore` upkeep + deposit reconstitution (#1942 S4).
|
|
3
|
+
*
|
|
4
|
+
* Writes the canonical deft-install baseline (mirroring cmd/deft-install/setup.go
|
|
5
|
+
* EnsureGitignoreLines) and, for greenfield installs, appends `.deft/core/` so the
|
|
6
|
+
* deposit is born ignored (node_modules model). Existing tracked deposits are left
|
|
7
|
+
* alone — the vendored→hybrid un-commit is owned by #1941.
|
|
8
|
+
*
|
|
9
|
+
* Refs #1942, #1941, #1015, #1464, #1672.
|
|
10
|
+
*/
|
|
11
|
+
import type { InitDepositIo } from "./scaffold.js";
|
|
12
|
+
/** Directory ignore entry for the hybrid deposit (greenfield only). */
|
|
13
|
+
export declare const GITIGNORE_DEFT_CORE_LINE = ".deft/core/";
|
|
14
|
+
/**
|
|
15
|
+
* Canonical baseline mirrored from cmd/deft-install/setup.go::canonicalGitignoreLines
|
|
16
|
+
* (excluding `.deft/core/` — that line is greenfield-only per Option B / #1941 split).
|
|
17
|
+
*/
|
|
18
|
+
export declare const CANONICAL_GITIGNORE_BASELINE: readonly string[];
|
|
19
|
+
export interface EnsureInitGitignoreResult {
|
|
20
|
+
readonly changed: boolean;
|
|
21
|
+
readonly deftCoreIgnored: boolean;
|
|
22
|
+
readonly skippedDeftCoreBecauseTracked: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface ReconstituteDepositResult {
|
|
25
|
+
readonly reconstituted: boolean;
|
|
26
|
+
}
|
|
27
|
+
export type GitLsFiles = (projectDir: string, paths: readonly string[]) => string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Reports whether `.deft/core` is tracked in git. Returns `null` when git is
|
|
30
|
+
* unavailable or the tree is not a repository (treated as greenfield).
|
|
31
|
+
*/
|
|
32
|
+
export declare function isDepositTrackedInGit(projectDir: string, gitLsFiles?: GitLsFiles): boolean | null;
|
|
33
|
+
/** Build the canonical line set for this init, honoring the Option B journey split. */
|
|
34
|
+
export declare function resolveInitGitignoreLines(projectDir: string, gitLsFiles?: GitLsFiles): {
|
|
35
|
+
readonly lines: readonly string[];
|
|
36
|
+
readonly includeDeftCore: boolean;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Ensure the consumer `.gitignore` carries the canonical baseline plus, for
|
|
40
|
+
* greenfield installs, the `.deft/core/` ignore entry. Heals forbidden blanket
|
|
41
|
+
* `vbrief/.eval/` lines (#1464). Never un-commits a tracked deposit (#1941).
|
|
42
|
+
*/
|
|
43
|
+
export declare function ensureInitGitignoreLines(projectDir: string, io: InitDepositIo, options?: {
|
|
44
|
+
gitLsFiles?: GitLsFiles;
|
|
45
|
+
}): EnsureInitGitignoreResult;
|
|
46
|
+
/**
|
|
47
|
+
* Copy the content package into `.deft/core`, reporting whether the deposit was
|
|
48
|
+
* absent before copy (reconstitution). Always refreshes when present.
|
|
49
|
+
*/
|
|
50
|
+
export declare function reconstituteDepositFromContent(contentRoot: string, deftDir: string, copyContent: (src: string, dst: string) => Promise<void>): Promise<ReconstituteDepositResult>;
|
|
51
|
+
//# sourceMappingURL=gitignore.d.ts.map
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Greenfield init `.gitignore` upkeep + deposit reconstitution (#1942 S4).
|
|
3
|
+
*
|
|
4
|
+
* Writes the canonical deft-install baseline (mirroring cmd/deft-install/setup.go
|
|
5
|
+
* EnsureGitignoreLines) and, for greenfield installs, appends `.deft/core/` so the
|
|
6
|
+
* deposit is born ignored (node_modules model). Existing tracked deposits are left
|
|
7
|
+
* alone — the vendored→hybrid un-commit is owned by #1941.
|
|
8
|
+
*
|
|
9
|
+
* Refs #1942, #1941, #1015, #1464, #1672.
|
|
10
|
+
*/
|
|
11
|
+
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { FORBIDDEN_BLANKET_EVAL_LINES, stripGitignoreInlineComment, } from "../triage/bootstrap/gitignore.js";
|
|
15
|
+
/** Directory ignore entry for the hybrid deposit (greenfield only). */
|
|
16
|
+
export const GITIGNORE_DEFT_CORE_LINE = ".deft/core/";
|
|
17
|
+
/** Alternate spellings that already cover the deposit ignore entry. */
|
|
18
|
+
const DEFT_CORE_COVERING_LINES = new Set([".deft/core/", ".deft/core"]);
|
|
19
|
+
/**
|
|
20
|
+
* Canonical baseline mirrored from cmd/deft-install/setup.go::canonicalGitignoreLines
|
|
21
|
+
* (excluding `.deft/core/` — that line is greenfield-only per Option B / #1941 split).
|
|
22
|
+
*/
|
|
23
|
+
export const CANONICAL_GITIGNORE_BASELINE = [
|
|
24
|
+
".deft-cache/",
|
|
25
|
+
".deft/ritual-state.json",
|
|
26
|
+
".deft/last-session.json",
|
|
27
|
+
".deft/routing.local.json",
|
|
28
|
+
"vbrief/.eval/candidates.jsonl",
|
|
29
|
+
"vbrief/.eval/summary-history.jsonl",
|
|
30
|
+
"vbrief/.eval/scope-lifecycle.jsonl",
|
|
31
|
+
"vbrief/.eval/decompositions/",
|
|
32
|
+
"vbrief/.eval/doctor-state.json",
|
|
33
|
+
"vbrief/*.lock",
|
|
34
|
+
".deft/core.bak-*/",
|
|
35
|
+
".deft/*.bak-*",
|
|
36
|
+
"*.premigrate.*",
|
|
37
|
+
];
|
|
38
|
+
const DEFT_FRAMEWORK_GITIGNORE_HEADER = "# Deft framework: ignore local-only caches and scratch directories\n";
|
|
39
|
+
const DEFT_CORE_GITIGNORE_RATIONALE = "# Hybrid deposit (#1942): reconstituted by `directive init` like node_modules.\n" +
|
|
40
|
+
"# The vendored→hybrid un-commit for existing tracked deposits is #1941.\n";
|
|
41
|
+
function defaultGitLsFiles(projectDir, paths) {
|
|
42
|
+
try {
|
|
43
|
+
return execFileSync("git", ["ls-files", "--", ...paths], {
|
|
44
|
+
cwd: projectDir,
|
|
45
|
+
encoding: "utf8",
|
|
46
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
const code = err.code;
|
|
51
|
+
if (code === "ENOENT")
|
|
52
|
+
return null;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function isForbiddenBlanketEvalLine(line) {
|
|
57
|
+
return FORBIDDEN_BLANKET_EVAL_LINES.includes(line);
|
|
58
|
+
}
|
|
59
|
+
function gitignoreCoversLine(present, line) {
|
|
60
|
+
if (present.has(line))
|
|
61
|
+
return true;
|
|
62
|
+
if (line === GITIGNORE_DEFT_CORE_LINE) {
|
|
63
|
+
return [...DEFT_CORE_COVERING_LINES].some((candidate) => present.has(candidate));
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
function collectPresentGitignoreLines(existing) {
|
|
68
|
+
const present = new Set();
|
|
69
|
+
for (const raw of existing.split("\n")) {
|
|
70
|
+
const stripped = stripGitignoreInlineComment(raw);
|
|
71
|
+
if (stripped)
|
|
72
|
+
present.add(stripped);
|
|
73
|
+
}
|
|
74
|
+
return present;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Reports whether `.deft/core` is tracked in git. Returns `null` when git is
|
|
78
|
+
* unavailable or the tree is not a repository (treated as greenfield).
|
|
79
|
+
*/
|
|
80
|
+
export function isDepositTrackedInGit(projectDir, gitLsFiles = defaultGitLsFiles) {
|
|
81
|
+
const tracked = gitLsFiles(projectDir, [".deft/core", ".deft/core/"]);
|
|
82
|
+
if (tracked === null)
|
|
83
|
+
return null;
|
|
84
|
+
return tracked.trim().length > 0;
|
|
85
|
+
}
|
|
86
|
+
/** Build the canonical line set for this init, honoring the Option B journey split. */
|
|
87
|
+
export function resolveInitGitignoreLines(projectDir, gitLsFiles = defaultGitLsFiles) {
|
|
88
|
+
const tracked = isDepositTrackedInGit(projectDir, gitLsFiles);
|
|
89
|
+
const includeDeftCore = tracked !== true;
|
|
90
|
+
return {
|
|
91
|
+
lines: includeDeftCore
|
|
92
|
+
? [...CANONICAL_GITIGNORE_BASELINE, GITIGNORE_DEFT_CORE_LINE]
|
|
93
|
+
: CANONICAL_GITIGNORE_BASELINE,
|
|
94
|
+
includeDeftCore,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Ensure the consumer `.gitignore` carries the canonical baseline plus, for
|
|
99
|
+
* greenfield installs, the `.deft/core/` ignore entry. Heals forbidden blanket
|
|
100
|
+
* `vbrief/.eval/` lines (#1464). Never un-commits a tracked deposit (#1941).
|
|
101
|
+
*/
|
|
102
|
+
export function ensureInitGitignoreLines(projectDir, io, options = {}) {
|
|
103
|
+
const gitLsFiles = options.gitLsFiles ?? defaultGitLsFiles;
|
|
104
|
+
const { lines: targetLines, includeDeftCore } = resolveInitGitignoreLines(projectDir, gitLsFiles);
|
|
105
|
+
const tracked = isDepositTrackedInGit(projectDir, gitLsFiles);
|
|
106
|
+
const path = join(projectDir, ".gitignore");
|
|
107
|
+
let existing = "";
|
|
108
|
+
if (existsSync(path)) {
|
|
109
|
+
try {
|
|
110
|
+
existing = readFileSync(path, { encoding: "utf8" });
|
|
111
|
+
}
|
|
112
|
+
catch (cause) {
|
|
113
|
+
throw new Error(`could not read .gitignore: ${String(cause)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let rawLines = existing.split("\n");
|
|
117
|
+
let trailingNewline = false;
|
|
118
|
+
if (existing.endsWith("\n") && rawLines.length > 0 && rawLines[rawLines.length - 1] === "") {
|
|
119
|
+
trailingNewline = true;
|
|
120
|
+
rawLines = rawLines.slice(0, -1);
|
|
121
|
+
}
|
|
122
|
+
const kept = [];
|
|
123
|
+
let blanketRemoved = false;
|
|
124
|
+
const present = new Set();
|
|
125
|
+
for (const raw of rawLines) {
|
|
126
|
+
const stripped = stripGitignoreInlineComment(raw);
|
|
127
|
+
if (isForbiddenBlanketEvalLine(stripped)) {
|
|
128
|
+
blanketRemoved = true;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
kept.push(raw);
|
|
132
|
+
if (stripped)
|
|
133
|
+
present.add(stripped);
|
|
134
|
+
}
|
|
135
|
+
const additions = [];
|
|
136
|
+
for (const line of targetLines) {
|
|
137
|
+
if (!gitignoreCoversLine(present, line)) {
|
|
138
|
+
additions.push(line);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!blanketRemoved && additions.length === 0) {
|
|
142
|
+
io.printf(".gitignore already covers the canonical deft entries — skipping.\n");
|
|
143
|
+
return {
|
|
144
|
+
changed: false,
|
|
145
|
+
deftCoreIgnored: gitignoreCoversLine(present, GITIGNORE_DEFT_CORE_LINE),
|
|
146
|
+
skippedDeftCoreBecauseTracked: tracked === true,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
let healed = kept.join("\n");
|
|
150
|
+
if (kept.length > 0 && trailingNewline) {
|
|
151
|
+
healed += "\n";
|
|
152
|
+
}
|
|
153
|
+
let body = healed;
|
|
154
|
+
if (additions.length > 0) {
|
|
155
|
+
if (healed !== "" && !healed.endsWith("\n")) {
|
|
156
|
+
body += "\n";
|
|
157
|
+
}
|
|
158
|
+
if (healed !== "" && !healed.endsWith("\n\n")) {
|
|
159
|
+
body += "\n";
|
|
160
|
+
}
|
|
161
|
+
body += DEFT_FRAMEWORK_GITIGNORE_HEADER;
|
|
162
|
+
if (includeDeftCore && additions.includes(GITIGNORE_DEFT_CORE_LINE)) {
|
|
163
|
+
body += DEFT_CORE_GITIGNORE_RATIONALE;
|
|
164
|
+
}
|
|
165
|
+
for (const add of additions) {
|
|
166
|
+
body += `${add}\n`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
writeFileSync(path, body, { encoding: "utf8", mode: 0o644 });
|
|
171
|
+
}
|
|
172
|
+
catch (cause) {
|
|
173
|
+
throw new Error(`could not write .gitignore: ${String(cause)}`);
|
|
174
|
+
}
|
|
175
|
+
if (additions.length > 0) {
|
|
176
|
+
io.printf(`.gitignore updated with canonical entries: ${additions.join(", ")}\n`);
|
|
177
|
+
}
|
|
178
|
+
if (blanketRemoved) {
|
|
179
|
+
io.printf(".gitignore healed: removed forbidden blanket vbrief/.eval/ line (#1464).\n");
|
|
180
|
+
}
|
|
181
|
+
if (tracked === true) {
|
|
182
|
+
io.printf(".deft/core is tracked in git — leaving it tracked; vendored→hybrid un-commit is #1941.\n");
|
|
183
|
+
}
|
|
184
|
+
const finalPresent = collectPresentGitignoreLines(body);
|
|
185
|
+
return {
|
|
186
|
+
changed: true,
|
|
187
|
+
deftCoreIgnored: gitignoreCoversLine(finalPresent, GITIGNORE_DEFT_CORE_LINE),
|
|
188
|
+
skippedDeftCoreBecauseTracked: tracked === true,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Copy the content package into `.deft/core`, reporting whether the deposit was
|
|
193
|
+
* absent before copy (reconstitution). Always refreshes when present.
|
|
194
|
+
*/
|
|
195
|
+
export async function reconstituteDepositFromContent(contentRoot, deftDir, copyContent) {
|
|
196
|
+
const wasAbsent = !existsSync(deftDir);
|
|
197
|
+
await copyContent(contentRoot, deftDir);
|
|
198
|
+
return { reconstituted: wasAbsent };
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=gitignore.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TS-native greenfield init deposit orchestrator (#1942 S2).
|
|
3
|
+
*
|
|
4
|
+
* Composes the S1 resolve-and-copy primitive with AGENTS.md render, vbrief
|
|
5
|
+
* scaffold, skills/githooks/#1430 neutralization, and Taskfile wiring.
|
|
6
|
+
* Refs #1942, #11, #1430.
|
|
7
|
+
*/
|
|
8
|
+
import { type LegacyLayoutDetection } from "./legacy-detect.js";
|
|
9
|
+
import { type InitDepositIo, writeConsumerGitHooks } from "./scaffold.js";
|
|
10
|
+
export interface InitDepositArgs {
|
|
11
|
+
readonly projectDir: string;
|
|
12
|
+
readonly jsonOut: boolean;
|
|
13
|
+
readonly nonInteractive: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface InitDepositResult {
|
|
16
|
+
readonly projectDir: string;
|
|
17
|
+
readonly deftDir: string;
|
|
18
|
+
readonly skillsCreated: boolean;
|
|
19
|
+
readonly taskfileWired: boolean;
|
|
20
|
+
readonly configDir: string;
|
|
21
|
+
readonly legacyLayout: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface InitDepositSeams {
|
|
24
|
+
resolveContentRoot?: () => Promise<string>;
|
|
25
|
+
copyContent?: (src: string, dst: string) => Promise<void>;
|
|
26
|
+
readPackageVersion?: () => string;
|
|
27
|
+
nowIso?: () => string;
|
|
28
|
+
gitHooks?: Parameters<typeof writeConsumerGitHooks>[3];
|
|
29
|
+
detectLegacy?: (projectDir: string) => LegacyLayoutDetection;
|
|
30
|
+
}
|
|
31
|
+
export declare function parseInitArgv(canonicalArgv: readonly string[], userArgv?: readonly string[]): InitDepositArgs;
|
|
32
|
+
export declare function userConfigDir(): string;
|
|
33
|
+
export declare function createUserConfigDir(io: InitDepositIo): string;
|
|
34
|
+
export declare function buildInstallSummaryJson(result: InitDepositResult, options: InitDepositArgs): Record<string, unknown>;
|
|
35
|
+
export declare function printNextSteps(result: InitDepositResult, io: InitDepositIo): void;
|
|
36
|
+
export declare function runInitDeposit(args: InitDepositArgs, io: InitDepositIo, seams?: InitDepositSeams): Promise<InitDepositResult>;
|
|
37
|
+
export interface RunInitDepositCliOptions extends InitDepositArgs {
|
|
38
|
+
readonly writeOut: (text: string) => void;
|
|
39
|
+
readonly writeErr: (text: string) => void;
|
|
40
|
+
readonly seams?: InitDepositSeams;
|
|
41
|
+
}
|
|
42
|
+
/** CLI-facing wrapper: runs deposit, emits JSON or wizard UX, returns exit code. */
|
|
43
|
+
export declare function runInitDepositCli(options: RunInitDepositCliOptions): Promise<number>;
|
|
44
|
+
//# sourceMappingURL=init-deposit.d.ts.map
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TS-native greenfield init deposit orchestrator (#1942 S2).
|
|
3
|
+
*
|
|
4
|
+
* Composes the S1 resolve-and-copy primitive with AGENTS.md render, vbrief
|
|
5
|
+
* scaffold, skills/githooks/#1430 neutralization, and Taskfile wiring.
|
|
6
|
+
* Refs #1942, #11, #1430.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
9
|
+
import { homedir, platform } from "node:os";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import { copyTree } from "../deposit/copy-tree.js";
|
|
12
|
+
import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
|
|
13
|
+
import { readCorePackageVersion } from "../engine-version.js";
|
|
14
|
+
import { ensureInitGitignoreLines, reconstituteDepositFromContent } from "./gitignore.js";
|
|
15
|
+
import { buildLegacyRefusalJson, buildLegacyRefusalMessage, detectLegacyLayout, LEGACY_LAYOUT_REFUSED_EXIT_CODE, LegacyLayoutRefusedError, } from "./legacy-detect.js";
|
|
16
|
+
import { CANONICAL_INSTALL_ROOT, depositNeutralization, ensureTaskfile, writeAgentsMd, writeAgentsSkills, writeConsumerGitHooks, writeConsumerVbrief, writeInstallManifest, } from "./scaffold.js";
|
|
17
|
+
export function parseInitArgv(canonicalArgv, userArgv = []) {
|
|
18
|
+
const args = [...canonicalArgv, ...userArgv];
|
|
19
|
+
let projectDir = process.cwd();
|
|
20
|
+
let jsonOut = false;
|
|
21
|
+
let nonInteractive = false;
|
|
22
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
23
|
+
const arg = args[i];
|
|
24
|
+
if (arg === "--json" || arg === "/json")
|
|
25
|
+
jsonOut = true;
|
|
26
|
+
if (arg === "--yes" ||
|
|
27
|
+
arg === "--non-interactive" ||
|
|
28
|
+
arg === "/yes" ||
|
|
29
|
+
arg === "/non-interactive") {
|
|
30
|
+
nonInteractive = true;
|
|
31
|
+
}
|
|
32
|
+
if ((arg === "--repo-root" || arg === "/repo-root") && args[i + 1]) {
|
|
33
|
+
projectDir = resolve(args[i + 1] ?? projectDir);
|
|
34
|
+
i += 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { projectDir: resolve(projectDir), jsonOut, nonInteractive };
|
|
38
|
+
}
|
|
39
|
+
export function userConfigDir() {
|
|
40
|
+
const override = process.env.DEFT_USER_PATH?.trim();
|
|
41
|
+
if (override)
|
|
42
|
+
return resolve(override);
|
|
43
|
+
if (platform() === "win32") {
|
|
44
|
+
const appData = process.env.APPDATA?.trim();
|
|
45
|
+
return appData ? join(appData, "deft") : join(homedir(), "AppData", "Roaming", "deft");
|
|
46
|
+
}
|
|
47
|
+
return join(homedir(), ".config", "deft");
|
|
48
|
+
}
|
|
49
|
+
export function createUserConfigDir(io) {
|
|
50
|
+
const dir = userConfigDir();
|
|
51
|
+
mkdirSync(dir, { recursive: true });
|
|
52
|
+
const userMd = join(dir, "USER.md");
|
|
53
|
+
if (existsSync(userMd)) {
|
|
54
|
+
io.printf(`USER.md already exists at ${userMd} — keeping existing file.\n`);
|
|
55
|
+
}
|
|
56
|
+
return dir;
|
|
57
|
+
}
|
|
58
|
+
function readContentVersion(contentRoot, readVersion = readCorePackageVersion) {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(readFileSync(join(contentRoot, "package.json"), "utf8"));
|
|
61
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
62
|
+
const version = parsed.version;
|
|
63
|
+
if (version?.trim())
|
|
64
|
+
return version.trim();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// fall through
|
|
69
|
+
}
|
|
70
|
+
return readVersion();
|
|
71
|
+
}
|
|
72
|
+
export function buildInstallSummaryJson(result, options) {
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
action: "install",
|
|
76
|
+
version: readCorePackageVersion(),
|
|
77
|
+
project_dir: result.projectDir,
|
|
78
|
+
deft_dir: result.deftDir,
|
|
79
|
+
legacy_layout: result.legacyLayout,
|
|
80
|
+
update: false,
|
|
81
|
+
non_interactive: options.nonInteractive,
|
|
82
|
+
upgrade: false,
|
|
83
|
+
taskfile_wired: result.taskfileWired,
|
|
84
|
+
missing_tools: [],
|
|
85
|
+
maintainer_mode: false,
|
|
86
|
+
maintainer_tools: [],
|
|
87
|
+
skipped_consumer_projections: [],
|
|
88
|
+
user_config_dir: result.configDir,
|
|
89
|
+
skills_created: result.skillsCreated,
|
|
90
|
+
payload_layout: "vendored",
|
|
91
|
+
strategy: "vendor",
|
|
92
|
+
dirty_tree: false,
|
|
93
|
+
dirty_files: [],
|
|
94
|
+
staged_paths: [],
|
|
95
|
+
backup_path: "",
|
|
96
|
+
previous_version: "",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export function printNextSteps(result, io) {
|
|
100
|
+
const skillsStatus = result.skillsCreated ? "created" : "already present";
|
|
101
|
+
io.printf("\n✓ Deft installed successfully!\n\n");
|
|
102
|
+
io.printf(` Location : ${result.deftDir}\n`);
|
|
103
|
+
io.printf(" AGENTS.md : updated\n");
|
|
104
|
+
io.printf(` Skills : .agents/skills/ ${skillsStatus} (auto-discovered by AI agents)\n`);
|
|
105
|
+
io.printf(` User config : ${result.configDir}\n`);
|
|
106
|
+
io.printf("\nNext steps:\n");
|
|
107
|
+
io.printf(` 1. Open your AI coding assistant in ${result.projectDir}\n`);
|
|
108
|
+
io.printf(" 2. Deft skill auto-discovery is partially implemented — if your agent doesn't\n");
|
|
109
|
+
io.printf(' start setup automatically, tell it: "Use AGENTS.md"\n');
|
|
110
|
+
io.printf(" 3. On first session, the agent will guide you through creating USER.md and PROJECT-DEFINITION.vbrief.json\n");
|
|
111
|
+
io.printf("\n");
|
|
112
|
+
}
|
|
113
|
+
export async function runInitDeposit(args, io, seams = {}) {
|
|
114
|
+
const projectDir = args.projectDir;
|
|
115
|
+
const deftDir = join(projectDir, CANONICAL_INSTALL_ROOT);
|
|
116
|
+
// #1912: refuse a legacy on-disk layout BEFORE any deposit. The npm CLI never
|
|
117
|
+
// migrates -- the frozen Go bridge does (stage 1), then the npm path (stage 2).
|
|
118
|
+
const detectLegacy = seams.detectLegacy ?? detectLegacyLayout;
|
|
119
|
+
const legacy = detectLegacy(projectDir);
|
|
120
|
+
if (legacy.legacy) {
|
|
121
|
+
throw new LegacyLayoutRefusedError(legacy);
|
|
122
|
+
}
|
|
123
|
+
const resolveContent = seams.resolveContentRoot ?? resolveInstalledContentRoot;
|
|
124
|
+
const copyContent = seams.copyContent ?? copyTree;
|
|
125
|
+
const contentRoot = await resolveContent();
|
|
126
|
+
await reconstituteDepositFromContent(contentRoot, deftDir, copyContent);
|
|
127
|
+
ensureInitGitignoreLines(projectDir, io);
|
|
128
|
+
const nowIso = seams.nowIso ?? (() => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"));
|
|
129
|
+
const version = readContentVersion(contentRoot, seams.readPackageVersion ?? readCorePackageVersion);
|
|
130
|
+
const manifestFields = {
|
|
131
|
+
ref: version.startsWith("v") ? version : `v${version}`,
|
|
132
|
+
sha: "content-package",
|
|
133
|
+
tag: version.startsWith("v") ? version : `v${version}`,
|
|
134
|
+
installRoot: CANONICAL_INSTALL_ROOT,
|
|
135
|
+
fetchedAt: nowIso(),
|
|
136
|
+
fetchedBy: "directive-init",
|
|
137
|
+
};
|
|
138
|
+
writeInstallManifest(projectDir, deftDir, manifestFields);
|
|
139
|
+
writeAgentsMd(projectDir, deftDir, io);
|
|
140
|
+
const skillsCreated = writeAgentsSkills(projectDir, io);
|
|
141
|
+
await depositNeutralization(projectDir, io);
|
|
142
|
+
await writeConsumerVbrief(projectDir, deftDir, io);
|
|
143
|
+
writeConsumerGitHooks(projectDir, deftDir, io, seams.gitHooks);
|
|
144
|
+
let taskfileWired = false;
|
|
145
|
+
if (args.nonInteractive) {
|
|
146
|
+
taskfileWired = ensureTaskfile(projectDir, io);
|
|
147
|
+
}
|
|
148
|
+
const configDir = createUserConfigDir(io);
|
|
149
|
+
return {
|
|
150
|
+
projectDir,
|
|
151
|
+
deftDir,
|
|
152
|
+
skillsCreated,
|
|
153
|
+
taskfileWired,
|
|
154
|
+
configDir,
|
|
155
|
+
legacyLayout: false,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** CLI-facing wrapper: runs deposit, emits JSON or wizard UX, returns exit code. */
|
|
159
|
+
export async function runInitDepositCli(options) {
|
|
160
|
+
const io = {
|
|
161
|
+
printf: (text) => {
|
|
162
|
+
if (options.jsonOut) {
|
|
163
|
+
options.writeErr(text);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
options.writeOut(text);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
try {
|
|
171
|
+
const result = await runInitDeposit(options, io, options.seams);
|
|
172
|
+
if (options.jsonOut) {
|
|
173
|
+
options.writeOut(`${JSON.stringify(buildInstallSummaryJson(result, options), null, 2)}\n`);
|
|
174
|
+
printNextSteps(result, { printf: options.writeErr });
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
printNextSteps(result, io);
|
|
178
|
+
}
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
181
|
+
catch (cause) {
|
|
182
|
+
if (cause instanceof LegacyLayoutRefusedError) {
|
|
183
|
+
io.printf(buildLegacyRefusalMessage("init", cause.detection));
|
|
184
|
+
if (options.jsonOut) {
|
|
185
|
+
options.writeOut(`${JSON.stringify(buildLegacyRefusalJson("init", resolve(options.projectDir), cause.detection), null, 2)}\n`);
|
|
186
|
+
}
|
|
187
|
+
return LEGACY_LAYOUT_REFUSED_EXIT_CODE;
|
|
188
|
+
}
|
|
189
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
190
|
+
options.writeErr(`directive init: ${message}\n`);
|
|
191
|
+
if (options.jsonOut) {
|
|
192
|
+
options.writeOut(`${JSON.stringify({ success: false, error: message, error_code: "init_deposit_failed" }, null, 2)}\n`);
|
|
193
|
+
}
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=init-deposit.js.map
|