@deftai/directive-core 0.67.0 → 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/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/doctor/checks.js +11 -11
- package/dist/doctor/constants.d.ts +2 -0
- package/dist/doctor/constants.js +2 -0
- package/dist/doctor/payload-staleness.js +15 -6
- 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/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-md.d.ts +28 -0
- package/dist/platform/agents-md.js +34 -0
- package/dist/policy/resolve.js +56 -46
- package/dist/pr-merge-readiness/compute.d.ts +10 -0
- package/dist/pr-merge-readiness/compute.js +53 -17
- package/dist/pr-merge-readiness/index.d.ts +2 -1
- package/dist/pr-merge-readiness/index.js +1 -0
- package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
- package/dist/pr-merge-readiness/mergeability.js +105 -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/render/project-render.js +114 -89
- 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 +7 -0
- package/dist/swarm/launch.js +17 -5
- 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/package.json +7 -7
- package/dist/install-upgrade/index.d.ts +0 -13
- package/dist/install-upgrade/index.js +0 -251
|
@@ -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
|
|
@@ -0,0 +1,176 @@
|
|
|
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 { execFileSync } from "node:child_process";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join, relative } from "node:path";
|
|
10
|
+
import { gitPorcelain } from "../story-ready/git.js";
|
|
11
|
+
import { CANONICAL_INSTALL_ROOT } from "./constants.js";
|
|
12
|
+
export const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
|
|
13
|
+
export const CORE_GUARD_WORKFLOW_REL = ".github/workflows/deft-core-guard.yml";
|
|
14
|
+
const VBRIEF_LIFECYCLE_DIRS = ["proposed", "pending", "active", "completed", "cancelled"];
|
|
15
|
+
/** Single source of truth for installer-managed paths (#1440 / #1576). */
|
|
16
|
+
export function installerManagedMatchers() {
|
|
17
|
+
return [
|
|
18
|
+
{ exact: "AGENTS.md" },
|
|
19
|
+
{ prefix: ".agents/" },
|
|
20
|
+
{ prefix: ".githooks/" },
|
|
21
|
+
{ exact: ".gitattributes" },
|
|
22
|
+
{ exact: ".gitignore" },
|
|
23
|
+
{ exact: "greptile.json" },
|
|
24
|
+
{ exact: CODEQL_CONFIG_REL },
|
|
25
|
+
{ exact: CORE_GUARD_WORKFLOW_REL },
|
|
26
|
+
{ exact: "Taskfile.yml" },
|
|
27
|
+
{ exact: "vbrief/.deft-version" },
|
|
28
|
+
{ exact: "vbrief/vbrief.md" },
|
|
29
|
+
{ prefix: "vbrief/schemas/" },
|
|
30
|
+
{ prefix: "vbrief/migration/" },
|
|
31
|
+
...VBRIEF_LIFECYCLE_DIRS.map((sub) => ({ exact: `vbrief/${sub}/.gitkeep` })),
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
function escapeEre(value) {
|
|
35
|
+
return value.replace(/[.^$*+?()[\]{}|\\]/g, "\\$&");
|
|
36
|
+
}
|
|
37
|
+
function matcherToEre(matcher) {
|
|
38
|
+
if (matcher.exact)
|
|
39
|
+
return `^${escapeEre(matcher.exact)}$`;
|
|
40
|
+
return `^${escapeEre(matcher.prefix ?? "")}`;
|
|
41
|
+
}
|
|
42
|
+
/** POSIX ERE alternation embedded in the deposited deft-core-guard workflow. */
|
|
43
|
+
export function installerManagedGuardEre() {
|
|
44
|
+
return installerManagedMatchers()
|
|
45
|
+
.map((matcher) => matcherToEre(matcher))
|
|
46
|
+
.join("|");
|
|
47
|
+
}
|
|
48
|
+
function matchesInstallerManaged(path, matchers) {
|
|
49
|
+
for (const matcher of matchers) {
|
|
50
|
+
if (matcher.exact && path === matcher.exact)
|
|
51
|
+
return true;
|
|
52
|
+
if (matcher.prefix && path.startsWith(matcher.prefix))
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
export function isInstallerManagedPath(path) {
|
|
58
|
+
return matchesInstallerManaged(path, installerManagedMatchers());
|
|
59
|
+
}
|
|
60
|
+
export function frameworkStagePaths(projectDir, deftDir, options = {}) {
|
|
61
|
+
const paths = [];
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
const add = (rel) => {
|
|
64
|
+
const normalized = rel.replace(/\\/g, "/");
|
|
65
|
+
if (!normalized || normalized === "." || seen.has(normalized))
|
|
66
|
+
return;
|
|
67
|
+
if (!existsSync(join(projectDir, normalized)))
|
|
68
|
+
return;
|
|
69
|
+
seen.add(normalized);
|
|
70
|
+
paths.push(normalized);
|
|
71
|
+
};
|
|
72
|
+
const relDeft = relative(projectDir, deftDir);
|
|
73
|
+
if (relDeft && !relDeft.startsWith("..") && !relDeft.startsWith("/")) {
|
|
74
|
+
add(relDeft);
|
|
75
|
+
}
|
|
76
|
+
for (const matcher of installerManagedMatchers()) {
|
|
77
|
+
if (matcher.exact === "Taskfile.yml" && !options.includeTaskfile)
|
|
78
|
+
continue;
|
|
79
|
+
if (matcher.exact) {
|
|
80
|
+
add(matcher.exact);
|
|
81
|
+
}
|
|
82
|
+
else if (matcher.prefix) {
|
|
83
|
+
add(matcher.prefix.replace(/\/$/, ""));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return paths;
|
|
87
|
+
}
|
|
88
|
+
/** Best-effort scoped `git add` — never fails the install/update (#1453 Layer 2b). */
|
|
89
|
+
export function stageFrameworkPaths(projectDir, paths, seams = {}) {
|
|
90
|
+
if (paths.length === 0)
|
|
91
|
+
return { staged: false, error: null };
|
|
92
|
+
const readPorcelain = seams.gitPorcelain ?? gitPorcelain;
|
|
93
|
+
if (readPorcelain(projectDir) === null)
|
|
94
|
+
return { staged: false, error: null };
|
|
95
|
+
const runGitAdd = seams.runGitAdd ??
|
|
96
|
+
((root, stagePaths) => {
|
|
97
|
+
execFileSync("git", ["add", ...stagePaths], {
|
|
98
|
+
cwd: root,
|
|
99
|
+
encoding: "utf8",
|
|
100
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
try {
|
|
104
|
+
runGitAdd(projectDir, paths);
|
|
105
|
+
return { staged: true, error: null };
|
|
106
|
+
}
|
|
107
|
+
catch (cause) {
|
|
108
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
109
|
+
return { staged: false, error };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export const COMMIT_HYGIENE_BRANCH_NAME = "chore/deft-framework-upgrade";
|
|
113
|
+
export function printCommitGuidance(io, paths, staged) {
|
|
114
|
+
if (paths.length === 0)
|
|
115
|
+
return;
|
|
116
|
+
const addCmd = `git add ${paths.join(" ")}`;
|
|
117
|
+
io.printf("\nCommit hygiene (#1453, #1671): keep the framework deposit in its OWN branch/PR.\n");
|
|
118
|
+
io.printf("Do NOT use `git add -A` -- mixing the payload with your own files trips the\n");
|
|
119
|
+
io.printf("deft-core-guard CI check.\n");
|
|
120
|
+
if (staged) {
|
|
121
|
+
io.printf("The installer already staged ONLY these framework + installer-managed paths:\n");
|
|
122
|
+
io.printf(` ${addCmd}\n`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
io.printf("Stage ONLY these framework + installer-managed paths:\n");
|
|
126
|
+
io.printf(` ${addCmd}\n`);
|
|
127
|
+
}
|
|
128
|
+
io.printf("Then take the framework deposit through the full PR lifecycle so deft-core-guard\n");
|
|
129
|
+
io.printf("evaluates a clean, standalone PR:\n");
|
|
130
|
+
io.printf(` 1. Branch: git switch -c ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
|
|
131
|
+
io.printf(' 2. Commit: git commit -m "chore(deft): update framework payload"\n');
|
|
132
|
+
io.printf(` 3. Push: git push -u origin ${COMMIT_HYGIENE_BRANCH_NAME}\n`);
|
|
133
|
+
io.printf(' 4. PR: gh pr create --fill --title "chore(deft): update framework payload"\n');
|
|
134
|
+
io.printf(" 5. Merge: gh pr merge --squash --delete-branch # after deft-core-guard passes\n");
|
|
135
|
+
}
|
|
136
|
+
function defaultCachedNames(projectDir) {
|
|
137
|
+
try {
|
|
138
|
+
const out = execFileSync("git", ["diff", "--cached", "--name-only", "-z"], {
|
|
139
|
+
cwd: projectDir,
|
|
140
|
+
encoding: "utf8",
|
|
141
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
142
|
+
});
|
|
143
|
+
return out
|
|
144
|
+
.split("\0")
|
|
145
|
+
.map((entry) => entry.trim().replace(/\\/g, "/"))
|
|
146
|
+
.filter(Boolean);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Narrow the candidate stage paths to those that actually have staged index
|
|
154
|
+
* changes. A candidate file matches when the cached name is identical; a
|
|
155
|
+
* candidate directory (e.g. ``.deft/core``) matches when any cached name lives
|
|
156
|
+
* beneath it. This keeps ``staged_paths`` honest for downstream automation --
|
|
157
|
+
* it reports what git actually staged, not every path passed to ``git add``.
|
|
158
|
+
*/
|
|
159
|
+
function actuallyStagedPaths(stagePaths, cachedNames) {
|
|
160
|
+
return stagePaths.filter((candidate) => cachedNames.some((name) => name === candidate || name.startsWith(`${candidate}/`)));
|
|
161
|
+
}
|
|
162
|
+
export function depositStagePaths(projectDir, options = {}) {
|
|
163
|
+
const deftDir = join(projectDir, CANONICAL_INSTALL_ROOT);
|
|
164
|
+
const stagePaths = frameworkStagePaths(projectDir, deftDir, {
|
|
165
|
+
includeTaskfile: options.includeTaskfile ?? false,
|
|
166
|
+
});
|
|
167
|
+
const { staged } = stageFrameworkPaths(projectDir, stagePaths, options);
|
|
168
|
+
const readCachedNames = options.readCachedNames ?? defaultCachedNames;
|
|
169
|
+
const cachedNames = staged ? readCachedNames(projectDir) : [];
|
|
170
|
+
return {
|
|
171
|
+
stagePaths,
|
|
172
|
+
staged,
|
|
173
|
+
stagedPaths: staged ? actuallyStagedPaths(stagePaths, cachedNames) : [],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=hygiene.js.map
|
|
@@ -19,6 +19,7 @@ export interface InitDepositResult {
|
|
|
19
19
|
readonly taskfileWired: boolean;
|
|
20
20
|
readonly configDir: string;
|
|
21
21
|
readonly legacyLayout: boolean;
|
|
22
|
+
readonly stagedPaths: string[];
|
|
22
23
|
}
|
|
23
24
|
export interface InitDepositSeams {
|
|
24
25
|
resolveContentRoot?: () => Promise<string>;
|
|
@@ -13,6 +13,7 @@ import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
|
|
|
13
13
|
import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
|
|
14
14
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
15
15
|
import { ensureInitGitignoreLines, reconstituteDepositFromContent } from "./gitignore.js";
|
|
16
|
+
import { depositStagePaths, printCommitGuidance } from "./hygiene.js";
|
|
16
17
|
import { buildLegacyRefusalJson, buildLegacyRefusalMessage, detectLegacyLayout, LEGACY_LAYOUT_REFUSED_EXIT_CODE, LegacyLayoutRefusedError, } from "./legacy-detect.js";
|
|
17
18
|
import { printMigrateNudgeIfNeeded } from "./migrate.js";
|
|
18
19
|
import { CANONICAL_INSTALL_ROOT, depositNeutralization, ensureTaskfile, writeAgentsMd, writeAgentsSkills, writeConsumerGitHooks, writeConsumerVbrief, writeInstallManifest, } from "./scaffold.js";
|
|
@@ -93,7 +94,7 @@ export function buildInstallSummaryJson(result, options) {
|
|
|
93
94
|
strategy: "vendor",
|
|
94
95
|
dirty_tree: false,
|
|
95
96
|
dirty_files: [],
|
|
96
|
-
staged_paths:
|
|
97
|
+
staged_paths: result.stagedPaths,
|
|
97
98
|
backup_path: "",
|
|
98
99
|
previous_version: "",
|
|
99
100
|
};
|
|
@@ -150,6 +151,10 @@ export async function runInitDeposit(args, io, seams = {}) {
|
|
|
150
151
|
taskfileWired = ensureTaskfile(projectDir, io);
|
|
151
152
|
}
|
|
152
153
|
const configDir = createUserConfigDir(io);
|
|
154
|
+
const { stagePaths, staged, stagedPaths } = depositStagePaths(projectDir, {
|
|
155
|
+
includeTaskfile: taskfileWired,
|
|
156
|
+
});
|
|
157
|
+
printCommitGuidance(io, stagePaths, staged);
|
|
153
158
|
return {
|
|
154
159
|
projectDir,
|
|
155
160
|
deftDir,
|
|
@@ -157,6 +162,7 @@ export async function runInitDeposit(args, io, seams = {}) {
|
|
|
157
162
|
taskfileWired,
|
|
158
163
|
configDir,
|
|
159
164
|
legacyLayout: false,
|
|
165
|
+
stagedPaths,
|
|
160
166
|
};
|
|
161
167
|
}
|
|
162
168
|
/** CLI-facing wrapper: runs deposit, emits JSON or wizard UX, returns exit code. */
|
|
@@ -22,6 +22,8 @@ export interface RefreshDepositResult {
|
|
|
22
22
|
readonly agentsMdUpdated: boolean;
|
|
23
23
|
readonly versionSkewNotice: string | null;
|
|
24
24
|
readonly legacyLayout: boolean;
|
|
25
|
+
readonly taskfileWired: boolean;
|
|
26
|
+
readonly stagedPaths: string[];
|
|
25
27
|
}
|
|
26
28
|
export interface RefreshDepositSeams {
|
|
27
29
|
resolveContentRoot?: () => Promise<string>;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Refs #1942, #1430, #1671.
|
|
9
9
|
*/
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { join, resolve } from "node:path";
|
|
12
12
|
import { copyTree } from "../deposit/copy-tree.js";
|
|
13
13
|
import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
|
|
@@ -17,31 +17,11 @@ import { readCorePackageVersion } from "../engine-version.js";
|
|
|
17
17
|
import { resolveLifecycleRoot } from "../layout/resolve.js";
|
|
18
18
|
import { DEV_FALLBACK } from "../platform/constants.js";
|
|
19
19
|
import { gitPorcelain } from "../story-ready/git.js";
|
|
20
|
+
import { depositStagePaths, isInstallerManagedPath, printCommitGuidance } from "./hygiene.js";
|
|
20
21
|
import { parseInitArgv } from "./init-deposit.js";
|
|
21
22
|
import { buildLegacyRefusalJson, buildLegacyRefusalMessage, detectLegacyLayout, LEGACY_LAYOUT_REFUSED_EXIT_CODE, LegacyLayoutRefusedError, } from "./legacy-detect.js";
|
|
22
23
|
import { printMigrateNudgeIfNeeded } from "./migrate.js";
|
|
23
|
-
import { CANONICAL_INSTALL_ROOT, depositNeutralization, writeAgentsMd, writeInstallManifest, } from "./scaffold.js";
|
|
24
|
-
const INSTALLER_MANAGED_EXACT = new Set([
|
|
25
|
-
"AGENTS.md",
|
|
26
|
-
".gitattributes",
|
|
27
|
-
".gitignore",
|
|
28
|
-
"greptile.json",
|
|
29
|
-
".github/codeql/codeql-config.yml",
|
|
30
|
-
".github/workflows/deft-core-guard.yml",
|
|
31
|
-
"vbrief/.deft-version",
|
|
32
|
-
"vbrief/vbrief.md",
|
|
33
|
-
"vbrief/proposed/.gitkeep",
|
|
34
|
-
"vbrief/pending/.gitkeep",
|
|
35
|
-
"vbrief/active/.gitkeep",
|
|
36
|
-
"vbrief/completed/.gitkeep",
|
|
37
|
-
"vbrief/cancelled/.gitkeep",
|
|
38
|
-
]);
|
|
39
|
-
const INSTALLER_MANAGED_PREFIXES = [
|
|
40
|
-
".agents/",
|
|
41
|
-
".githooks/",
|
|
42
|
-
"vbrief/schemas/",
|
|
43
|
-
"vbrief/migration/",
|
|
44
|
-
];
|
|
24
|
+
import { CANONICAL_INSTALL_ROOT, depositNeutralization, ensureTaskfile, writeAgentsMd, writeInstallManifest, } from "./scaffold.js";
|
|
45
25
|
function normalizeVersion(version) {
|
|
46
26
|
return version.trim().replace(/^v/, "");
|
|
47
27
|
}
|
|
@@ -110,10 +90,34 @@ function syncBareVersionMarker(projectDir, version) {
|
|
|
110
90
|
// best-effort, mirrors install-upgrade marker write
|
|
111
91
|
}
|
|
112
92
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Retire a stale legacy `.deft/VERSION` after the canonical `.deft/core/VERSION`
|
|
95
|
+
* has been (re)written (#2064). Folded in from the former `install-upgrade`
|
|
96
|
+
* path so the shared refresh transaction covers the v0.27.x -> v0.28 manifest
|
|
97
|
+
* transition (#1046 PR-B) that the doctor's `install-manifest-disagreement`
|
|
98
|
+
* check still cites `task upgrade` to repair. Only acts when the canonical
|
|
99
|
+
* manifest lives at `<project>/.deft/core/VERSION` and a legacy
|
|
100
|
+
* `<project>/.deft/VERSION` disagrees; renames the legacy file to
|
|
101
|
+
* `.deft/VERSION.premigrate` (best-effort, never fatal).
|
|
102
|
+
*/
|
|
103
|
+
function migrateLegacyInstallManifest(projectDir, canonicalManifestPath) {
|
|
104
|
+
const canonical = resolve(canonicalManifestPath);
|
|
105
|
+
const expectedParent = resolve(projectDir, ".deft", "core");
|
|
106
|
+
if (resolve(canonical, "..") !== expectedParent)
|
|
107
|
+
return;
|
|
108
|
+
const legacy = join(projectDir, ".deft", "VERSION");
|
|
109
|
+
if (!existsSync(legacy) || !statSync(legacy).isFile())
|
|
110
|
+
return;
|
|
111
|
+
try {
|
|
112
|
+
const legacyVersion = manifestTagToVersion(parseInstallManifest(readFileSync(legacy, "utf8")));
|
|
113
|
+
const canonicalVersion = manifestTagToVersion(parseInstallManifest(readFileSync(canonical, "utf8")));
|
|
114
|
+
if (legacyVersion !== null && legacyVersion === canonicalVersion)
|
|
115
|
+
return;
|
|
116
|
+
renameSync(legacy, join(projectDir, ".deft", "VERSION.premigrate"));
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// best-effort
|
|
120
|
+
}
|
|
117
121
|
}
|
|
118
122
|
function unquoteGitPath(path) {
|
|
119
123
|
if (path.length >= 2 && path.startsWith('"') && path.endsWith('"')) {
|
|
@@ -206,7 +210,7 @@ export function buildUpdateSummaryJson(result, options) {
|
|
|
206
210
|
update: true,
|
|
207
211
|
non_interactive: options.nonInteractive,
|
|
208
212
|
upgrade: options.upgrade,
|
|
209
|
-
taskfile_wired:
|
|
213
|
+
taskfile_wired: result.taskfileWired,
|
|
210
214
|
missing_tools: [],
|
|
211
215
|
maintainer_mode: false,
|
|
212
216
|
maintainer_tools: [],
|
|
@@ -217,7 +221,7 @@ export function buildUpdateSummaryJson(result, options) {
|
|
|
217
221
|
strategy: "file-swap",
|
|
218
222
|
dirty_tree: false,
|
|
219
223
|
dirty_files: [],
|
|
220
|
-
staged_paths:
|
|
224
|
+
staged_paths: result.stagedPaths,
|
|
221
225
|
backup_path: "",
|
|
222
226
|
previous_version: result.previousDepositVersion ?? "",
|
|
223
227
|
content_version: result.contentVersion,
|
|
@@ -268,13 +272,25 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
268
272
|
fetchedBy: "directive-update",
|
|
269
273
|
...(previousManagedBy ? { managedBy: previousManagedBy } : {}),
|
|
270
274
|
};
|
|
271
|
-
writeInstallManifest(projectDir, deftDir, manifestFields);
|
|
275
|
+
const writtenManifestPath = writeInstallManifest(projectDir, deftDir, manifestFields);
|
|
276
|
+
// #2064: retire a stale legacy .deft/VERSION now that the canonical
|
|
277
|
+
// .deft/core/VERSION has been rewritten (folded in from install-upgrade so no
|
|
278
|
+
// manifest behavior is lost by the redirect). Best-effort; never fatal.
|
|
279
|
+
migrateLegacyInstallManifest(projectDir, writtenManifestPath);
|
|
272
280
|
// #2055: regenerate the bare .deft-version derivative so it agrees with the
|
|
273
281
|
// freshly written manifest tag (otherwise doctor's manifest-agreement check
|
|
274
282
|
// fails and the operator must hand-edit the marker).
|
|
275
283
|
syncBareVersionMarker(projectDir, contentVersion);
|
|
276
284
|
const agentsMdUpdated = writeAgentsMd(projectDir, deftDir, io);
|
|
277
285
|
await depositNeutralization(projectDir, io);
|
|
286
|
+
let taskfileWired = false;
|
|
287
|
+
if (args.nonInteractive) {
|
|
288
|
+
taskfileWired = ensureTaskfile(projectDir, io);
|
|
289
|
+
}
|
|
290
|
+
const { stagePaths, staged, stagedPaths } = depositStagePaths(projectDir, {
|
|
291
|
+
includeTaskfile: taskfileWired,
|
|
292
|
+
});
|
|
293
|
+
printCommitGuidance(io, stagePaths, staged);
|
|
278
294
|
const readPorcelain = seams.gitPorcelain ?? gitPorcelain;
|
|
279
295
|
printRefreshSideEffects(io, frameworkRefreshSideEffects(projectDir, readPorcelain));
|
|
280
296
|
if (versionSkewNotice) {
|
|
@@ -289,6 +305,8 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
289
305
|
agentsMdUpdated,
|
|
290
306
|
versionSkewNotice,
|
|
291
307
|
legacyLayout: false,
|
|
308
|
+
taskfileWired,
|
|
309
|
+
stagedPaths,
|
|
292
310
|
};
|
|
293
311
|
}
|
|
294
312
|
/** CLI-facing wrapper: runs refresh, emits JSON or wizard UX, returns exit code. */
|
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Refs #1942, #1430, #1463, #1179.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
export declare const CANONICAL_INSTALL_ROOT = ".deft/core";
|
|
7
|
+
import { CANONICAL_INSTALL_ROOT, type InitDepositIo } from "./constants.js";
|
|
8
|
+
export type { InitDepositIo };
|
|
9
|
+
export { CANONICAL_INSTALL_ROOT };
|
|
11
10
|
export declare const CORE_GLOB = ".deft/core/**";
|
|
12
11
|
export declare const MINIMAL_TASKFILE = "version: '3'\n\n# Taskfile for this project.\n# Installed by deft-install --yes (Epic-4). Add your own tasks below or in\n# additional included files. The deft include makes all framework tasks\n# (task check, task vbrief:*, task doctor, etc.) available from the project root.\n\nincludes:\n deft:\n taskfile: ./.deft/core/Taskfile.yml\n optional: true\n";
|
|
13
12
|
export declare const CANONICAL_TASKFILE_INCLUDE = "taskfile: ./.deft/core/Taskfile.yml";
|
|
@@ -12,7 +12,9 @@ import { dirname, join, relative } from "node:path";
|
|
|
12
12
|
import { copyTree } from "../deposit/copy-tree.js";
|
|
13
13
|
import { resolveLifecycleRoot } from "../layout/resolve.js";
|
|
14
14
|
import { agentsRefreshPlan } from "../platform/agents-md.js";
|
|
15
|
-
|
|
15
|
+
import { CANONICAL_INSTALL_ROOT } from "./constants.js";
|
|
16
|
+
import { installerManagedGuardEre } from "./hygiene.js";
|
|
17
|
+
export { CANONICAL_INSTALL_ROOT };
|
|
16
18
|
export const CORE_GLOB = ".deft/core/**";
|
|
17
19
|
const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
|
|
18
20
|
const CORE_GUARD_WORKFLOW_REL = ".github/workflows/deft-core-guard.yml";
|
|
@@ -475,29 +477,6 @@ export function writeConsumerGitHooks(projectDir, deftDir, io, seams = {}) {
|
|
|
475
477
|
}
|
|
476
478
|
return filesDeposited || configWired;
|
|
477
479
|
}
|
|
478
|
-
function escapeEre(value) {
|
|
479
|
-
return value.replace(/[.^$*+?()[\]{}|\\]/g, "\\$&");
|
|
480
|
-
}
|
|
481
|
-
function installerManagedGuardEre() {
|
|
482
|
-
const matchers = [
|
|
483
|
-
{ exact: "AGENTS.md" },
|
|
484
|
-
{ prefix: ".agents/" },
|
|
485
|
-
{ prefix: ".githooks/" },
|
|
486
|
-
{ exact: ".gitattributes" },
|
|
487
|
-
{ exact: ".gitignore" },
|
|
488
|
-
{ exact: "greptile.json" },
|
|
489
|
-
{ exact: CODEQL_CONFIG_REL },
|
|
490
|
-
{ exact: CORE_GUARD_WORKFLOW_REL },
|
|
491
|
-
{ exact: "vbrief/.deft-version" },
|
|
492
|
-
{ exact: "vbrief/vbrief.md" },
|
|
493
|
-
{ prefix: "vbrief/schemas/" },
|
|
494
|
-
{ prefix: "vbrief/migration/" },
|
|
495
|
-
...VBRIEF_LIFECYCLE_DIRS.map((sub) => ({ exact: `vbrief/${sub}/.gitkeep` })),
|
|
496
|
-
];
|
|
497
|
-
return matchers
|
|
498
|
-
.map((m) => (m.exact ? `^${escapeEre(m.exact)}$` : `^${escapeEre(m.prefix ?? "")}`))
|
|
499
|
-
.join("|");
|
|
500
|
-
}
|
|
501
480
|
function githubActionsExpr(expression) {
|
|
502
481
|
return ["$", "{{ ", expression, " }}"].join("");
|
|
503
482
|
}
|
|
@@ -11,7 +11,6 @@ export interface DispatchCall {
|
|
|
11
11
|
projectRoot: string;
|
|
12
12
|
frameworkRoot: string;
|
|
13
13
|
}
|
|
14
|
-
export declare function isFrameworkSourceContext(frameworkRoot: string, projectRoot: string): boolean;
|
|
15
14
|
/** Mirror ``scripts/_project_context.dispatch_task_check`` for consumer-task e2e. */
|
|
16
15
|
export declare function dispatchTaskCheck(frameworkRoot: string, projectRoot: string, runner: (command: string, projectRoot: string, frameworkRoot: string) => {
|
|
17
16
|
code: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
|
-
import { join
|
|
3
|
+
import { join } from "node:path";
|
|
4
4
|
import { afterEach } from "vitest";
|
|
5
5
|
import { restIssueListPaginated, setPaginatedLister, setSingleIssueFetcher, setSleepFn, } from "../cache/fetch.js";
|
|
6
6
|
import { auditPath } from "../cache/paths.js";
|
|
@@ -108,14 +108,10 @@ export function writeScopeVbrief(projectRoot, folder, filename, status = "propos
|
|
|
108
108
|
}, null, 2)}\n`, "utf8");
|
|
109
109
|
return target;
|
|
110
110
|
}
|
|
111
|
-
|
|
112
|
-
return resolve(frameworkRoot) === resolve(projectRoot);
|
|
113
|
-
}
|
|
111
|
+
import { resolveCheckTarget } from "../check/orchestrator.js";
|
|
114
112
|
/** Mirror ``scripts/_project_context.dispatch_task_check`` for consumer-task e2e. */
|
|
115
113
|
export function dispatchTaskCheck(frameworkRoot, projectRoot, runner) {
|
|
116
|
-
const target =
|
|
117
|
-
? "check:framework-source"
|
|
118
|
-
: "check:consumer";
|
|
114
|
+
const target = resolveCheckTarget(frameworkRoot, projectRoot);
|
|
119
115
|
return runner(target, projectRoot, frameworkRoot).code;
|
|
120
116
|
}
|
|
121
117
|
/** Hermetic mirror of ``tests/integration/test_scm_smoke._probe_rate_limit``. */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Lifecycle folders searched when a folder-qualified ref dangles (#1926). */
|
|
2
|
+
export declare const LIFECYCLE_SCOPE_FOLDERS: readonly ["proposed", "pending", "active", "completed", "cancelled"];
|
|
3
|
+
export interface ResolveLifecycleArtifactRefOptions {
|
|
4
|
+
/** When false, only resolve the literal path (for rewrite matching). Default true (#1926). */
|
|
5
|
+
readonly allowCrossFolderSearch?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a lifecycle-relative artifact reference to an absolute path.
|
|
9
|
+
*
|
|
10
|
+
* When the direct path is missing but the ref names a lifecycle artifact
|
|
11
|
+
* (folder-qualified or bare basename), search all lifecycle folders for the
|
|
12
|
+
* same filename (#1926).
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveLifecycleArtifactRef(rel: string, lifecycleRoot: string, options?: ResolveLifecycleArtifactRefOptions): string;
|
|
15
|
+
//# sourceMappingURL=lifecycle-ref.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { basename, resolve } from "node:path";
|
|
3
|
+
import { hasArtifactSuffix } from "./resolve.js";
|
|
4
|
+
/** Lifecycle folders searched when a folder-qualified ref dangles (#1926). */
|
|
5
|
+
export const LIFECYCLE_SCOPE_FOLDERS = [
|
|
6
|
+
"proposed",
|
|
7
|
+
"pending",
|
|
8
|
+
"active",
|
|
9
|
+
"completed",
|
|
10
|
+
"cancelled",
|
|
11
|
+
];
|
|
12
|
+
function isLifecycleFolder(name) {
|
|
13
|
+
return LIFECYCLE_SCOPE_FOLDERS.includes(name);
|
|
14
|
+
}
|
|
15
|
+
function searchLifecycleFolders(lifecycleRoot, artifactBasename) {
|
|
16
|
+
for (const folder of LIFECYCLE_SCOPE_FOLDERS) {
|
|
17
|
+
const candidate = resolve(lifecycleRoot, folder, artifactBasename);
|
|
18
|
+
if (existsSync(candidate)) {
|
|
19
|
+
return candidate;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a lifecycle-relative artifact reference to an absolute path.
|
|
26
|
+
*
|
|
27
|
+
* When the direct path is missing but the ref names a lifecycle artifact
|
|
28
|
+
* (folder-qualified or bare basename), search all lifecycle folders for the
|
|
29
|
+
* same filename (#1926).
|
|
30
|
+
*/
|
|
31
|
+
export function resolveLifecycleArtifactRef(rel, lifecycleRoot, options = {}) {
|
|
32
|
+
const allowCrossFolderSearch = options.allowCrossFolderSearch ?? true;
|
|
33
|
+
const direct = resolve(lifecycleRoot, rel);
|
|
34
|
+
if (existsSync(direct)) {
|
|
35
|
+
return direct;
|
|
36
|
+
}
|
|
37
|
+
if (!allowCrossFolderSearch) {
|
|
38
|
+
return direct;
|
|
39
|
+
}
|
|
40
|
+
const parts = rel.split("/").filter(Boolean);
|
|
41
|
+
if (parts.length >= 2 && isLifecycleFolder(parts[0] ?? "")) {
|
|
42
|
+
const found = searchLifecycleFolders(lifecycleRoot, basename(rel));
|
|
43
|
+
if (found !== null) {
|
|
44
|
+
return found;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!rel.includes("/") && hasArtifactSuffix(rel)) {
|
|
48
|
+
const found = searchLifecycleFolders(lifecycleRoot, rel);
|
|
49
|
+
if (found !== null) {
|
|
50
|
+
return found;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return direct;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=lifecycle-ref.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LockDeps } from "../slice/lock.js";
|
|
1
2
|
export interface ManagedSectionAttrs {
|
|
2
3
|
readonly version: number;
|
|
3
4
|
readonly sha: string | null;
|
|
@@ -27,5 +28,32 @@ export declare function attributeRenderManagedSection(rendered: string, attrs: {
|
|
|
27
28
|
}): string;
|
|
28
29
|
/** Compute AGENTS.md managed-section freshness plan (mirrors scripts/_agents_md.py). */
|
|
29
30
|
export declare function agentsRefreshPlan(projectRoot: string, seams?: AgentsMdSeams): Record<string, unknown>;
|
|
31
|
+
export interface ApplyAgentsRefreshOptions {
|
|
32
|
+
/** Compute state only, never write — mirrors `agents:refresh --check`. */
|
|
33
|
+
readonly check?: boolean;
|
|
34
|
+
/** Compute the plan but skip the write — mirrors `agents:refresh --dry-run`. */
|
|
35
|
+
readonly dryRun?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface AgentsRefreshApplyResult {
|
|
38
|
+
readonly state: string;
|
|
39
|
+
readonly path: string;
|
|
40
|
+
/** True only when the managed section was written to disk on this call. */
|
|
41
|
+
readonly wrote: boolean;
|
|
42
|
+
/** True when the plan produced writable `new_content` for a writable state. */
|
|
43
|
+
readonly writable: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Serialize the AGENTS.md managed-section read->compute->write behind an advisory
|
|
47
|
+
* file lock and write atomically (temp file + rename), so concurrent
|
|
48
|
+
* install/upgrade/migrate/refresh writers cannot clobber one another's `session=`
|
|
49
|
+
* write or leave a partially-written file (#1329).
|
|
50
|
+
*
|
|
51
|
+
* Reuses the existing cross-process append lock (`withAppendLock`, keyed here on
|
|
52
|
+
* the AGENTS.md path -> `AGENTS.md.lock`) and atomic writer (`atomicWriteText`)
|
|
53
|
+
* rather than introducing a competing lock utility. The parallel
|
|
54
|
+
* `projectDefinitionMutationLock` remains owned by #1260; a general lock helper is
|
|
55
|
+
* deliberately not added here to avoid two competing mutation-lock utilities.
|
|
56
|
+
*/
|
|
57
|
+
export declare function applyAgentsRefresh(projectRoot: string, options?: ApplyAgentsRefreshOptions, seams?: AgentsMdSeams, lockDeps?: LockDeps): AgentsRefreshApplyResult;
|
|
30
58
|
export declare function hasV3ManagedMarker(projectRoot: string, readText?: (path: string) => string | null): boolean;
|
|
31
59
|
//# sourceMappingURL=agents-md.d.ts.map
|