@deftai/directive-core 0.74.0 → 0.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents-md-budget/evaluate.d.ts +61 -0
- package/dist/agents-md-budget/evaluate.js +341 -19
- package/dist/agents-md-budget/index.d.ts +1 -0
- package/dist/agents-md-budget/index.js +1 -0
- package/dist/agents-md-budget/skill-frontmatter.d.ts +35 -0
- package/dist/agents-md-budget/skill-frontmatter.js +116 -0
- package/dist/cache/operations.d.ts +2 -0
- package/dist/cache/operations.js +16 -1
- package/dist/content-contracts/skills/helpers.d.ts +0 -1
- package/dist/content-contracts/skills/helpers.js +1 -11
- package/dist/content-contracts/skills/skill-frontmatter.js +8 -1
- package/dist/content-contracts/standards/_helpers.js +4 -2
- package/dist/content-contracts/standards/_taskfile-helpers.d.ts +2 -0
- package/dist/content-contracts/standards/_taskfile-helpers.js +11 -5
- package/dist/doctor/doctor-state.js +2 -1
- package/dist/doctor/paths.js +2 -1
- package/dist/eval-health-relocation/evaluate.d.ts +71 -0
- package/dist/eval-health-relocation/evaluate.js +252 -0
- package/dist/eval-health-relocation/index.d.ts +2 -0
- package/dist/eval-health-relocation/index.js +2 -0
- package/dist/init-deposit/scaffold.js +35 -20
- package/dist/intake/issue-emit.js +2 -2
- package/dist/intake/issue-ingest.js +23 -4
- package/dist/packaging/openpackage-tiers.d.ts +12 -0
- package/dist/packaging/openpackage-tiers.js +42 -0
- package/dist/policy/agents-md-budget.d.ts +23 -0
- package/dist/policy/agents-md-budget.js +75 -4
- package/dist/preflight/evaluate.d.ts +5 -2
- package/dist/preflight/evaluate.js +9 -4
- package/dist/preflight/index.d.ts +1 -1
- package/dist/preflight/index.js +1 -1
- package/dist/release/pipeline-fixture.d.ts +3 -0
- package/dist/release/pipeline-fixture.js +11 -0
- package/dist/release/pipeline.js +4 -0
- package/dist/release/spawn.js +5 -3
- package/dist/scope/decompose.js +4 -3
- package/dist/scope/transition.js +17 -2
- package/dist/scope/vbrief-ref.d.ts +6 -0
- package/dist/scope/vbrief-ref.js +23 -4
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/posture.d.ts +50 -0
- package/dist/session/posture.js +152 -0
- package/dist/session/ritual-sentinel.d.ts +2 -0
- package/dist/session/ritual-sentinel.js +3 -0
- package/dist/session/session-start.d.ts +8 -0
- package/dist/session/session-start.js +43 -0
- package/dist/session/verify-session-ritual.d.ts +6 -0
- package/dist/session/verify-session-ritual.js +67 -4
- package/dist/swarm/complete-cohort.js +6 -6
- package/dist/swarm/launch.js +2 -2
- package/dist/triage/bootstrap/gitignore.js +8 -4
- package/dist/triage/cache-path.js +16 -3
- package/dist/triage/queue/cache.d.ts +14 -0
- package/dist/triage/queue/cache.js +61 -1
- package/dist/triage/welcome/writers.js +2 -0
- package/dist/ts-check-lane/index.d.ts +1 -1
- package/dist/ts-check-lane/index.js +1 -1
- package/dist/ts-check-lane/run-lane.d.ts +5 -0
- package/dist/ts-check-lane/run-lane.js +15 -0
- package/dist/validate-content/validate-links.js +2 -2
- package/dist/value/readback.d.ts +1 -0
- package/dist/value/readback.js +5 -1
- package/dist/vbrief-build/constants.d.ts +3 -3
- package/dist/vbrief-build/constants.js +4 -3
- package/dist/vbrief-build/index.d.ts +1 -1
- package/dist/vbrief-build/index.js +1 -1
- package/dist/verify-env/toolchain-check.js +20 -5
- package/dist/verify-env/verify-hooks-installed.d.ts +2 -0
- package/dist/verify-env/verify-hooks-installed.js +17 -15
- package/package.json +7 -3
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join, relative } from "node:path";
|
|
9
|
+
import { assertProjectionContained } from "../fs/projection-containment.js";
|
|
9
10
|
import { MIGRATED_ARTIFACT_DIR, resolveEvalDir, resolveLifecycleLayout, resolveLifecycleRoot, } from "../layout/resolve.js";
|
|
10
11
|
import { generateTriageCacheReadmeBody } from "./bootstrap/gitignore.js";
|
|
11
12
|
/** Directory name for the triage working-set cache (not version-eval results). */
|
|
@@ -24,8 +25,8 @@ export const TRIAGE_CACHE_FILE_NAMES = [
|
|
|
24
25
|
];
|
|
25
26
|
/** Known triage-cache directory names migrated off the legacy `.eval/` tree. */
|
|
26
27
|
export const TRIAGE_CACHE_DIR_NAMES = ["decompositions"];
|
|
27
|
-
/**
|
|
28
|
-
|
|
28
|
+
/** Compute the layout-aware `.triage-cache/` directory without containment checks. */
|
|
29
|
+
function triageCacheDirPath(projectRoot) {
|
|
29
30
|
let layoutRoot;
|
|
30
31
|
try {
|
|
31
32
|
layoutRoot = resolveLifecycleRoot(projectRoot);
|
|
@@ -35,6 +36,17 @@ export function resolveTriageCacheDir(projectRoot) {
|
|
|
35
36
|
}
|
|
36
37
|
return join(layoutRoot, TRIAGE_CACHE_DIR_NAME);
|
|
37
38
|
}
|
|
39
|
+
/** Refuse symlink-escaping xbrief/.triage-cache before mkdir/read/write (#2446). */
|
|
40
|
+
function assertWritableTriageCachePath(projectRoot, ...segments) {
|
|
41
|
+
const base = triageCacheDirPath(projectRoot);
|
|
42
|
+
const target = segments.length > 0 ? join(base, ...segments) : base;
|
|
43
|
+
assertProjectionContained(projectRoot, target);
|
|
44
|
+
}
|
|
45
|
+
/** Absolute path to the layout-aware `.triage-cache/` directory. */
|
|
46
|
+
export function resolveTriageCacheDir(projectRoot) {
|
|
47
|
+
assertWritableTriageCachePath(projectRoot);
|
|
48
|
+
return triageCacheDirPath(projectRoot);
|
|
49
|
+
}
|
|
38
50
|
/** POSIX-style path relative to project root (e.g. `xbrief/.triage-cache/foo`). */
|
|
39
51
|
export function triageCacheRelPath(projectRoot, ...segments) {
|
|
40
52
|
let artifactDir;
|
|
@@ -118,8 +130,9 @@ export function migrateLegacyTriageCacheFromEval(projectRoot) {
|
|
|
118
130
|
}
|
|
119
131
|
/** Resolve a path under `.triage-cache/`, migrating legacy `.eval/` files first. */
|
|
120
132
|
export function resolveTriageCachePath(projectRoot, ...segments) {
|
|
133
|
+
assertWritableTriageCachePath(projectRoot, ...segments);
|
|
121
134
|
migrateLegacyTriageCacheFromEval(projectRoot);
|
|
122
|
-
return join(
|
|
135
|
+
return join(triageCacheDirPath(projectRoot), ...segments);
|
|
123
136
|
}
|
|
124
137
|
/** Display helper: project-root-relative POSIX path for logs and gitignore copy. */
|
|
125
138
|
export function triageCacheDisplayPath(projectRoot, absPath) {
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { CachedIssue } from "./types.js";
|
|
2
|
+
/** Neutral title shown when the scanner fences injection-shaped title text. */
|
|
3
|
+
export declare const QUARANTINED_TITLE_PLACEHOLDER = "[quarantined title]";
|
|
4
|
+
/**
|
|
5
|
+
* Read `meta.json` `scan_result.passed` for a cache entry.
|
|
6
|
+
* Returns `false` when the scanner hard-failed, `true` when it passed, and
|
|
7
|
+
* `null` when meta is missing/unreadable (legacy or incomplete entries).
|
|
8
|
+
*/
|
|
9
|
+
export declare function readCacheScanPassed(entryDir: string): boolean | null;
|
|
10
|
+
/**
|
|
11
|
+
* Sanitize a queue title through the cache scanner.
|
|
12
|
+
* Returns `null` when the title hard-fails (omit the issue); otherwise a
|
|
13
|
+
* display title that never carries unfenced injection-shaped attacker text.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sanitizeQueueTitle(rawTitle: string): string | null;
|
|
2
16
|
/** Read slices.jsonl records. */
|
|
3
17
|
export declare function resolveSlicesLogPath(options?: {
|
|
4
18
|
readonly slicesLogPath?: string | null;
|
|
@@ -1,10 +1,58 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { scan } from "../../cache/scanner.js";
|
|
3
4
|
import { resolveTriageCachePath } from "../cache-path.js";
|
|
4
5
|
import { extractAuthor, extractMilestone } from "../scope-drift/cache-walker.js";
|
|
5
6
|
import { CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE } from "./constants.js";
|
|
6
7
|
import { hasActiveScopeIgnores, isRawIssueScopeIgnored, resolveScopeIgnores, } from "./scope-ignores-filter.js";
|
|
7
8
|
import { blockedByIssueNumber, rankByIssueNumber } from "./scope-walk.js";
|
|
9
|
+
/** Neutral title shown when the scanner fences injection-shaped title text. */
|
|
10
|
+
export const QUARANTINED_TITLE_PLACEHOLDER = "[quarantined title]";
|
|
11
|
+
/**
|
|
12
|
+
* Read `meta.json` `scan_result.passed` for a cache entry.
|
|
13
|
+
* Returns `false` when the scanner hard-failed, `true` when it passed, and
|
|
14
|
+
* `null` when meta is missing/unreadable (legacy or incomplete entries).
|
|
15
|
+
*/
|
|
16
|
+
export function readCacheScanPassed(entryDir) {
|
|
17
|
+
const metaPath = join(entryDir, "meta.json");
|
|
18
|
+
if (!existsSync(metaPath)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(readFileSync(metaPath, { encoding: "utf8" }));
|
|
23
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const scanResult = parsed.scan_result;
|
|
27
|
+
if (typeof scanResult !== "object" || scanResult === null) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const passed = scanResult.passed;
|
|
31
|
+
return typeof passed === "boolean" ? passed : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Sanitize a queue title through the cache scanner.
|
|
39
|
+
* Returns `null` when the title hard-fails (omit the issue); otherwise a
|
|
40
|
+
* display title that never carries unfenced injection-shaped attacker text.
|
|
41
|
+
*/
|
|
42
|
+
export function sanitizeQueueTitle(rawTitle) {
|
|
43
|
+
const result = scan(rawTitle);
|
|
44
|
+
if (!result.passed) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (result.flags.some((flag) => flag.category === "injection-heading")) {
|
|
48
|
+
return QUARANTINED_TITLE_PLACEHOLDER;
|
|
49
|
+
}
|
|
50
|
+
if (result.flags.some((flag) => flag.category === "invisible-unicode")) {
|
|
51
|
+
const stripped = result.transformed_content.replace(/\r?\n+$/u, "");
|
|
52
|
+
return stripped.length > 0 ? stripped : QUARANTINED_TITLE_PLACEHOLDER;
|
|
53
|
+
}
|
|
54
|
+
return rawTitle;
|
|
55
|
+
}
|
|
8
56
|
function cachedState(issue) {
|
|
9
57
|
if (issue === undefined) {
|
|
10
58
|
return "";
|
|
@@ -158,9 +206,21 @@ export function loadCachedIssues(repo, options) {
|
|
|
158
206
|
if (filterScopeIgnores && isRawIssueScopeIgnored(payload, scopeIgnores)) {
|
|
159
207
|
continue;
|
|
160
208
|
}
|
|
209
|
+
// Fail closed on scanner hard-fail: cachePut keeps raw.json but suppresses
|
|
210
|
+
// content.md when scan_result.passed is false. The queue must not promote
|
|
211
|
+
// those titles into the mandatory triage:queue agent context.
|
|
212
|
+
const scanPassed = readCacheScanPassed(entryDir);
|
|
213
|
+
if (scanPassed === false) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const rawTitle = typeof payload.title === "string" ? payload.title : "";
|
|
217
|
+
const safeTitle = sanitizeQueueTitle(rawTitle);
|
|
218
|
+
if (safeTitle === null) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
161
221
|
issues.push({
|
|
162
222
|
number: n,
|
|
163
|
-
title:
|
|
223
|
+
title: safeTitle,
|
|
164
224
|
state,
|
|
165
225
|
labels: parseLabels(payload.labels),
|
|
166
226
|
author: extractAuthor(payload),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { assertProjectionContained } from "../../fs/projection-containment.js";
|
|
3
4
|
import { hasArtifactSuffix, resolveLifecycleFolder, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
|
|
4
5
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY } from "../../policy/plan-extensions.js";
|
|
5
6
|
import { projectDefinitionMutationLock } from "../../vbrief-build/project-definition-io.js";
|
|
@@ -12,6 +13,7 @@ function utcIso() {
|
|
|
12
13
|
}
|
|
13
14
|
export function appendAuditEntry(projectRoot, entry) {
|
|
14
15
|
const logPath = join(resolve(projectRoot), AUDIT_LOG_REL_PATH);
|
|
16
|
+
assertProjectionContained(projectRoot, logPath);
|
|
15
17
|
mkdirSync(dirname(logPath), { recursive: true });
|
|
16
18
|
const line = `${utcIso()} ${entry}\n`;
|
|
17
19
|
if (!existsSync(logPath)) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { LaneRunner, ResolvePnpmOptions, RunnerResult, RunTsLaneOptions } from "./run-lane.js";
|
|
2
|
-
export { LANE_COMMANDS, resolvePnpm, runTsLane, SKIP_NOTICE } from "./run-lane.js";
|
|
2
|
+
export { LANE_COMMANDS, resolvePnpm, runTsLane, SKIP_NOTICE, sanitizeTsLaneEnv, } from "./run-lane.js";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { LANE_COMMANDS, resolvePnpm, runTsLane, SKIP_NOTICE } from "./run-lane.js";
|
|
1
|
+
export { LANE_COMMANDS, resolvePnpm, runTsLane, SKIP_NOTICE, sanitizeTsLaneEnv, } from "./run-lane.js";
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -29,6 +29,11 @@ export interface RunnerResult {
|
|
|
29
29
|
readonly error?: Error;
|
|
30
30
|
}
|
|
31
31
|
export type LaneRunner = (argv: readonly string[], cwd: string) => RunnerResult;
|
|
32
|
+
/**
|
|
33
|
+
* Strip release preflight bypass vars before spawning pnpm/vitest so nested unit
|
|
34
|
+
* tests observe fail-closed branch and cache gates (#2434 / #1553 recurrence).
|
|
35
|
+
*/
|
|
36
|
+
export declare function sanitizeTsLaneEnv(base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
32
37
|
export interface RunTsLaneOptions {
|
|
33
38
|
/** Resolved pnpm executable path, or null when not installed. */
|
|
34
39
|
readonly pnpm: string | null;
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
import { spawnSync } from "node:child_process";
|
|
20
20
|
import { existsSync } from "node:fs";
|
|
21
21
|
import { posix, win32 } from "node:path";
|
|
22
|
+
import { BRANCH_GATE_BYPASS_ENV, RELEASE_PREFLIGHT_ENV } from "../release/constants.js";
|
|
23
|
+
/** Release Step-5 vars that must not leak into vitest via inherited pnpm env (#2434). */
|
|
24
|
+
const TS_LANE_POISON_ENV_KEYS = [BRANCH_GATE_BYPASS_ENV, RELEASE_PREFLIGHT_ENV];
|
|
22
25
|
/**
|
|
23
26
|
* Run order is deliberate: lint (cheapest, catches the biome class first),
|
|
24
27
|
* then build, then the test suite.
|
|
@@ -31,6 +34,17 @@ export const LANE_COMMANDS = [
|
|
|
31
34
|
export const SKIP_NOTICE = "[ts:check-lane] pnpm not found on PATH -- skipping the TypeScript lane " +
|
|
32
35
|
"(build/lint/test). The TS engine stays validated by the dedicated CI job. " +
|
|
33
36
|
"Install the Node toolchain (pnpm) to run the TS lane locally.";
|
|
37
|
+
/**
|
|
38
|
+
* Strip release preflight bypass vars before spawning pnpm/vitest so nested unit
|
|
39
|
+
* tests observe fail-closed branch and cache gates (#2434 / #1553 recurrence).
|
|
40
|
+
*/
|
|
41
|
+
export function sanitizeTsLaneEnv(base = process.env) {
|
|
42
|
+
const env = { ...base };
|
|
43
|
+
for (const key of TS_LANE_POISON_ENV_KEYS) {
|
|
44
|
+
delete env[key];
|
|
45
|
+
}
|
|
46
|
+
return env;
|
|
47
|
+
}
|
|
34
48
|
/** Windows command shims (.cmd/.bat) need a shell; native executables do not. */
|
|
35
49
|
export function shouldUseShellForCommand(command, platform = process.platform) {
|
|
36
50
|
return platform === "win32" && /\.(?:cmd|bat)$/i.test(command);
|
|
@@ -41,6 +55,7 @@ function defaultRunner(argv, cwd) {
|
|
|
41
55
|
const commandPath = command ?? "";
|
|
42
56
|
const result = spawnSync(commandPath, rest, {
|
|
43
57
|
cwd,
|
|
58
|
+
env: sanitizeTsLaneEnv(process.env),
|
|
44
59
|
stdio: "inherit",
|
|
45
60
|
shell: shouldUseShellForCommand(commandPath),
|
|
46
61
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
2
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { extractLinkTargets, shouldSkipLinkTarget } from "./link-parser.js";
|
|
4
4
|
const EXCLUDE_DIRS = new Set([
|
|
5
5
|
".git",
|
|
@@ -55,7 +55,7 @@ function findBrokenLinks(cwd) {
|
|
|
55
55
|
catch {
|
|
56
56
|
continue;
|
|
57
57
|
}
|
|
58
|
-
const rel = md.startsWith(
|
|
58
|
+
const rel = md.startsWith(root + sep) ? relative(root, md) : md;
|
|
59
59
|
const lines = text.split("\n");
|
|
60
60
|
for (let i = 0; i < lines.length; i += 1) {
|
|
61
61
|
const line = lines[i] ?? "";
|
package/dist/value/readback.d.ts
CHANGED
package/dist/value/readback.js
CHANGED
|
@@ -415,7 +415,11 @@ export function runValueShow(options) {
|
|
|
415
415
|
};
|
|
416
416
|
}
|
|
417
417
|
const windowMs = parseWindowMs(options.window);
|
|
418
|
-
const trend = computeValueShowTrend(root, {
|
|
418
|
+
const trend = computeValueShowTrend(root, {
|
|
419
|
+
windowMs,
|
|
420
|
+
logPath: options.logPath,
|
|
421
|
+
now: options.now,
|
|
422
|
+
});
|
|
419
423
|
const empty = trend.total === 0;
|
|
420
424
|
if (options.format === "json") {
|
|
421
425
|
return {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/** Canonical
|
|
2
|
-
export declare const EMITTED_VBRIEF_VERSION
|
|
1
|
+
/** Canonical envelope version emitted on new scope xBRIEFs (#533, #2318). */
|
|
2
|
+
export declare const EMITTED_VBRIEF_VERSION: "0.8";
|
|
3
3
|
/** Migrator provenance namespace under ``plan.metadata`` (#616). */
|
|
4
4
|
export declare const MIGRATOR_METADATA_KEY = "x-migrator";
|
|
5
|
-
|
|
5
|
+
/** Layout-aware PROJECT-DEFINITION paths live in ``project-definition-io.ts`` / ``policy/resolve.ts`` (#2302). */
|
|
6
6
|
export declare const DEPRECATION_SENTINEL = "<!-- deft:deprecated-redirect -->";
|
|
7
7
|
export declare const INTERNAL_REFERENCE_TYPES: Set<string>;
|
|
8
8
|
export declare const EXTERNAL_REFERENCE_TYPES: Set<string>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { VBRIEF_VERSION } from "@deftai/directive-types";
|
|
2
|
+
/** Canonical envelope version emitted on new scope xBRIEFs (#533, #2318). */
|
|
3
|
+
export const EMITTED_VBRIEF_VERSION = VBRIEF_VERSION;
|
|
3
4
|
/** Migrator provenance namespace under ``plan.metadata`` (#616). */
|
|
4
5
|
export const MIGRATOR_METADATA_KEY = "x-migrator";
|
|
5
|
-
|
|
6
|
+
/** Layout-aware PROJECT-DEFINITION paths live in ``project-definition-io.ts`` / ``policy/resolve.ts`` (#2302). */
|
|
6
7
|
export const DEPRECATION_SENTINEL = "<!-- deft:deprecated-redirect -->";
|
|
7
8
|
export const INTERNAL_REFERENCE_TYPES = new Set([
|
|
8
9
|
"x-vbrief/plan",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createScopeVbrief, referenceHasRequiredFields, referenceWithDefaultTrust, setTodayForTests, slugify, TODAY, } from "./build.js";
|
|
2
|
-
export { DEFAULT_STATUS_FOR_FOLDER, DEPRECATION_SENTINEL, EMITTED_VBRIEF_VERSION, EXTERNAL_REFERENCE_TYPES, FOLDER_TO_STATUSES, INTERNAL_REFERENCE_TYPES, LIFECYCLE_FOLDERS, MIGRATOR_METADATA_KEY,
|
|
2
|
+
export { DEFAULT_STATUS_FOR_FOLDER, DEPRECATION_SENTINEL, EMITTED_VBRIEF_VERSION, EXTERNAL_REFERENCE_TYPES, FOLDER_TO_STATUSES, INTERNAL_REFERENCE_TYPES, LIFECYCLE_FOLDERS, MIGRATOR_METADATA_KEY, STATUS_TO_FOLDER, } from "./constants.js";
|
|
3
3
|
export { pythonJsonPretty } from "./json.js";
|
|
4
4
|
export { cmdVbriefBuild, run, usage } from "./main.js";
|
|
5
5
|
export { PARITY_SCENARIO_NAMES, renderScenarioOutput, runParityScenario, SAMPLE_PROJECT_MD, SAMPLE_ROADMAP_MD, SAMPLE_SPEC_MD, SAMPLE_SPEC_VBRIEF, } from "./parity-scenarios.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createScopeVbrief, referenceHasRequiredFields, referenceWithDefaultTrust, setTodayForTests, slugify, TODAY, } from "./build.js";
|
|
2
|
-
export { DEFAULT_STATUS_FOR_FOLDER, DEPRECATION_SENTINEL, EMITTED_VBRIEF_VERSION, EXTERNAL_REFERENCE_TYPES, FOLDER_TO_STATUSES, INTERNAL_REFERENCE_TYPES, LIFECYCLE_FOLDERS, MIGRATOR_METADATA_KEY,
|
|
2
|
+
export { DEFAULT_STATUS_FOR_FOLDER, DEPRECATION_SENTINEL, EMITTED_VBRIEF_VERSION, EXTERNAL_REFERENCE_TYPES, FOLDER_TO_STATUSES, INTERNAL_REFERENCE_TYPES, LIFECYCLE_FOLDERS, MIGRATOR_METADATA_KEY, STATUS_TO_FOLDER, } from "./constants.js";
|
|
3
3
|
export { pythonJsonPretty } from "./json.js";
|
|
4
4
|
export { cmdVbriefBuild, run, usage } from "./main.js";
|
|
5
5
|
export { PARITY_SCENARIO_NAMES, renderScenarioOutput, runParityScenario, SAMPLE_PROJECT_MD, SAMPLE_ROADMAP_MD, SAMPLE_SPEC_MD, SAMPLE_SPEC_VBRIEF, } from "./parity-scenarios.js";
|
|
@@ -20,16 +20,31 @@ export const CONSUMER_TOOLS = [
|
|
|
20
20
|
export const TOOLS = MAINTAINER_TOOLS;
|
|
21
21
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
22
22
|
export function defaultCommandRunner(command, timeoutMs) {
|
|
23
|
+
const bin = command[0] ?? "";
|
|
24
|
+
const args = command.slice(1);
|
|
25
|
+
const trySpawn = (shell) => childProcess.execFileSync(bin, args, {
|
|
26
|
+
encoding: "utf8",
|
|
27
|
+
timeout: timeoutMs,
|
|
28
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
29
|
+
shell,
|
|
30
|
+
});
|
|
23
31
|
try {
|
|
24
|
-
const stdout =
|
|
25
|
-
encoding: "utf8",
|
|
26
|
-
timeout: timeoutMs,
|
|
27
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
28
|
-
});
|
|
32
|
+
const stdout = trySpawn(false);
|
|
29
33
|
return { returncode: 0, stdout: typeof stdout === "string" ? stdout : "", stderr: "" };
|
|
30
34
|
}
|
|
31
35
|
catch (err) {
|
|
32
36
|
const e = err;
|
|
37
|
+
// win32: npm global shims are `.cmd` and need shell/PATHEXT (#2467 / #2415).
|
|
38
|
+
if (e.code === "ENOENT" && process.platform === "win32") {
|
|
39
|
+
try {
|
|
40
|
+
const stdout = trySpawn(true);
|
|
41
|
+
return { returncode: 0, stdout: typeof stdout === "string" ? stdout : "", stderr: "" };
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Shell still failed — binary is absent (cmd.exe often exits 1, not ENOENT).
|
|
45
|
+
return { error: "not-found", message: "" };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
33
48
|
if (e.code === "ENOENT") {
|
|
34
49
|
return { error: "not-found", message: "" };
|
|
35
50
|
}
|
|
@@ -5,6 +5,7 @@ export interface EvaluateResult {
|
|
|
5
5
|
readonly stream: OutputStream;
|
|
6
6
|
}
|
|
7
7
|
export declare const REQUIRED_HOOKS: readonly ["pre-commit", "pre-push"];
|
|
8
|
+
export declare const REQUIRED_HOOK_SUPPORT_FILES: readonly ["_deft-run.sh"];
|
|
8
9
|
/** Substrings each hook must contain when it dispatches through the deft CLI (#2049). */
|
|
9
10
|
export declare const PRE_COMMIT_DEFT_COMMANDS: readonly ["verify:branch", "verify:encoding"];
|
|
10
11
|
export declare const PRE_PUSH_DEFT_COMMANDS: readonly ["preflight-gh"];
|
|
@@ -15,6 +16,7 @@ export type GitConfigReader = (projectRoot: string) => {
|
|
|
15
16
|
export interface EvaluateOptions {
|
|
16
17
|
readonly gitConfigReader?: GitConfigReader;
|
|
17
18
|
readonly platform?: NodeJS.Platform;
|
|
19
|
+
readonly hookExecutable?: (hookPath: string) => boolean;
|
|
18
20
|
}
|
|
19
21
|
/** Drop shell ``#`` comment lines before pattern scans (#2049 shipped-hook false positives). */
|
|
20
22
|
export declare function stripShellCommentLines(content: string): string;
|
|
@@ -2,6 +2,7 @@ import * as childProcess from "node:child_process";
|
|
|
2
2
|
import { accessSync, constants, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
export const REQUIRED_HOOKS = ["pre-commit", "pre-push"];
|
|
5
|
+
export const REQUIRED_HOOK_SUPPORT_FILES = ["_deft-run.sh"];
|
|
5
6
|
/** Substrings each hook must contain when it dispatches through the deft CLI (#2049). */
|
|
6
7
|
export const PRE_COMMIT_DEFT_COMMANDS = ["verify:branch", "verify:encoding"];
|
|
7
8
|
export const PRE_PUSH_DEFT_COMMANDS = ["preflight-gh"];
|
|
@@ -52,7 +53,7 @@ function isDirectory(path) {
|
|
|
52
53
|
function isPosix(platform) {
|
|
53
54
|
return platform !== "win32";
|
|
54
55
|
}
|
|
55
|
-
function
|
|
56
|
+
function defaultHookExecutable(hookPath) {
|
|
56
57
|
try {
|
|
57
58
|
accessSync(hookPath, constants.X_OK);
|
|
58
59
|
return true;
|
|
@@ -112,6 +113,7 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
112
113
|
const root = resolve(projectRoot);
|
|
113
114
|
const gitReader = options.gitConfigReader ?? defaultGitConfigReader;
|
|
114
115
|
const platform = options.platform ?? process.platform;
|
|
116
|
+
const hookExecutable = options.hookExecutable ?? defaultHookExecutable;
|
|
115
117
|
if (!isDirectory(root)) {
|
|
116
118
|
return {
|
|
117
119
|
code: 2,
|
|
@@ -173,29 +175,29 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
173
175
|
};
|
|
174
176
|
}
|
|
175
177
|
}
|
|
178
|
+
const contentIssues = [];
|
|
179
|
+
const missingSupport = REQUIRED_HOOK_SUPPORT_FILES.filter((h) => !isFile(join(hooksDir, h)));
|
|
180
|
+
if (missingSupport.length > 0) {
|
|
181
|
+
contentIssues.push(`${hooksDir} is missing ${missingSupport.join(", ")} helper(s); pre-commit/pre-push source them at runtime`);
|
|
182
|
+
}
|
|
176
183
|
const preCommitIssue = validateHookContent("pre-commit", readHookContent(join(hooksDir, "pre-commit")), PRE_COMMIT_DEFT_COMMANDS);
|
|
177
184
|
if (preCommitIssue) {
|
|
178
|
-
|
|
179
|
-
code: 1,
|
|
180
|
-
message: `❌ deft hooks wired but NON-FUNCTIONAL: ${preCommitIssue} (#2049).\n` +
|
|
181
|
-
" Recovery: re-run the deft installer / `task setup` to refresh .githooks/.",
|
|
182
|
-
stream: "stderr",
|
|
183
|
-
};
|
|
185
|
+
contentIssues.push(`${preCommitIssue} (#2049)`);
|
|
184
186
|
}
|
|
185
187
|
const prePushContent = readHookContent(join(hooksDir, "pre-push"));
|
|
186
188
|
const prePushIssue = validateHookContent("pre-push", prePushContent, PRE_PUSH_DEFT_COMMANDS);
|
|
187
189
|
if (prePushIssue) {
|
|
188
|
-
|
|
189
|
-
code: 1,
|
|
190
|
-
message: `❌ deft hooks wired but NON-FUNCTIONAL: ${prePushIssue} (#2049).\n` +
|
|
191
|
-
" Recovery: re-run the deft installer / `task setup` to refresh .githooks/.",
|
|
192
|
-
stream: "stderr",
|
|
193
|
-
};
|
|
190
|
+
contentIssues.push(`${prePushIssue} (#2049)`);
|
|
194
191
|
}
|
|
195
192
|
if (prePushContent && prePushInvokesVerifyBranch(prePushContent)) {
|
|
193
|
+
contentIssues.push("pre-push must not invoke verify:branch (#1814)");
|
|
194
|
+
}
|
|
195
|
+
if (contentIssues.length > 0) {
|
|
196
196
|
return {
|
|
197
197
|
code: 1,
|
|
198
|
-
message: "❌ deft hooks wired but NON-FUNCTIONAL
|
|
198
|
+
message: "❌ deft hooks wired but NON-FUNCTIONAL:\n" +
|
|
199
|
+
contentIssues.map((issue) => ` - ${issue.replace(/\r?\n/g, " ")}`).join("\n") +
|
|
200
|
+
"\n" +
|
|
199
201
|
" Recovery: re-run the deft installer / `task setup` to refresh .githooks/.",
|
|
200
202
|
stream: "stderr",
|
|
201
203
|
};
|
|
@@ -203,7 +205,7 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
203
205
|
return {
|
|
204
206
|
code: 0,
|
|
205
207
|
message: `✓ deft hooks installed and functional: core.hooksPath=${hooksPath}, ` +
|
|
206
|
-
`hooks ${REQUIRED_HOOKS.join(", ")} present and dispatch via deft CLI (#2049).`,
|
|
208
|
+
`hooks ${REQUIRED_HOOKS.join(", ")} plus ${REQUIRED_HOOK_SUPPORT_FILES.join(", ")} present and dispatch via deft CLI (#2049).`,
|
|
207
209
|
stream: "stdout",
|
|
208
210
|
};
|
|
209
211
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.76.0",
|
|
4
4
|
"description": "TypeScript engine core for the Directive framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
"types": "./dist/agents-md-advisory/index.d.ts",
|
|
39
39
|
"default": "./dist/agents-md-advisory/index.js"
|
|
40
40
|
},
|
|
41
|
+
"./eval-health-relocation": {
|
|
42
|
+
"types": "./dist/eval-health-relocation/index.d.ts",
|
|
43
|
+
"default": "./dist/eval-health-relocation/index.js"
|
|
44
|
+
},
|
|
41
45
|
"./xbrief-migrate": {
|
|
42
46
|
"types": "./dist/xbrief-migrate/index.d.ts",
|
|
43
47
|
"default": "./dist/xbrief-migrate/index.js"
|
|
@@ -281,8 +285,8 @@
|
|
|
281
285
|
"provenance": true
|
|
282
286
|
},
|
|
283
287
|
"dependencies": {
|
|
284
|
-
"@deftai/directive-content": "^0.
|
|
285
|
-
"@deftai/directive-types": "^0.
|
|
288
|
+
"@deftai/directive-content": "^0.76.0",
|
|
289
|
+
"@deftai/directive-types": "^0.76.0",
|
|
286
290
|
"archiver": "^8.0.0"
|
|
287
291
|
},
|
|
288
292
|
"scripts": {
|