@deftai/directive-core 0.82.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/cursor-hooks.d.ts +23 -0
- package/dist/hooks/cursor-hooks.js +95 -0
- package/dist/hooks/dispatcher.d.ts +10 -1
- package/dist/hooks/dispatcher.js +66 -6
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/init-deposit/agent-hooks.d.ts +4 -2
- package/dist/init-deposit/agent-hooks.js +137 -14
- package/dist/init-deposit/gitignore.js +1 -0
- package/dist/init-deposit/hygiene.js +1 -0
- package/dist/lifecycle/events.d.ts +1 -0
- package/dist/lifecycle/events.js +59 -9
- package/dist/policy/host-hooks.d.ts +21 -0
- package/dist/policy/host-hooks.js +96 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +15 -1
- package/dist/product-signal/consent.d.ts +20 -3
- package/dist/product-signal/consent.js +63 -6
- package/dist/product-signal/gates.d.ts +1 -1
- package/dist/product-signal/submit.d.ts +5 -1
- package/dist/product-signal/submit.js +42 -18
- 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/dist/vbrief-validate/plan-hooks.d.ts +2 -0
- package/dist/vbrief-validate/plan-hooks.js +25 -0
- package/dist/verify-env/agent-hooks.d.ts +2 -1
- package/dist/verify-env/agent-hooks.js +3 -2
- package/package.json +7 -3
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
import { DEFAULT_PRODUCT_SIGNAL_SINK_REPO } from "../policy/product-signal.js";
|
|
4
5
|
import { platformUserConfigDir } from "../user-config/resolve-user-md.js";
|
|
5
6
|
export const PRODUCT_SIGNAL_CONSENT_FILENAME = "product-signal-consent.json";
|
|
6
|
-
/** Consent record schema version (#2693 D2). */
|
|
7
|
-
export const PRODUCT_SIGNAL_CONSENT_VERSION =
|
|
7
|
+
/** Consent record schema version (#2693 D2, #2767 v2 sink binding). */
|
|
8
|
+
export const PRODUCT_SIGNAL_CONSENT_VERSION = 2;
|
|
9
|
+
/** Legacy consent schema — authorizes default sink only (#2767). */
|
|
10
|
+
const PRODUCT_SIGNAL_CONSENT_VERSION_V1 = 1;
|
|
8
11
|
/** Phase-1 consent tier permitting qualitative outbound (#2693 D2). */
|
|
9
12
|
export const PRODUCT_SIGNAL_CONSENT_TIER = "product-signal";
|
|
10
13
|
function resolveHomeDirForConsent(options) {
|
|
@@ -32,25 +35,76 @@ export function resolveProductSignalConsentPath(options = {}) {
|
|
|
32
35
|
const homeDir = resolveHomeDirForConsent(options);
|
|
33
36
|
return join(platformUserConfigDir(platform, env, homeDir), PRODUCT_SIGNAL_CONSENT_FILENAME);
|
|
34
37
|
}
|
|
38
|
+
/** Normalize sinkRepo to lowercase owner/repo (#2767). */
|
|
39
|
+
export function normalizeProductSignalSinkRepo(raw) {
|
|
40
|
+
const sink = raw.trim().replace(/^https?:\/\/github\.com\//i, "");
|
|
41
|
+
return sink.replace(/\/+$/, "").toLowerCase();
|
|
42
|
+
}
|
|
35
43
|
function parseConsentRecord(raw) {
|
|
36
44
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
37
45
|
return null;
|
|
38
46
|
}
|
|
39
47
|
const rec = raw;
|
|
40
|
-
if (typeof rec.consentVersion !== "number" ||
|
|
48
|
+
if (typeof rec.consentVersion !== "number" ||
|
|
49
|
+
typeof rec.grantedAt !== "string" ||
|
|
50
|
+
typeof rec.tier !== "string") {
|
|
41
51
|
return null;
|
|
42
52
|
}
|
|
43
|
-
|
|
53
|
+
const revokedAt = typeof rec.revokedAt === "string" ? rec.revokedAt : undefined;
|
|
54
|
+
let sinkRepo;
|
|
55
|
+
if (rec.consentVersion >= PRODUCT_SIGNAL_CONSENT_VERSION) {
|
|
56
|
+
if (typeof rec.sinkRepo !== "string" || rec.sinkRepo.trim().length === 0) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
sinkRepo = normalizeProductSignalSinkRepo(rec.sinkRepo);
|
|
60
|
+
if (sinkRepo.length === 0) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (rec.consentVersion !== PRODUCT_SIGNAL_CONSENT_VERSION_V1) {
|
|
44
65
|
return null;
|
|
45
66
|
}
|
|
46
|
-
const revokedAt = typeof rec.revokedAt === "string" ? rec.revokedAt : undefined;
|
|
47
67
|
return {
|
|
48
68
|
consentVersion: rec.consentVersion,
|
|
49
69
|
grantedAt: rec.grantedAt,
|
|
50
70
|
tier: rec.tier,
|
|
71
|
+
sinkRepo,
|
|
51
72
|
revokedAt,
|
|
52
73
|
};
|
|
53
74
|
}
|
|
75
|
+
/** Resolve the sink authorized by a consent record (#2767). */
|
|
76
|
+
export function resolveConsentedProductSignalSink(consent) {
|
|
77
|
+
if (consent === null) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (consent.consentVersion >= PRODUCT_SIGNAL_CONSENT_VERSION) {
|
|
81
|
+
return consent.sinkRepo ?? null;
|
|
82
|
+
}
|
|
83
|
+
if (consent.consentVersion === PRODUCT_SIGNAL_CONSENT_VERSION_V1) {
|
|
84
|
+
return normalizeProductSignalSinkRepo(DEFAULT_PRODUCT_SIGNAL_SINK_REPO);
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
/** Authorize configured sink against install consent (#2767). */
|
|
89
|
+
export function authorizeProductSignalSink(configuredSink, consent) {
|
|
90
|
+
const configured = normalizeProductSignalSinkRepo(configuredSink);
|
|
91
|
+
const consented = resolveConsentedProductSignalSink(consent);
|
|
92
|
+
const sinksMatch = consented !== null && configured === consented;
|
|
93
|
+
let message = "sink authorized";
|
|
94
|
+
if (!sinksMatch) {
|
|
95
|
+
message =
|
|
96
|
+
consented === null
|
|
97
|
+
? "product-signal requires consent (`task product-signal:consent -- --grant`)."
|
|
98
|
+
: `product-signal skipped (sink-unconsented): configured sink=${configured} does not match consented sink=${consented}. Re-run \`task product-signal:consent -- --grant\` after confirming the destination.`;
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
authorized: sinksMatch,
|
|
102
|
+
configuredSink: configured,
|
|
103
|
+
consentedSink: consented,
|
|
104
|
+
sinksMatch,
|
|
105
|
+
message,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
54
108
|
/** Read consent file; returns null when absent, invalid, or revoked. */
|
|
55
109
|
export function readProductSignalConsent(options = {}) {
|
|
56
110
|
const path = resolveProductSignalConsentPath(options);
|
|
@@ -76,13 +130,16 @@ export function readProductSignalConsent(options = {}) {
|
|
|
76
130
|
export function isProductSignalConsented(options = {}) {
|
|
77
131
|
return readProductSignalConsent(options) !== null;
|
|
78
132
|
}
|
|
79
|
-
/** Write a fresh consent grant (#2693 D17 yes path). */
|
|
133
|
+
/** Write a fresh consent grant (#2693 D17 yes path, #2767 v2 sink binding). */
|
|
80
134
|
export function grantProductSignalConsent(options = {}) {
|
|
81
135
|
const now = options.now ?? new Date();
|
|
136
|
+
const normalizedSink = normalizeProductSignalSinkRepo((options.sinkRepo ?? DEFAULT_PRODUCT_SIGNAL_SINK_REPO).trim());
|
|
137
|
+
const sinkRepo = normalizedSink || normalizeProductSignalSinkRepo(DEFAULT_PRODUCT_SIGNAL_SINK_REPO);
|
|
82
138
|
const record = {
|
|
83
139
|
consentVersion: PRODUCT_SIGNAL_CONSENT_VERSION,
|
|
84
140
|
grantedAt: now.toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
85
141
|
tier: PRODUCT_SIGNAL_CONSENT_TIER,
|
|
142
|
+
sinkRepo,
|
|
86
143
|
};
|
|
87
144
|
const path = resolveProductSignalConsentPath(options);
|
|
88
145
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ProductSignalOutcome = "submitted" | "dry-run" | "disabled" | "no-consent" | "no-network" | "non-interactive" | "sink-unreachable" | "sink-unauthorized" | "validation" | "error-config";
|
|
1
|
+
export type ProductSignalOutcome = "submitted" | "dry-run" | "disabled" | "no-consent" | "no-network" | "non-interactive" | "sink-unconsented" | "sink-unreachable" | "sink-unauthorized" | "validation" | "error-config";
|
|
2
2
|
export interface GateEvaluation {
|
|
3
3
|
readonly allowed: boolean;
|
|
4
4
|
readonly outcome: ProductSignalOutcome;
|
|
@@ -32,7 +32,11 @@ export declare function runProductSignalEnable(projectRoot: string | null, confi
|
|
|
32
32
|
exitCode: 0 | 1 | 2;
|
|
33
33
|
text: string;
|
|
34
34
|
};
|
|
35
|
-
export
|
|
35
|
+
export interface ProductSignalConsentRunOptions {
|
|
36
|
+
readonly action: "grant" | "revoke";
|
|
37
|
+
readonly projectRoot?: string | null;
|
|
38
|
+
}
|
|
39
|
+
export declare function runProductSignalConsent(options: ProductSignalConsentRunOptions): {
|
|
36
40
|
exitCode: 0 | 1;
|
|
37
41
|
text: string;
|
|
38
42
|
};
|
|
@@ -3,7 +3,7 @@ import { join, resolve } from "node:path";
|
|
|
3
3
|
import { enableProductSignal, formatProductSignalStatusLine, resolveProductSignal, } from "../policy/product-signal.js";
|
|
4
4
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
5
5
|
import { resolveActorName } from "./actor-name.js";
|
|
6
|
-
import {
|
|
6
|
+
import { authorizeProductSignalSink, grantProductSignalConsent, readProductSignalConsent, revokeProductSignalConsent, } from "./consent.js";
|
|
7
7
|
import { evaluateProductSignalGates } from "./gates.js";
|
|
8
8
|
import { GitHubPrivateSinkAdapter } from "./github-private-sink-adapter.js";
|
|
9
9
|
import { collectInstallContext } from "./install-context.js";
|
|
@@ -105,15 +105,25 @@ export async function submitProductSignal(options) {
|
|
|
105
105
|
payload,
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
+
const policy = resolveProductSignal(root);
|
|
109
|
+
const consent = readProductSignalConsent();
|
|
110
|
+
const sinkAuth = authorizeProductSignalSink(policy.sinkRepo, consent);
|
|
111
|
+
if (!sinkAuth.authorized) {
|
|
112
|
+
return {
|
|
113
|
+
outcome: "sink-unconsented",
|
|
114
|
+
exitCode: 0,
|
|
115
|
+
message: `${sinkAuth.message}\n`,
|
|
116
|
+
payload,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
108
119
|
if (options.dryRun) {
|
|
109
120
|
return {
|
|
110
121
|
outcome: "dry-run",
|
|
111
122
|
exitCode: 0,
|
|
112
|
-
message: `[dry-run] payload valid for ${payload.surface}\n`,
|
|
123
|
+
message: `[dry-run] payload valid for ${payload.surface} (sink=${sinkAuth.configuredSink})\n`,
|
|
113
124
|
payload,
|
|
114
125
|
};
|
|
115
126
|
}
|
|
116
|
-
const policy = resolveProductSignal(root);
|
|
117
127
|
const adapter = new GitHubPrivateSinkAdapter({ sinkRepo: policy.sinkRepo });
|
|
118
128
|
const result = await adapter.submit(payload, { gapText: options.gapText });
|
|
119
129
|
if (result.outcome === "submitted") {
|
|
@@ -131,11 +141,13 @@ export async function submitProductSignal(options) {
|
|
|
131
141
|
export function runProductSignalStatus(projectRoot) {
|
|
132
142
|
const root = resolveProjectRoot(projectRoot ?? undefined) ?? process.cwd();
|
|
133
143
|
const policy = resolveProductSignal(root);
|
|
134
|
-
const
|
|
144
|
+
const consent = readProductSignalConsent();
|
|
145
|
+
const configuredSink = policy.sinkRepo;
|
|
146
|
+
const sinkAuth = authorizeProductSignalSink(configuredSink, consent);
|
|
135
147
|
const last = readLastSubmitSummary(root);
|
|
136
148
|
const lines = [
|
|
137
149
|
formatProductSignalStatusLine(policy),
|
|
138
|
-
`[deft product-signal] consented=${String(
|
|
150
|
+
`[deft product-signal] consented=${String(consent !== null)} configuredSink=${configuredSink} consentedSink=${sinkAuth.consentedSink ?? "none"} sinksMatch=${String(sinkAuth.sinksMatch)}`,
|
|
139
151
|
last ? `[deft product-signal] ${last}` : "[deft product-signal] last submit: none",
|
|
140
152
|
];
|
|
141
153
|
return { exitCode: 0, text: `${lines.join("\n")}\n` };
|
|
@@ -148,12 +160,15 @@ export function runProductSignalEnable(projectRoot, confirm) {
|
|
|
148
160
|
const result = enableProductSignal(root, { confirm });
|
|
149
161
|
return { exitCode: result.exitCode, text: result.stdout };
|
|
150
162
|
}
|
|
151
|
-
export function runProductSignalConsent(
|
|
152
|
-
if (action === "grant") {
|
|
153
|
-
const
|
|
163
|
+
export function runProductSignalConsent(options) {
|
|
164
|
+
if (options.action === "grant") {
|
|
165
|
+
const root = resolveProjectRoot(options.projectRoot ?? undefined);
|
|
166
|
+
const sinkRepo = root !== null ? resolveProductSignal(root).sinkRepo : undefined;
|
|
167
|
+
const record = grantProductSignalConsent({ sinkRepo });
|
|
154
168
|
return {
|
|
155
169
|
exitCode: 0,
|
|
156
|
-
text: `product-signal consent granted (tier=${record.tier}, version=${record.consentVersion}
|
|
170
|
+
text: `product-signal consent granted (tier=${record.tier}, version=${record.consentVersion}, ` +
|
|
171
|
+
`sinkRepo=${record.sinkRepo ?? "unknown"}).\n`,
|
|
157
172
|
};
|
|
158
173
|
}
|
|
159
174
|
const ok = revokeProductSignalConsent();
|
|
@@ -216,24 +231,29 @@ export function parseProductSignalSubmitArgs(argv) {
|
|
|
216
231
|
}
|
|
217
232
|
return { surface, dryRun, json, projectRoot, nps };
|
|
218
233
|
}
|
|
234
|
+
function parseOptionalProjectRootArg(argv, fallback) {
|
|
235
|
+
const rootIdx = argv.indexOf("--project-root");
|
|
236
|
+
if (rootIdx >= 0) {
|
|
237
|
+
return argv[rootIdx + 1] ?? fallback;
|
|
238
|
+
}
|
|
239
|
+
const eqArg = argv.find((a) => a.startsWith("--project-root="));
|
|
240
|
+
if (eqArg !== undefined) {
|
|
241
|
+
return eqArg.slice("--project-root=".length) || fallback;
|
|
242
|
+
}
|
|
243
|
+
return fallback;
|
|
244
|
+
}
|
|
219
245
|
/** CLI module entrypoint for dispatch (#2693). */
|
|
220
246
|
export async function productSignalMain(argv = process.argv.slice(2)) {
|
|
221
247
|
const sub = argv[0];
|
|
222
248
|
if (sub === "status") {
|
|
223
|
-
const
|
|
224
|
-
const root = rootIdx >= 0
|
|
225
|
-
? (argv[rootIdx + 1] ?? ".")
|
|
226
|
-
: (argv.find((a) => a.startsWith("--project-root="))?.split("=")[1] ?? ".");
|
|
249
|
+
const root = parseOptionalProjectRootArg(argv, ".") ?? ".";
|
|
227
250
|
const result = runProductSignalStatus(root);
|
|
228
251
|
process.stdout.write(result.text);
|
|
229
252
|
return result.exitCode;
|
|
230
253
|
}
|
|
231
254
|
if (sub === "enable") {
|
|
232
255
|
const confirm = argv.includes("--confirm");
|
|
233
|
-
const
|
|
234
|
-
const root = rootIdx >= 0
|
|
235
|
-
? (argv[rootIdx + 1] ?? ".")
|
|
236
|
-
: (argv.find((a) => a.startsWith("--project-root="))?.split("=")[1] ?? ".");
|
|
256
|
+
const root = parseOptionalProjectRootArg(argv, ".") ?? ".";
|
|
237
257
|
const result = runProductSignalEnable(root, confirm);
|
|
238
258
|
process.stdout.write(result.text);
|
|
239
259
|
return result.exitCode;
|
|
@@ -245,7 +265,11 @@ export async function productSignalMain(argv = process.argv.slice(2)) {
|
|
|
245
265
|
process.stderr.write("usage: product-signal consent -- --grant|--revoke\n");
|
|
246
266
|
return 1;
|
|
247
267
|
}
|
|
248
|
-
const
|
|
268
|
+
const root = parseOptionalProjectRootArg(argv, null);
|
|
269
|
+
const result = runProductSignalConsent({
|
|
270
|
+
action: grant ? "grant" : "revoke",
|
|
271
|
+
projectRoot: root,
|
|
272
|
+
});
|
|
249
273
|
process.stdout.write(result.text);
|
|
250
274
|
return result.exitCode;
|
|
251
275
|
}
|
package/dist/scope/decompose.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { accessSync, constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
11
11
|
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
12
|
+
import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
12
13
|
import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, resolveLifecycleRoot, } from "../layout/resolve.js";
|
|
13
14
|
import { referenceWithDefaultTrust, slugify } from "../vbrief-build/build.js";
|
|
14
15
|
import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
|
|
@@ -52,7 +53,8 @@ function loadJson(path) {
|
|
|
52
53
|
}
|
|
53
54
|
return data;
|
|
54
55
|
}
|
|
55
|
-
function writeJson(path, data) {
|
|
56
|
+
function writeJson(projectRoot, path, data) {
|
|
57
|
+
assertWriteTargetSafe(projectRoot, path);
|
|
56
58
|
mkdirSync(dirname(path), { recursive: true });
|
|
57
59
|
writeFileSync(path, formatBriefJson(data), "utf8");
|
|
58
60
|
}
|
|
@@ -871,7 +873,7 @@ export function applyDecomposition(opts) {
|
|
|
871
873
|
}
|
|
872
874
|
for (let i = 0; i < childPaths.length; i += 1) {
|
|
873
875
|
// biome-ignore lint/style/noNonNullAssertion: loop bound ensures these exist
|
|
874
|
-
writeJson(childPaths[i].target, childDocs[i]);
|
|
876
|
+
writeJson(projectRoot, childPaths[i].target, childDocs[i]);
|
|
875
877
|
}
|
|
876
878
|
let parentPlan = parent.plan;
|
|
877
879
|
if (parentPlan === null || parentPlan === undefined) {
|
|
@@ -911,7 +913,7 @@ export function applyDecomposition(opts) {
|
|
|
911
913
|
planObj.references = dedupeReferences(references
|
|
912
914
|
.filter((r) => typeof r === "object" && r !== null && !Array.isArray(r))
|
|
913
915
|
.map((r) => r));
|
|
914
|
-
writeJson(parentPath, parent);
|
|
916
|
+
writeJson(projectRoot, parentPath, parent);
|
|
915
917
|
actions.push(`UPDATE ${parentRel} references`);
|
|
916
918
|
return actions;
|
|
917
919
|
}
|
|
@@ -1022,6 +1024,10 @@ export function decomposeMain(argv) {
|
|
|
1022
1024
|
process.stderr.write(`ERROR: ${err.message}\n`);
|
|
1023
1025
|
return 1;
|
|
1024
1026
|
}
|
|
1027
|
+
if (err instanceof ProjectionContainmentError) {
|
|
1028
|
+
process.stderr.write(`ERROR: ${err.message}\n`);
|
|
1029
|
+
return 2;
|
|
1030
|
+
}
|
|
1025
1031
|
process.stderr.write(`ERROR: ${String(err)}\n`);
|
|
1026
1032
|
return 1;
|
|
1027
1033
|
}
|
package/dist/session/git.d.ts
CHANGED
|
@@ -10,5 +10,7 @@ export declare function gitHead(projectRoot: string, runGit?: GitRunner): {
|
|
|
10
10
|
error: string | null;
|
|
11
11
|
};
|
|
12
12
|
export declare function worktreePath(projectRoot: string, runGit?: GitRunner): string;
|
|
13
|
+
/** True when `ancestor` is reachable from `descendant` (same commit counts). */
|
|
14
|
+
export declare function gitIsAncestor(projectRoot: string, ancestor: string, descendant: string, runGit?: GitRunner): boolean | null;
|
|
13
15
|
export declare function detectBranch(projectRoot: string, runGit?: GitRunner): string | null;
|
|
14
16
|
//# sourceMappingURL=git.d.ts.map
|
package/dist/session/git.js
CHANGED
|
@@ -35,6 +35,20 @@ export function worktreePath(projectRoot, runGit = defaultGitRunner) {
|
|
|
35
35
|
}
|
|
36
36
|
return resolve(projectRoot);
|
|
37
37
|
}
|
|
38
|
+
/** True when `ancestor` is reachable from `descendant` (same commit counts). */
|
|
39
|
+
export function gitIsAncestor(projectRoot, ancestor, descendant, runGit = defaultGitRunner) {
|
|
40
|
+
if (ancestor === descendant) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
const { code } = runGit(projectRoot, ["merge-base", "--is-ancestor", ancestor, descendant]);
|
|
44
|
+
if (code === 0) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (code === 1) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
38
52
|
export function detectBranch(projectRoot, runGit = defaultGitRunner) {
|
|
39
53
|
const sym = runGit(projectRoot, ["symbolic-ref", "--short", "HEAD"]);
|
|
40
54
|
if (sym.code === 0 && sym.stdout.trim()) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { formatFrameworkCommand } from "../render/framework-commands.js";
|
|
3
|
-
import { defaultGitRunner, gitHead, worktreePath } from "./git.js";
|
|
3
|
+
import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
|
|
4
4
|
import { pythonJsonDump } from "./json.js";
|
|
5
5
|
import { ENV_SESSION_POSTURE, readOnlyPostureMessage, resolveSessionPosture, ritualStateIsPostureAuthority, } from "./posture.js";
|
|
6
6
|
import { defaultRitualRunner } from "./ritual-entrypoint.js";
|
|
@@ -59,6 +59,10 @@ function runGatedStep(projectRoot, payload, stepName, runner, now) {
|
|
|
59
59
|
}
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
|
+
function headDriftRecoveryMessage() {
|
|
63
|
+
return (`session ritual state is stale because git HEAD changed discontinuously. ` +
|
|
64
|
+
`Run \`${formatFrameworkCommand(["session:start"])}\` again.`);
|
|
65
|
+
}
|
|
62
66
|
function evaluateLoadedState(projectRoot, state, input) {
|
|
63
67
|
const runGit = input.runGit ?? defaultGitRunner;
|
|
64
68
|
const { head: currentHead, error: headError } = gitHead(projectRoot, runGit);
|
|
@@ -73,10 +77,22 @@ function evaluateLoadedState(projectRoot, state, input) {
|
|
|
73
77
|
];
|
|
74
78
|
}
|
|
75
79
|
if (state.gitHead !== currentHead) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
const forward = gitIsAncestor(projectRoot, state.gitHead, currentHead, runGit);
|
|
81
|
+
if (forward === null) {
|
|
82
|
+
return [2, "could not verify git history for session ritual"];
|
|
83
|
+
}
|
|
84
|
+
if (!forward) {
|
|
85
|
+
return [1, headDriftRecoveryMessage()];
|
|
86
|
+
}
|
|
87
|
+
if (input.rebindForwardHead) {
|
|
88
|
+
const payload = { ...state.raw, git_head: currentHead };
|
|
89
|
+
try {
|
|
90
|
+
writeRitualState(projectRoot, payload);
|
|
91
|
+
}
|
|
92
|
+
catch (exc) {
|
|
93
|
+
return [2, `could not rebind session ritual git HEAD: ${String(exc)}`];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
80
96
|
}
|
|
81
97
|
const staleness = resolveSessionRitualStalenessHours(projectRoot);
|
|
82
98
|
if (staleness.source === "default-on-error") {
|
|
@@ -157,6 +173,7 @@ export function inspectSessionRitual(projectRoot, options = {}) {
|
|
|
157
173
|
tier,
|
|
158
174
|
now: options.now ?? new Date(),
|
|
159
175
|
runGit: options.runGit,
|
|
176
|
+
rebindForwardHead: false,
|
|
160
177
|
});
|
|
161
178
|
return {
|
|
162
179
|
code,
|
|
@@ -246,6 +263,7 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
246
263
|
tier: "quick",
|
|
247
264
|
now: instant,
|
|
248
265
|
runGit: options.runGit,
|
|
266
|
+
rebindForwardHead: true,
|
|
249
267
|
});
|
|
250
268
|
if (precheckCode !== 0) {
|
|
251
269
|
return {
|
|
@@ -259,6 +277,21 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
259
277
|
ritualStateRequired,
|
|
260
278
|
};
|
|
261
279
|
}
|
|
280
|
+
const reloadedAfterPrecheck = readRitualState(projectRoot);
|
|
281
|
+
state = reloadedAfterPrecheck[0];
|
|
282
|
+
err = reloadedAfterPrecheck[1];
|
|
283
|
+
if (state === null) {
|
|
284
|
+
return {
|
|
285
|
+
code: 2,
|
|
286
|
+
message: err ?? "ritual state invalid after precheck",
|
|
287
|
+
tier,
|
|
288
|
+
statePath,
|
|
289
|
+
bypassed: false,
|
|
290
|
+
wouldFailCode: null,
|
|
291
|
+
posture,
|
|
292
|
+
ritualStateRequired,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
262
295
|
const payload = { ...state.raw };
|
|
263
296
|
const gated = { ...payload.gated_steps };
|
|
264
297
|
payload.gated_steps = gated;
|
|
@@ -303,6 +336,7 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
303
336
|
tier,
|
|
304
337
|
now: instant,
|
|
305
338
|
runGit: options.runGit,
|
|
339
|
+
rebindForwardHead: true,
|
|
306
340
|
});
|
|
307
341
|
if (isBypassed) {
|
|
308
342
|
return {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { PROJECTION_CONTAINMENT_REFUSED_EXIT_CODE, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
4
5
|
import { getPlatformCapabilities } from "../intake/platform-capabilities.js";
|
|
5
6
|
import { EXIT_CONFIG_ERROR, EXIT_OK } from "./constants.js";
|
|
6
7
|
import { dispatchProviderFromRuntime, HARNESS_BOUND_PROVIDERS, ROUTING_MODE_HARNESS_DEFAULT, ROUTING_MODE_PINNED, resolveRoutingPath, SWARM_WORKER_ROLES, writeModelDecision, } from "./routing.js";
|
|
@@ -66,11 +67,21 @@ export function routingSetMain(argv = process.argv.slice(2)) {
|
|
|
66
67
|
"so only --harness-default is recordable here.\n");
|
|
67
68
|
return EXIT_CONFIG_ERROR;
|
|
68
69
|
}
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
const root = resolve(projectRoot);
|
|
71
|
+
const path = resolveRoutingPath(root);
|
|
72
|
+
try {
|
|
73
|
+
writeModelDecision(root, path, resolvedProvider, role, {
|
|
74
|
+
model,
|
|
75
|
+
mode: harnessDefault ? ROUTING_MODE_HARNESS_DEFAULT : ROUTING_MODE_PINNED,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (err instanceof ProjectionContainmentError) {
|
|
80
|
+
process.stderr.write(`ERROR: ${err.message}\n`);
|
|
81
|
+
return PROJECTION_CONTAINMENT_REFUSED_EXIT_CODE;
|
|
82
|
+
}
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
74
85
|
const modelText = model ?? "<harness default>";
|
|
75
86
|
process.stdout.write(`Recorded route: provider '${resolvedProvider}', role '${role}' -> model ${modelText}.\n` +
|
|
76
87
|
`Route file: ${path}\n`);
|
package/dist/swarm/routing.d.ts
CHANGED
|
@@ -62,5 +62,5 @@ export declare function resolveDispatchProvider(environ?: NodeJS.ProcessEnv): st
|
|
|
62
62
|
* `decidedAt` when the caller did not supply one. Used by the interactive
|
|
63
63
|
* resolver path (resolver step 5) and the `swarm:routing-set` task.
|
|
64
64
|
*/
|
|
65
|
-
export declare function writeModelDecision(path: string, provider: string, role: string, decision: RouteDecision): void;
|
|
65
|
+
export declare function writeModelDecision(projectRoot: string, path: string, provider: string, role: string, decision: RouteDecision): void;
|
|
66
66
|
//# sourceMappingURL=routing.d.ts.map
|
package/dist/swarm/routing.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { execFileSync } from "node:child_process";
|
|
17
17
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
18
18
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
19
|
+
import { assertWriteTargetSafe } from "../fs/projection-containment.js";
|
|
19
20
|
/**
|
|
20
21
|
* The fixed worker-role vocabulary (reused from #1531). No separate tier
|
|
21
22
|
* vocabulary to start; decisions are strictly per-role.
|
|
@@ -201,9 +202,10 @@ function assertSafeRoutingKey(kind, key) {
|
|
|
201
202
|
* `decidedAt` when the caller did not supply one. Used by the interactive
|
|
202
203
|
* resolver path (resolver step 5) and the `swarm:routing-set` task.
|
|
203
204
|
*/
|
|
204
|
-
export function writeModelDecision(path, provider, role, decision) {
|
|
205
|
+
export function writeModelDecision(projectRoot, path, provider, role, decision) {
|
|
205
206
|
assertSafeRoutingKey("provider", provider);
|
|
206
207
|
assertSafeRoutingKey("role", role);
|
|
208
|
+
assertWriteTargetSafe(projectRoot, path);
|
|
207
209
|
const { data } = loadRoutingFile(path);
|
|
208
210
|
// Null-prototype write targets so a computed provider/role key can only ever
|
|
209
211
|
// set an own property and can never reach `Object.prototype`, even if the
|
package/dist/value/readback.js
CHANGED
|
@@ -2,6 +2,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { runningInsideDeftRepo } from "../doctor/paths.js";
|
|
4
4
|
import { ALL_ATTRIBUTION_EVENT_NAMES } from "../events/attribution-constants.js";
|
|
5
|
+
import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
5
6
|
import { DEFAULT_EVENT_LOG, readEvents } from "../lifecycle/events.js";
|
|
6
7
|
import { policyColonInvocation } from "../policy/policy-invocation.js";
|
|
7
8
|
import { isValueFeedbackPathAllowed, resolveValueFeedback, } from "../policy/value-feedback.js";
|
|
@@ -229,10 +230,14 @@ function appendReadbackHistory(projectRoot, eventId, line, options = {}) {
|
|
|
229
230
|
line,
|
|
230
231
|
};
|
|
231
232
|
try {
|
|
233
|
+
assertWriteTargetSafe(projectRoot, path);
|
|
232
234
|
mkdirSync(join(path, ".."), { recursive: true });
|
|
233
235
|
appendFileSync(path, `${JSON.stringify(record)}\n`, "utf8");
|
|
234
236
|
}
|
|
235
|
-
catch {
|
|
237
|
+
catch (err) {
|
|
238
|
+
if (err instanceof ProjectionContainmentError) {
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
236
241
|
// observability only
|
|
237
242
|
}
|
|
238
243
|
}
|
|
@@ -6,6 +6,8 @@ export declare function validateSessionRitualStalenessHoursOnPlan(plan: unknown,
|
|
|
6
6
|
export declare function validateTriageRankingLabelsOnPlan(plan: unknown, filepath: string): string[];
|
|
7
7
|
/** vbrief_validate hook: validate ``plan.policy.runtimeAuthority`` (#1394). */
|
|
8
8
|
export declare function validateRuntimeAuthorityOnPlan(plan: unknown, filepath: string): string[];
|
|
9
|
+
/** vbrief_validate hook: validate ``plan.policy.hostHooks`` (#2752). */
|
|
10
|
+
export declare function validateHostHooksOnPlan(plan: unknown, filepath: string): string[];
|
|
9
11
|
/** vbrief_validate hook: validate ``plan.policy.stalenessTickler`` (#2489). */
|
|
10
12
|
export declare function validateStalenessTicklerOnPlan(plan: unknown, filepath: string): string[];
|
|
11
13
|
/** Run all PROJECT-DEFINITION policy hooks (mirrors lazy-import block in Python). */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateHostHooks } from "../policy/host-hooks.js";
|
|
1
2
|
import { readPlanPolicy } from "../policy/plan-extensions.js";
|
|
2
3
|
import { validateRuntimeAuthority } from "../policy/runtime-authority.js";
|
|
3
4
|
import { validateStalenessTickler } from "../policy/staleness-tickler.js";
|
|
@@ -121,6 +122,24 @@ export function validateRuntimeAuthorityOnPlan(plan, filepath) {
|
|
|
121
122
|
}
|
|
122
123
|
return out;
|
|
123
124
|
}
|
|
125
|
+
/** vbrief_validate hook: validate ``plan.policy.hostHooks`` (#2752). */
|
|
126
|
+
export function validateHostHooksOnPlan(plan, filepath) {
|
|
127
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const policy = readPlanPolicy(plan);
|
|
131
|
+
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
if (!("hostHooks" in policy)) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const err of validateHostHooks(policy.hostHooks)) {
|
|
139
|
+
out.push(`${filepath}: ${err} (#2752)`);
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
124
143
|
/** vbrief_validate hook: validate ``plan.policy.stalenessTickler`` (#2489). */
|
|
125
144
|
export function validateStalenessTicklerOnPlan(plan, filepath) {
|
|
126
145
|
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
@@ -191,6 +210,12 @@ export function runProjectDefinitionHooks(plan, filepath) {
|
|
|
191
210
|
catch {
|
|
192
211
|
/* hook must not break validation */
|
|
193
212
|
}
|
|
213
|
+
try {
|
|
214
|
+
errors.push(...validateHostHooksOnPlan(plan, filepath));
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
/* hook must not break validation */
|
|
218
|
+
}
|
|
194
219
|
return errors;
|
|
195
220
|
}
|
|
196
221
|
//# sourceMappingURL=plan-hooks.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentHookInspection } from "../init-deposit/agent-hooks.js";
|
|
2
|
+
import type { HostHooksPolicy } from "../policy/host-hooks.js";
|
|
2
3
|
import type { OutputStream } from "./verify-hooks-installed.js";
|
|
3
4
|
export interface AgentHookHealthResult {
|
|
4
5
|
readonly code: 0 | 1 | 2;
|
|
@@ -7,5 +8,5 @@ export interface AgentHookHealthResult {
|
|
|
7
8
|
readonly registrations: readonly AgentHookInspection[];
|
|
8
9
|
}
|
|
9
10
|
/** Read-only P0 agent-host registration health, independent of git hooks. */
|
|
10
|
-
export declare function evaluateAgentHooks(projectRoot: string): AgentHookHealthResult;
|
|
11
|
+
export declare function evaluateAgentHooks(projectRoot: string, hostHooksPolicy?: HostHooksPolicy): AgentHookHealthResult;
|
|
11
12
|
//# sourceMappingURL=agent-hooks.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { statSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { inspectAgentHookDeposit } from "../init-deposit/agent-hooks.js";
|
|
4
|
+
import { loadHostHooksPolicyFromProject } from "../policy/host-hooks.js";
|
|
4
5
|
function isDirectory(path) {
|
|
5
6
|
try {
|
|
6
7
|
return statSync(path).isDirectory();
|
|
@@ -10,7 +11,7 @@ function isDirectory(path) {
|
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
/** Read-only P0 agent-host registration health, independent of git hooks. */
|
|
13
|
-
export function evaluateAgentHooks(projectRoot) {
|
|
14
|
+
export function evaluateAgentHooks(projectRoot, hostHooksPolicy = loadHostHooksPolicyFromProject(projectRoot)) {
|
|
14
15
|
const root = resolve(projectRoot);
|
|
15
16
|
if (!isDirectory(root)) {
|
|
16
17
|
return {
|
|
@@ -20,7 +21,7 @@ export function evaluateAgentHooks(projectRoot) {
|
|
|
20
21
|
registrations: [],
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
|
-
const registrations = inspectAgentHookDeposit(root);
|
|
24
|
+
const registrations = inspectAgentHookDeposit(root, hostHooksPolicy);
|
|
24
25
|
const unhealthy = registrations.filter((entry) => entry.status !== "healthy");
|
|
25
26
|
if (unhealthy.length > 0) {
|
|
26
27
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.84.0",
|
|
4
4
|
"description": "TypeScript engine core for the Directive framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -86,6 +86,10 @@
|
|
|
86
86
|
"types": "./dist/cache/index.d.ts",
|
|
87
87
|
"default": "./dist/cache/index.js"
|
|
88
88
|
},
|
|
89
|
+
"./cache/task-cache": {
|
|
90
|
+
"types": "./dist/cache/task-cache/index.d.ts",
|
|
91
|
+
"default": "./dist/cache/task-cache/index.js"
|
|
92
|
+
},
|
|
89
93
|
"./doctor": {
|
|
90
94
|
"types": "./dist/doctor/index.d.ts",
|
|
91
95
|
"default": "./dist/doctor/index.js"
|
|
@@ -313,8 +317,8 @@
|
|
|
313
317
|
"provenance": true
|
|
314
318
|
},
|
|
315
319
|
"dependencies": {
|
|
316
|
-
"@deftai/directive-content": "^0.
|
|
317
|
-
"@deftai/directive-types": "^0.
|
|
320
|
+
"@deftai/directive-content": "^0.84.0",
|
|
321
|
+
"@deftai/directive-types": "^0.84.0",
|
|
318
322
|
"archiver": "^8.0.0"
|
|
319
323
|
},
|
|
320
324
|
"scripts": {
|