@deftai/directive-core 0.83.0 → 0.84.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/main.js +36 -2
- package/dist/cache/task-cache/constants.d.ts +4 -0
- package/dist/cache/task-cache/constants.js +4 -0
- package/dist/cache/task-cache/executor.d.ts +9 -0
- package/dist/cache/task-cache/executor.js +51 -0
- package/dist/cache/task-cache/hash.d.ts +17 -0
- package/dist/cache/task-cache/hash.js +92 -0
- package/dist/cache/task-cache/index.d.ts +14 -0
- package/dist/cache/task-cache/index.js +15 -0
- package/dist/cache/task-cache/lint.d.ts +4 -0
- package/dist/cache/task-cache/lint.js +55 -0
- package/dist/cache/task-cache/registry.d.ts +7 -0
- package/dist/cache/task-cache/registry.js +67 -0
- package/dist/cache/task-cache/store.d.ts +10 -0
- package/dist/cache/task-cache/store.js +48 -0
- package/dist/cache/task-cache/types.d.ts +48 -0
- package/dist/cache/task-cache/types.js +3 -0
- package/dist/check/cached-orchestrator.d.ts +16 -0
- package/dist/check/cached-orchestrator.js +76 -0
- package/dist/check/context.d.ts +30 -0
- package/dist/check/context.js +28 -0
- package/dist/check/gate-lists.d.ts +18 -0
- package/dist/check/gate-lists.js +68 -0
- package/dist/check/index.d.ts +4 -1
- package/dist/check/index.js +3 -0
- package/dist/check/orchestrator.d.ts +3 -45
- package/dist/check/orchestrator.js +8 -46
- package/dist/check/runner-detect.d.ts +20 -0
- package/dist/check/runner-detect.js +131 -0
- package/dist/eval/readback.js +6 -1
- package/dist/hooks/dispatcher.d.ts +2 -0
- package/dist/hooks/dispatcher.js +48 -6
- package/dist/init-deposit/gitignore.js +1 -0
- package/dist/scope/decompose.js +9 -3
- package/dist/session/git.d.ts +2 -0
- package/dist/session/git.js +14 -0
- package/dist/session/verify-session-ritual.js +39 -5
- package/dist/swarm/routing-set-cli.js +16 -5
- package/dist/swarm/routing.d.ts +1 -1
- package/dist/swarm/routing.js +3 -1
- package/dist/value/readback.js +6 -1
- package/package.json +7 -3
package/dist/cache/main.js
CHANGED
|
@@ -6,6 +6,10 @@ import { cacheFetchAll, cacheRefreshClosed } from "./fetch.js";
|
|
|
6
6
|
import { pythonBool, pythonJsonPretty } from "./json.js";
|
|
7
7
|
import { cacheGet, cacheInvalidate, cachePrune, cachePruneToCap, cachePut } from "./operations.js";
|
|
8
8
|
import { resolveCaps } from "./quota.js";
|
|
9
|
+
import { clearTaskCache } from "./task-cache/store.js";
|
|
10
|
+
function usage() {
|
|
11
|
+
process.stderr.write("usage: cache [-h] {put,get,invalidate,fetch-all,prune,clear} ...\n");
|
|
12
|
+
}
|
|
9
13
|
function normaliseLabelFilter(raw) {
|
|
10
14
|
if (!raw || raw.length === 0)
|
|
11
15
|
return [];
|
|
@@ -14,8 +18,36 @@ function normaliseLabelFilter(raw) {
|
|
|
14
18
|
.map((item) => item.trim())
|
|
15
19
|
.filter((item) => item.length > 0));
|
|
16
20
|
}
|
|
17
|
-
function
|
|
18
|
-
process.
|
|
21
|
+
function cmdClear(args) {
|
|
22
|
+
let projectRoot = process.cwd();
|
|
23
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
24
|
+
const arg = args[i];
|
|
25
|
+
if (arg === "--project-root") {
|
|
26
|
+
const next = args[i + 1];
|
|
27
|
+
if (next !== undefined) {
|
|
28
|
+
projectRoot = next;
|
|
29
|
+
}
|
|
30
|
+
i += 1;
|
|
31
|
+
}
|
|
32
|
+
else if (arg?.startsWith("--project-root=")) {
|
|
33
|
+
projectRoot = arg.slice("--project-root=".length);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
throw new CacheError(`unexpected argument: ${arg}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const result = clearTaskCache(projectRoot);
|
|
40
|
+
if (result.code !== 0) {
|
|
41
|
+
process.stderr.write("cache clear: failed to remove task cache directory\n");
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
if (result.removed) {
|
|
45
|
+
process.stdout.write(`cache clear: removed task cache under ${projectRoot}\n`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
process.stdout.write(`cache clear: no task cache present under ${projectRoot}\n`);
|
|
49
|
+
}
|
|
50
|
+
return 0;
|
|
19
51
|
}
|
|
20
52
|
function cmdPut(args) {
|
|
21
53
|
let source = "";
|
|
@@ -298,6 +330,8 @@ export function main(argv) {
|
|
|
298
330
|
return cmdFetchAll(rest);
|
|
299
331
|
case "prune":
|
|
300
332
|
return cmdPrune(rest);
|
|
333
|
+
case "clear":
|
|
334
|
+
return cmdClear(rest);
|
|
301
335
|
default:
|
|
302
336
|
usage();
|
|
303
337
|
process.stderr.write(`cache: error: argument cmd: invalid choice: '${cmd}'\n`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RunWithCacheOptions, TaskRunResult } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Run a task with content-hash caching (#1713).
|
|
4
|
+
* - Only exit-0 results are stored.
|
|
5
|
+
* - Failures always re-run.
|
|
6
|
+
* - Non-cacheable / incomplete inputs fail open to running.
|
|
7
|
+
*/
|
|
8
|
+
export declare function runWithCache(options: RunWithCacheOptions): TaskRunResult;
|
|
9
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { composeCacheKey, hashTaskInputs } from "./hash.js";
|
|
2
|
+
import { readCachedTaskRecord, writeCachedTaskRecord } from "./store.js";
|
|
3
|
+
/**
|
|
4
|
+
* Run a task with content-hash caching (#1713).
|
|
5
|
+
* - Only exit-0 results are stored.
|
|
6
|
+
* - Failures always re-run.
|
|
7
|
+
* - Non-cacheable / incomplete inputs fail open to running.
|
|
8
|
+
*/
|
|
9
|
+
export function runWithCache(options) {
|
|
10
|
+
const { projectRoot, contract, codeVersion, noCache = false, cacheRoot, runner } = options;
|
|
11
|
+
if (noCache || !contract.cacheable) {
|
|
12
|
+
const live = runner();
|
|
13
|
+
return { ...live, fromCache: false };
|
|
14
|
+
}
|
|
15
|
+
const effectiveVersion = contract.codeVersion ?? codeVersion;
|
|
16
|
+
const enumeration = hashTaskInputs(projectRoot, contract, process.env);
|
|
17
|
+
if (!enumeration.complete) {
|
|
18
|
+
const live = runner();
|
|
19
|
+
return { ...live, fromCache: false };
|
|
20
|
+
}
|
|
21
|
+
const cacheKey = composeCacheKey(contract.id, enumeration.digest, effectiveVersion);
|
|
22
|
+
const cached = readCachedTaskRecord(projectRoot, cacheKey, cacheRoot);
|
|
23
|
+
if (cached !== null && cached.codeVersion === effectiveVersion) {
|
|
24
|
+
if (cached.stdout.length > 0) {
|
|
25
|
+
process.stdout.write(cached.stdout);
|
|
26
|
+
}
|
|
27
|
+
if (cached.stderr.length > 0) {
|
|
28
|
+
process.stderr.write(cached.stderr);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
exitCode: cached.exitCode,
|
|
32
|
+
stdout: cached.stdout,
|
|
33
|
+
stderr: cached.stderr,
|
|
34
|
+
fromCache: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const live = runner();
|
|
38
|
+
if (live.exitCode === 0) {
|
|
39
|
+
writeCachedTaskRecord(projectRoot, cacheKey, {
|
|
40
|
+
taskId: contract.id,
|
|
41
|
+
inputsHash: enumeration.digest,
|
|
42
|
+
codeVersion: effectiveVersion,
|
|
43
|
+
exitCode: live.exitCode,
|
|
44
|
+
stdout: live.stdout,
|
|
45
|
+
stderr: live.stderr,
|
|
46
|
+
storedAt: new Date().toISOString(),
|
|
47
|
+
}, cacheRoot);
|
|
48
|
+
}
|
|
49
|
+
return { ...live, fromCache: false };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TaskContract, TaskInputSpec } from "./types.js";
|
|
2
|
+
export interface InputEnumeration {
|
|
3
|
+
readonly complete: boolean;
|
|
4
|
+
readonly digest: string;
|
|
5
|
+
}
|
|
6
|
+
/** Expand declared globs under projectRoot; returns sorted relative paths. */
|
|
7
|
+
export declare function expandInputGlobs(projectRoot: string, spec: TaskInputSpec): string[];
|
|
8
|
+
/** Collect env values for declared keys (missing vars map to empty string). */
|
|
9
|
+
export declare function collectEnvValues(spec: TaskInputSpec, env: NodeJS.ProcessEnv): Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Hash declared inputs. Returns `complete: false` when no inputs are declared
|
|
12
|
+
* (fail open to running — never cache without explicit enumeration).
|
|
13
|
+
*/
|
|
14
|
+
export declare function hashTaskInputs(projectRoot: string, contract: TaskContract, env: NodeJS.ProcessEnv): InputEnumeration;
|
|
15
|
+
/** Compose the final cache key digest including codeVersion. */
|
|
16
|
+
export declare function composeCacheKey(taskId: string, inputsHash: string, codeVersion: string): string;
|
|
17
|
+
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { globSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { relative, resolve } from "node:path";
|
|
4
|
+
function stableJson(value) {
|
|
5
|
+
if (value === null || typeof value !== "object") {
|
|
6
|
+
return JSON.stringify(value);
|
|
7
|
+
}
|
|
8
|
+
if (Array.isArray(value)) {
|
|
9
|
+
return `[${value.map((item) => stableJson(item)).join(",")}]`;
|
|
10
|
+
}
|
|
11
|
+
const obj = value;
|
|
12
|
+
const keys = Object.keys(obj).sort();
|
|
13
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${stableJson(obj[key])}`).join(",")}}`;
|
|
14
|
+
}
|
|
15
|
+
function hashFile(path) {
|
|
16
|
+
const hash = createHash("sha256");
|
|
17
|
+
hash.update(readFileSync(path));
|
|
18
|
+
return hash.digest("hex");
|
|
19
|
+
}
|
|
20
|
+
/** Expand declared globs under projectRoot; returns sorted relative paths. */
|
|
21
|
+
export function expandInputGlobs(projectRoot, spec) {
|
|
22
|
+
const root = resolve(projectRoot);
|
|
23
|
+
const globs = spec.globs ?? [];
|
|
24
|
+
const paths = new Set();
|
|
25
|
+
for (const pattern of globs) {
|
|
26
|
+
let matches;
|
|
27
|
+
try {
|
|
28
|
+
matches = globSync(pattern, { cwd: root }).filter((match) => {
|
|
29
|
+
try {
|
|
30
|
+
return !statSync(resolve(root, match)).isDirectory();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
for (const match of matches) {
|
|
41
|
+
paths.add(relative(root, resolve(root, match)).replace(/\\/g, "/"));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [...paths].sort();
|
|
45
|
+
}
|
|
46
|
+
/** Collect env values for declared keys (missing vars map to empty string). */
|
|
47
|
+
export function collectEnvValues(spec, env) {
|
|
48
|
+
const out = {};
|
|
49
|
+
for (const key of spec.env ?? []) {
|
|
50
|
+
out[key] = env[key] ?? "";
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Hash declared inputs. Returns `complete: false` when no inputs are declared
|
|
56
|
+
* (fail open to running — never cache without explicit enumeration).
|
|
57
|
+
*/
|
|
58
|
+
export function hashTaskInputs(projectRoot, contract, env) {
|
|
59
|
+
const spec = contract.inputs;
|
|
60
|
+
const hasGlobs = (spec.globs?.length ?? 0) > 0;
|
|
61
|
+
const hasEnv = (spec.env?.length ?? 0) > 0;
|
|
62
|
+
if (!hasGlobs && !hasEnv) {
|
|
63
|
+
return { complete: false, digest: "" };
|
|
64
|
+
}
|
|
65
|
+
const files = expandInputGlobs(projectRoot, spec);
|
|
66
|
+
if (hasGlobs && files.length === 0) {
|
|
67
|
+
return { complete: false, digest: "" };
|
|
68
|
+
}
|
|
69
|
+
const fileHashes = {};
|
|
70
|
+
for (const rel of files) {
|
|
71
|
+
const abs = resolve(projectRoot, rel);
|
|
72
|
+
try {
|
|
73
|
+
fileHashes[rel] = hashFile(abs);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Fail open: a file removed/unreadable between glob and read must not crash check.
|
|
77
|
+
return { complete: false, digest: "" };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const payload = {
|
|
81
|
+
taskId: contract.id,
|
|
82
|
+
files: fileHashes,
|
|
83
|
+
env: collectEnvValues(spec, env),
|
|
84
|
+
};
|
|
85
|
+
const digest = createHash("sha256").update(stableJson(payload)).digest("hex");
|
|
86
|
+
return { complete: true, digest };
|
|
87
|
+
}
|
|
88
|
+
/** Compose the final cache key digest including codeVersion. */
|
|
89
|
+
export function composeCacheKey(taskId, inputsHash, codeVersion) {
|
|
90
|
+
return createHash("sha256").update(stableJson({ taskId, inputsHash, codeVersion })).digest("hex");
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { lintTaskRegistry } from "./lint.js";
|
|
2
|
+
export { DEFAULT_TASK_CACHE_ROOT, TASK_CACHE_MANIFEST } from "./constants.js";
|
|
3
|
+
export { runWithCache } from "./executor.js";
|
|
4
|
+
export { composeCacheKey, expandInputGlobs, hashTaskInputs } from "./hash.js";
|
|
5
|
+
export { lintTaskContract, lintTaskRegistry } from "./lint.js";
|
|
6
|
+
export { defaultNonCacheableContract, lookupTaskContract, resolveTaskContract, TASK_REGISTRY, } from "./registry.js";
|
|
7
|
+
export { clearTaskCache, readCachedTaskRecord, taskCacheRoot, writeCachedTaskRecord, } from "./store.js";
|
|
8
|
+
export type { CachedTaskRecord, RegistryLintFinding, RunWithCacheOptions, TaskContract, TaskInputSpec, TaskRunResult, } from "./types.js";
|
|
9
|
+
/** Lint the shipped registry; under-declared cacheable tasks fail closed. */
|
|
10
|
+
export declare function lintShippedRegistry(): {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
findings: ReturnType<typeof lintTaskRegistry>;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { lintTaskRegistry } from "./lint.js";
|
|
2
|
+
import { TASK_REGISTRY } from "./registry.js";
|
|
3
|
+
export { DEFAULT_TASK_CACHE_ROOT, TASK_CACHE_MANIFEST } from "./constants.js";
|
|
4
|
+
export { runWithCache } from "./executor.js";
|
|
5
|
+
export { composeCacheKey, expandInputGlobs, hashTaskInputs } from "./hash.js";
|
|
6
|
+
export { lintTaskContract, lintTaskRegistry } from "./lint.js";
|
|
7
|
+
export { defaultNonCacheableContract, lookupTaskContract, resolveTaskContract, TASK_REGISTRY, } from "./registry.js";
|
|
8
|
+
export { clearTaskCache, readCachedTaskRecord, taskCacheRoot, writeCachedTaskRecord, } from "./store.js";
|
|
9
|
+
/** Lint the shipped registry; under-declared cacheable tasks fail closed. */
|
|
10
|
+
export function lintShippedRegistry() {
|
|
11
|
+
const findings = lintTaskRegistry(TASK_REGISTRY);
|
|
12
|
+
const blocking = findings.filter((f) => f.kind === "under-declared-input");
|
|
13
|
+
return { ok: blocking.length === 0, findings };
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RegistryLintFinding, TaskContract } from "./types.js";
|
|
2
|
+
export declare function lintTaskContract(contract: TaskContract): RegistryLintFinding[];
|
|
3
|
+
export declare function lintTaskRegistry(contracts: readonly TaskContract[]): RegistryLintFinding[];
|
|
4
|
+
//# sourceMappingURL=lint.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Strip trailing `*` glob wildcards without regex (CodeQL ReDoS-safe). */
|
|
2
|
+
function stripTrailingGlobStars(pattern) {
|
|
3
|
+
let end = pattern.length;
|
|
4
|
+
while (end > 0 && pattern[end - 1] === "*") {
|
|
5
|
+
end--;
|
|
6
|
+
}
|
|
7
|
+
return pattern.slice(0, end);
|
|
8
|
+
}
|
|
9
|
+
function diffSpec(superset, declared) {
|
|
10
|
+
const findings = [];
|
|
11
|
+
for (const glob of superset.globs ?? []) {
|
|
12
|
+
const covered = (declared.globs ?? []).some((decl) => decl === glob || glob.startsWith(stripTrailingGlobStars(decl)));
|
|
13
|
+
if (!covered) {
|
|
14
|
+
findings.push(`glob '${glob}' missing from declared inputs`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
for (const envKey of superset.env ?? []) {
|
|
18
|
+
if (!(declared.env ?? []).includes(envKey)) {
|
|
19
|
+
findings.push(`env '${envKey}' missing from declared inputs`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return findings;
|
|
23
|
+
}
|
|
24
|
+
export function lintTaskContract(contract) {
|
|
25
|
+
const findings = [];
|
|
26
|
+
if (!contract.knownReadSet) {
|
|
27
|
+
return findings;
|
|
28
|
+
}
|
|
29
|
+
const missing = diffSpec(contract.knownReadSet, contract.inputs);
|
|
30
|
+
for (const detail of missing) {
|
|
31
|
+
if (contract.cacheable) {
|
|
32
|
+
findings.push({
|
|
33
|
+
taskId: contract.id,
|
|
34
|
+
kind: "under-declared-input",
|
|
35
|
+
detail,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
findings.push({
|
|
40
|
+
taskId: contract.id,
|
|
41
|
+
kind: "non-cacheable",
|
|
42
|
+
detail: `${detail} (task marked non-cacheable)`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return findings;
|
|
47
|
+
}
|
|
48
|
+
export function lintTaskRegistry(contracts) {
|
|
49
|
+
const all = [];
|
|
50
|
+
for (const contract of contracts) {
|
|
51
|
+
all.push(...lintTaskContract(contract));
|
|
52
|
+
}
|
|
53
|
+
return all;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=lint.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TaskContract } from "./types.js";
|
|
2
|
+
/** Internal gate/task registry for directive dogfood (#1713). */
|
|
3
|
+
export declare const TASK_REGISTRY: readonly TaskContract[];
|
|
4
|
+
export declare function lookupTaskContract(taskId: string): TaskContract | undefined;
|
|
5
|
+
export declare function defaultNonCacheableContract(taskId: string): TaskContract;
|
|
6
|
+
export declare function resolveTaskContract(taskId: string): TaskContract;
|
|
7
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Internal gate/task registry for directive dogfood (#1713). */
|
|
2
|
+
export const TASK_REGISTRY = [
|
|
3
|
+
{
|
|
4
|
+
id: "verify:biome-config",
|
|
5
|
+
cacheable: true,
|
|
6
|
+
inputs: { globs: ["biome.json", "biome.jsonc"] },
|
|
7
|
+
knownReadSet: { globs: ["biome.json", "biome.jsonc"] },
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: "verify:encoding",
|
|
11
|
+
cacheable: true,
|
|
12
|
+
inputs: {
|
|
13
|
+
globs: ["biome.json", "packages/**/*.ts", "packages/**/*.tsx", "content/**/*"],
|
|
14
|
+
},
|
|
15
|
+
knownReadSet: {
|
|
16
|
+
globs: ["biome.json", "packages/**/*.ts", "packages/**/*.tsx", "content/**/*"],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "toolchain:check",
|
|
21
|
+
cacheable: true,
|
|
22
|
+
inputs: {
|
|
23
|
+
globs: ["package.json", "pnpm-lock.yaml", ".nvmrc", ".node-version"],
|
|
24
|
+
env: ["PATH"],
|
|
25
|
+
},
|
|
26
|
+
knownReadSet: {
|
|
27
|
+
globs: ["package.json", "pnpm-lock.yaml", ".nvmrc", ".node-version"],
|
|
28
|
+
env: ["PATH"],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "verify:branch",
|
|
33
|
+
cacheable: false,
|
|
34
|
+
inputs: { env: ["GIT_BRANCH", "DEFT_ALLOW_DEFAULT_BRANCH_COMMIT"] },
|
|
35
|
+
knownReadSet: {
|
|
36
|
+
globs: [".git/HEAD"],
|
|
37
|
+
env: ["GIT_BRANCH", "DEFT_ALLOW_DEFAULT_BRANCH_COMMIT"],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "verify:cache-fresh",
|
|
42
|
+
cacheable: false,
|
|
43
|
+
inputs: {},
|
|
44
|
+
knownReadSet: { globs: [".deft-cache/**/*", "xbrief/.triage-cache/**/*"] },
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "doctor",
|
|
48
|
+
cacheable: false,
|
|
49
|
+
inputs: {},
|
|
50
|
+
knownReadSet: { globs: ["**/*"] },
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
const REGISTRY_MAP = new Map(TASK_REGISTRY.map((entry) => [entry.id, entry]));
|
|
54
|
+
export function lookupTaskContract(taskId) {
|
|
55
|
+
return REGISTRY_MAP.get(taskId);
|
|
56
|
+
}
|
|
57
|
+
export function defaultNonCacheableContract(taskId) {
|
|
58
|
+
return {
|
|
59
|
+
id: taskId,
|
|
60
|
+
cacheable: false,
|
|
61
|
+
inputs: {},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function resolveTaskContract(taskId) {
|
|
65
|
+
return lookupTaskContract(taskId) ?? defaultNonCacheableContract(taskId);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CachedTaskRecord } from "./types.js";
|
|
2
|
+
export declare function readCachedTaskRecord(projectRoot: string, cacheKey: string, cacheRoot?: string): CachedTaskRecord | null;
|
|
3
|
+
export declare function writeCachedTaskRecord(projectRoot: string, cacheKey: string, record: CachedTaskRecord, cacheRoot?: string): void;
|
|
4
|
+
export interface ClearTaskCacheResult {
|
|
5
|
+
readonly code: number;
|
|
6
|
+
readonly removed: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function clearTaskCache(projectRoot: string, cacheRoot?: string): ClearTaskCacheResult;
|
|
9
|
+
export declare function taskCacheRoot(projectRoot: string, cacheRoot?: string): string;
|
|
10
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { assertWriteTargetSafe } from "../../fs/projection-containment.js";
|
|
4
|
+
import { DEFAULT_TASK_CACHE_ROOT, TASK_CACHE_MANIFEST } from "./constants.js";
|
|
5
|
+
function entryDir(projectRoot, cacheKey, cacheRoot = DEFAULT_TASK_CACHE_ROOT) {
|
|
6
|
+
return join(resolve(projectRoot), cacheRoot, cacheKey.slice(0, 2), cacheKey);
|
|
7
|
+
}
|
|
8
|
+
export function readCachedTaskRecord(projectRoot, cacheKey, cacheRoot = DEFAULT_TASK_CACHE_ROOT) {
|
|
9
|
+
const manifest = join(entryDir(projectRoot, cacheKey, cacheRoot), TASK_CACHE_MANIFEST);
|
|
10
|
+
try {
|
|
11
|
+
const raw = JSON.parse(readFileSync(manifest, "utf8"));
|
|
12
|
+
if (raw.exitCode !== 0) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return raw;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function writeCachedTaskRecord(projectRoot, cacheKey, record, cacheRoot = DEFAULT_TASK_CACHE_ROOT) {
|
|
22
|
+
if (record.exitCode !== 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const dir = entryDir(projectRoot, cacheKey, cacheRoot);
|
|
26
|
+
const manifest = join(dir, TASK_CACHE_MANIFEST);
|
|
27
|
+
assertWriteTargetSafe(resolve(projectRoot), dir);
|
|
28
|
+
mkdirSync(dirname(manifest), { recursive: true });
|
|
29
|
+
assertWriteTargetSafe(resolve(projectRoot), manifest);
|
|
30
|
+
writeFileSync(manifest, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
31
|
+
}
|
|
32
|
+
export function clearTaskCache(projectRoot, cacheRoot = DEFAULT_TASK_CACHE_ROOT) {
|
|
33
|
+
const root = join(resolve(projectRoot), cacheRoot);
|
|
34
|
+
if (!existsSync(root)) {
|
|
35
|
+
return { code: 0, removed: false };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
rmSync(root, { recursive: true, force: true });
|
|
39
|
+
return { code: 0, removed: true };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return { code: 1, removed: false };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function taskCacheRoot(projectRoot, cacheRoot = DEFAULT_TASK_CACHE_ROOT) {
|
|
46
|
+
return join(resolve(projectRoot), cacheRoot);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Internal gate/task contract (#1713). Public promotion deferred to #2784. */
|
|
2
|
+
export interface TaskInputSpec {
|
|
3
|
+
/** Glob patterns relative to the project root. */
|
|
4
|
+
readonly globs?: readonly string[];
|
|
5
|
+
/** Environment variable names whose values affect the task outcome. */
|
|
6
|
+
readonly env?: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export interface TaskContract {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
/** When false the task always runs and never stores a cache entry. */
|
|
11
|
+
readonly cacheable: boolean;
|
|
12
|
+
/** Override engine version in the cache key; defaults to installed directive version. */
|
|
13
|
+
readonly codeVersion?: string;
|
|
14
|
+
readonly inputs: TaskInputSpec;
|
|
15
|
+
/** Output globs for under-declaration lint (optional). */
|
|
16
|
+
readonly outputs?: readonly string[];
|
|
17
|
+
/** Superset of reads used by under-declaration lint. */
|
|
18
|
+
readonly knownReadSet?: TaskInputSpec;
|
|
19
|
+
}
|
|
20
|
+
export interface TaskRunResult {
|
|
21
|
+
readonly exitCode: number;
|
|
22
|
+
readonly stdout: string;
|
|
23
|
+
readonly stderr: string;
|
|
24
|
+
readonly fromCache: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface CachedTaskRecord {
|
|
27
|
+
readonly taskId: string;
|
|
28
|
+
readonly inputsHash: string;
|
|
29
|
+
readonly codeVersion: string;
|
|
30
|
+
readonly exitCode: number;
|
|
31
|
+
readonly stdout: string;
|
|
32
|
+
readonly stderr: string;
|
|
33
|
+
readonly storedAt: string;
|
|
34
|
+
}
|
|
35
|
+
export interface RunWithCacheOptions {
|
|
36
|
+
readonly projectRoot: string;
|
|
37
|
+
readonly contract: TaskContract;
|
|
38
|
+
readonly codeVersion: string;
|
|
39
|
+
readonly noCache?: boolean;
|
|
40
|
+
readonly cacheRoot?: string;
|
|
41
|
+
readonly runner: () => Pick<TaskRunResult, "exitCode" | "stdout" | "stderr">;
|
|
42
|
+
}
|
|
43
|
+
export interface RegistryLintFinding {
|
|
44
|
+
readonly taskId: string;
|
|
45
|
+
readonly kind: "under-declared-input" | "non-cacheable";
|
|
46
|
+
readonly detail: string;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TaskRunResult } from "../cache/task-cache/types.js";
|
|
2
|
+
import { type CheckOrchestratorSeams } from "./context.js";
|
|
3
|
+
export interface CachedCheckOptions extends CheckOrchestratorSeams {
|
|
4
|
+
readonly onGateStart?: (gateId: string) => void;
|
|
5
|
+
readonly onGateComplete?: (gateId: string, exitCode: number, fromCache: boolean) => void;
|
|
6
|
+
readonly gateSpawnFn?: (gateId: string, taskBin: string, taskArgs: string[], opts: {
|
|
7
|
+
cwd: string;
|
|
8
|
+
env?: NodeJS.ProcessEnv;
|
|
9
|
+
}) => Pick<TaskRunResult, "exitCode" | "stdout" | "stderr">;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Run check gates sequentially with content-hash caching (#1713).
|
|
13
|
+
* Falls back to fail-open execution for undeclared / non-cacheable gates.
|
|
14
|
+
*/
|
|
15
|
+
export declare function dispatchCachedTaskCheck(frameworkRoot: string, projectRoot: string, options?: CachedCheckOptions): number;
|
|
16
|
+
//# sourceMappingURL=cached-orchestrator.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { lintShippedRegistry, resolveTaskContract, runWithCache, } from "../cache/task-cache/index.js";
|
|
4
|
+
import { readCorePackageVersion } from "../engine-version.js";
|
|
5
|
+
import { resolveCheckTarget } from "./context.js";
|
|
6
|
+
import { checkGateId, checkGateSpawnArgs, gatesForCheckTarget } from "./gate-lists.js";
|
|
7
|
+
function captureSpawn(taskBin, args, opts) {
|
|
8
|
+
const result = spawnSync(taskBin, args, {
|
|
9
|
+
cwd: opts.cwd,
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
env: opts.env ?? process.env,
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
exitCode: result.status ?? 1,
|
|
15
|
+
stdout: result.stdout ?? "",
|
|
16
|
+
stderr: result.stderr ?? "",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Run check gates sequentially with content-hash caching (#1713).
|
|
21
|
+
* Falls back to fail-open execution for undeclared / non-cacheable gates.
|
|
22
|
+
*/
|
|
23
|
+
export function dispatchCachedTaskCheck(frameworkRoot, projectRoot, options = {}) {
|
|
24
|
+
const resolvedFramework = resolve(frameworkRoot);
|
|
25
|
+
const resolvedProject = resolve(projectRoot);
|
|
26
|
+
const taskfilePath = join(resolvedFramework, "Taskfile.yml");
|
|
27
|
+
const taskBin = options.taskBin ?? "task";
|
|
28
|
+
const target = resolveCheckTarget(resolvedFramework, resolvedProject);
|
|
29
|
+
const cwd = target === "check:framework-source" ? resolvedFramework : resolvedProject;
|
|
30
|
+
const gates = gatesForCheckTarget(target);
|
|
31
|
+
const codeVersion = readCorePackageVersion();
|
|
32
|
+
const registryLint = lintShippedRegistry();
|
|
33
|
+
if (!registryLint.ok) {
|
|
34
|
+
for (const finding of registryLint.findings.filter((f) => f.kind === "under-declared-input")) {
|
|
35
|
+
process.stderr.write(`check: task registry lint failed for ${finding.taskId}: ${finding.detail}\n`);
|
|
36
|
+
}
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
if (gates.length === 0) {
|
|
40
|
+
process.stderr.write(`check: no gate list for target ${target}\n`);
|
|
41
|
+
return 2;
|
|
42
|
+
}
|
|
43
|
+
for (const gateSpec of gates) {
|
|
44
|
+
const gateId = checkGateId(gateSpec);
|
|
45
|
+
options.onGateStart?.(gateId);
|
|
46
|
+
const contract = resolveTaskContract(gateId);
|
|
47
|
+
const taskArgs = checkGateSpawnArgs(gateSpec, taskfilePath);
|
|
48
|
+
const result = runWithCache({
|
|
49
|
+
projectRoot: cwd,
|
|
50
|
+
contract,
|
|
51
|
+
codeVersion,
|
|
52
|
+
noCache: options.noCache,
|
|
53
|
+
runner: () => {
|
|
54
|
+
const spawned = options.gateSpawnFn
|
|
55
|
+
? options.gateSpawnFn(gateId, taskBin, taskArgs, {
|
|
56
|
+
cwd,
|
|
57
|
+
env: options.env,
|
|
58
|
+
})
|
|
59
|
+
: captureSpawn(taskBin, taskArgs, { cwd, env: options.env });
|
|
60
|
+
if (spawned.stdout.length > 0) {
|
|
61
|
+
process.stdout.write(spawned.stdout);
|
|
62
|
+
}
|
|
63
|
+
if (spawned.stderr.length > 0) {
|
|
64
|
+
process.stderr.write(spawned.stderr);
|
|
65
|
+
}
|
|
66
|
+
return spawned;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
options.onGateComplete?.(gateId, result.exitCode, result.fromCache);
|
|
70
|
+
if (result.exitCode !== 0) {
|
|
71
|
+
return result.exitCode;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=cached-orchestrator.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface CheckOrchestratorOptions {
|
|
2
|
+
readonly noCache?: boolean;
|
|
3
|
+
readonly useTaskCache?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface CheckOrchestratorSeams extends CheckOrchestratorOptions {
|
|
6
|
+
/** Override the `task` binary path (default: "task"). */
|
|
7
|
+
readonly taskBin?: string;
|
|
8
|
+
/** Override the spawnSync implementation for unit testing. */
|
|
9
|
+
readonly spawnFn?: (cmd: string, args: string[], opts: {
|
|
10
|
+
cwd: string;
|
|
11
|
+
stdio: string;
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
}) => {
|
|
15
|
+
status: number | null;
|
|
16
|
+
signal?: NodeJS.Signals | null;
|
|
17
|
+
error?: Error;
|
|
18
|
+
};
|
|
19
|
+
/** Child-process environment (default: process.env). */
|
|
20
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
21
|
+
/** Wall-clock spawn timeout in milliseconds (default: none). */
|
|
22
|
+
readonly timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/** True when `path` is the directive framework source checkout root. */
|
|
25
|
+
export declare function isFrameworkRepoRoot(path: string): boolean;
|
|
26
|
+
/** Return true when running in the framework's own source checkout (#1519). */
|
|
27
|
+
export declare function isFrameworkSourceContext(frameworkRoot: string, projectRoot: string): boolean;
|
|
28
|
+
/** Select the Taskfile target for the given context. */
|
|
29
|
+
export declare function resolveCheckTarget(frameworkRoot: string, projectRoot: string): string;
|
|
30
|
+
//# sourceMappingURL=context.d.ts.map
|