@deftai/directive-core 0.69.0 → 0.71.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/branch/evaluate.js +7 -0
- package/dist/deposit/contain.d.ts +41 -0
- package/dist/deposit/contain.js +113 -0
- package/dist/deposit/copy-tree.js +8 -2
- package/dist/doctor/checks.d.ts +11 -0
- package/dist/doctor/checks.js +75 -2
- package/dist/doctor/doctor-state.js +2 -2
- package/dist/doctor/main.js +54 -1
- package/dist/doctor/manifest.d.ts +20 -0
- package/dist/doctor/manifest.js +22 -0
- package/dist/doctor/types.d.ts +8 -0
- package/dist/eval/crud-telemetry.d.ts +59 -0
- package/dist/eval/crud-telemetry.js +307 -0
- package/dist/eval/health.d.ts +52 -0
- package/dist/eval/health.js +240 -0
- package/dist/eval/readback.d.ts +38 -0
- package/dist/eval/readback.js +209 -0
- package/dist/eval/report.d.ts +53 -0
- package/dist/eval/report.js +161 -0
- package/dist/eval/run.d.ts +79 -0
- package/dist/eval/run.js +309 -0
- package/dist/events/attribution-constants.d.ts +13 -0
- package/dist/events/attribution-constants.js +18 -0
- package/dist/events/attribution-ledger.d.ts +43 -0
- package/dist/events/attribution-ledger.js +61 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/init-deposit/gitignore.js +5 -5
- package/dist/init-deposit/init-deposit.js +6 -0
- package/dist/init-deposit/refresh.js +5 -0
- package/dist/intake/issue-ingest.d.ts +11 -0
- package/dist/intake/issue-ingest.js +86 -11
- package/dist/layout/resolve.d.ts +2 -2
- package/dist/layout/resolve.js +2 -2
- package/dist/lifecycle/events.js +2 -0
- package/dist/policy/index.d.ts +2 -1
- package/dist/policy/index.js +15 -2
- package/dist/policy/plan-extensions.d.ts +31 -0
- package/dist/policy/plan-extensions.js +45 -0
- package/dist/policy/value-feedback.d.ts +56 -0
- package/dist/policy/value-feedback.js +284 -0
- package/dist/policy/wip.d.ts +2 -2
- package/dist/policy/wip.js +2 -2
- package/dist/preflight-cache/evaluate.js +4 -3
- package/dist/scope/audit-log.js +3 -3
- package/dist/scope/transition.js +16 -5
- package/dist/session/session-start.js +20 -0
- package/dist/slice/record.js +3 -3
- package/dist/swarm/routing.js +10 -3
- package/dist/triage/actions/candidates-log.d.ts +4 -4
- package/dist/triage/actions/candidates-log.js +4 -7
- package/dist/triage/bootstrap/gitignore.d.ts +7 -3
- package/dist/triage/bootstrap/gitignore.js +67 -37
- package/dist/triage/bootstrap/index.d.ts +1 -1
- package/dist/triage/bootstrap/index.js +4 -3
- package/dist/triage/bulk/index.d.ts +11 -2
- package/dist/triage/bulk/index.js +43 -6
- package/dist/triage/cache-path.d.ts +39 -0
- package/dist/triage/cache-path.js +98 -0
- package/dist/triage/help/registry-data.d.ts +53 -20
- package/dist/triage/help/registry-data.js +104 -20
- package/dist/triage/index.d.ts +2 -0
- package/dist/triage/index.js +2 -0
- package/dist/triage/queue/audit.js +2 -2
- package/dist/triage/queue/cache.js +2 -2
- package/dist/triage/reconcile/reconcile.js +4 -3
- package/dist/triage/scope/cli.d.ts +2 -1
- package/dist/triage/scope/cli.js +30 -6
- package/dist/triage/scope/mutations-core.js +3 -2
- package/dist/triage/scope/mutations.d.ts +10 -0
- package/dist/triage/scope/mutations.js +22 -0
- package/dist/triage/subscribe/index.d.ts +1 -1
- package/dist/triage/subscribe/index.js +4 -3
- package/dist/triage/summary/index.d.ts +2 -2
- package/dist/triage/summary/index.js +4 -3
- package/dist/triage/summary/reconcilable.d.ts +1 -1
- package/dist/triage/summary/reconcilable.js +3 -2
- package/dist/triage/welcome/constants.d.ts +3 -4
- package/dist/triage/welcome/constants.js +3 -4
- package/dist/triage/welcome/index.d.ts +1 -0
- package/dist/triage/welcome/index.js +1 -0
- package/dist/triage/welcome/onboard.d.ts +35 -0
- package/dist/triage/welcome/onboard.js +94 -0
- package/dist/triage/welcome/prior-state.js +3 -2
- package/dist/triage/welcome/summary.js +5 -4
- package/dist/umbrella-current-shape/index.d.ts +25 -1
- package/dist/umbrella-current-shape/index.js +84 -4
- package/dist/value/adoption-emit.d.ts +14 -0
- package/dist/value/adoption-emit.js +67 -0
- package/dist/value/adoption-registry.d.ts +62 -0
- package/dist/value/adoption-registry.js +214 -0
- package/dist/value/feedback-file.d.ts +67 -0
- package/dist/value/feedback-file.js +353 -0
- package/dist/value/friction-emit.d.ts +10 -0
- package/dist/value/friction-emit.js +24 -0
- package/dist/value/readback.d.ts +86 -0
- package/dist/value/readback.js +503 -0
- package/dist/vbrief-build/project-definition-io.d.ts +6 -0
- package/dist/vbrief-build/project-definition-io.js +7 -2
- package/dist/vbrief-validate/decomposition.js +1 -1
- package/dist/vbrief-validate/precutover.js +7 -3
- package/dist/wip-cap/evaluate.js +3 -0
- package/package.json +35 -3
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
3
|
+
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
4
|
+
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
5
|
+
/** Canonical registered policy field name (matches other FIELD_* dotted paths). */
|
|
6
|
+
export const FIELD_VALUE_FEEDBACK = "plan.policy.valueFeedback";
|
|
7
|
+
/** Short alias accepted by `policy:show --field=valueFeedback` (#1709). */
|
|
8
|
+
export const FIELD_VALUE_FEEDBACK_CLI_ALIAS = "valueFeedback";
|
|
9
|
+
export const DEFAULT_VALUE_FEEDBACK_ENABLED = false;
|
|
10
|
+
/** Sub-flag defaults applied when the master flag is enabled (#1709 tiered-cost decision). */
|
|
11
|
+
export const VALUE_FEEDBACK_SUBFLAG_DEFAULTS_WHEN_ENABLED = {
|
|
12
|
+
emitEvents: true,
|
|
13
|
+
sessionLine: true,
|
|
14
|
+
upstreamPrompt: false,
|
|
15
|
+
};
|
|
16
|
+
export const VALUE_FEEDBACK_CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- enabling value feedback opts into " +
|
|
17
|
+
"attributed awareness surfaces that may consume session context and nudge budget.\n" +
|
|
18
|
+
" \u2022 Local emit-only ledger writes to the gitignored `.deft-cache/events.jsonl` " +
|
|
19
|
+
"(no network by default).\n" +
|
|
20
|
+
" \u2022 A budgeted session one-liner may appear when concrete attributed value exists.\n" +
|
|
21
|
+
" \u2022 Upstream gap-escalation prompts stay OFF unless you explicitly enable " +
|
|
22
|
+
"`upstreamPrompt` (GitHub attention + token cost).\n" +
|
|
23
|
+
" \u2022 Inspect current state: `task policy:show --field=valueFeedback`.\n" +
|
|
24
|
+
" \u2022 Reversible: set `enabled: false` under the typed policy block in PROJECT-DEFINITION.\n" +
|
|
25
|
+
" \u2022 Changes are recorded to meta/policy-changes.log for auditability.";
|
|
26
|
+
function defaultResolved(source, error = null) {
|
|
27
|
+
return {
|
|
28
|
+
enabled: DEFAULT_VALUE_FEEDBACK_ENABLED,
|
|
29
|
+
emitEvents: false,
|
|
30
|
+
sessionLine: false,
|
|
31
|
+
upstreamPrompt: false,
|
|
32
|
+
source,
|
|
33
|
+
error,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function readSubFlag(block, key, masterEnabled) {
|
|
37
|
+
if (!masterEnabled) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (key in block && typeof block[key] === "boolean") {
|
|
41
|
+
return block[key];
|
|
42
|
+
}
|
|
43
|
+
return VALUE_FEEDBACK_SUBFLAG_DEFAULTS_WHEN_ENABLED[key];
|
|
44
|
+
}
|
|
45
|
+
/** Validate a `plan.policy.valueFeedback` payload. */
|
|
46
|
+
export function validateValueFeedback(value) {
|
|
47
|
+
if (value === null || value === undefined) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
51
|
+
return [`${FIELD_VALUE_FEEDBACK} must be an object; got ${typeof value}`];
|
|
52
|
+
}
|
|
53
|
+
const rec = value;
|
|
54
|
+
const errors = [];
|
|
55
|
+
for (const key of ["enabled", "emitEvents", "sessionLine", "upstreamPrompt"]) {
|
|
56
|
+
if (key in rec && typeof rec[key] !== "boolean") {
|
|
57
|
+
errors.push(`${FIELD_VALUE_FEEDBACK}.${key} must be a boolean`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return errors;
|
|
61
|
+
}
|
|
62
|
+
function resolveFromPolicyBlock(raw) {
|
|
63
|
+
const errors = validateValueFeedback(raw);
|
|
64
|
+
if (errors.length > 0) {
|
|
65
|
+
return defaultResolved("default-on-error", errors[0] ?? "invalid valueFeedback block");
|
|
66
|
+
}
|
|
67
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
68
|
+
return defaultResolved("default");
|
|
69
|
+
}
|
|
70
|
+
const block = raw;
|
|
71
|
+
const enabled = typeof block.enabled === "boolean" ? block.enabled : DEFAULT_VALUE_FEEDBACK_ENABLED;
|
|
72
|
+
if (!enabled) {
|
|
73
|
+
return {
|
|
74
|
+
enabled: false,
|
|
75
|
+
emitEvents: false,
|
|
76
|
+
sessionLine: false,
|
|
77
|
+
upstreamPrompt: false,
|
|
78
|
+
source: "typed",
|
|
79
|
+
error: null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
enabled: true,
|
|
84
|
+
emitEvents: readSubFlag(block, "emitEvents", true),
|
|
85
|
+
sessionLine: readSubFlag(block, "sessionLine", true),
|
|
86
|
+
upstreamPrompt: readSubFlag(block, "upstreamPrompt", true),
|
|
87
|
+
source: "typed",
|
|
88
|
+
error: null,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** Resolve `plan.policy.valueFeedback` from PROJECT-DEFINITION (#1709). */
|
|
92
|
+
export function resolveValueFeedback(projectRoot) {
|
|
93
|
+
const [data, err] = loadProjectDefinition(projectRoot);
|
|
94
|
+
if (data === null) {
|
|
95
|
+
return defaultResolved("default-on-error", err);
|
|
96
|
+
}
|
|
97
|
+
const policyBlock = readPlanPolicy(data.plan);
|
|
98
|
+
if (typeof policyBlock !== "object" ||
|
|
99
|
+
policyBlock === null ||
|
|
100
|
+
Array.isArray(policyBlock) ||
|
|
101
|
+
!("valueFeedback" in policyBlock)) {
|
|
102
|
+
return defaultResolved("default");
|
|
103
|
+
}
|
|
104
|
+
return resolveFromPolicyBlock(policyBlock.valueFeedback);
|
|
105
|
+
}
|
|
106
|
+
/** Master gate: when `enabled` is false, every downstream path is rejected. */
|
|
107
|
+
export function isValueFeedbackPathAllowed(path, policy) {
|
|
108
|
+
if (!policy.enabled) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return policy[path];
|
|
112
|
+
}
|
|
113
|
+
/** Resolved per-path gate booleans for policy:show and enable status output. */
|
|
114
|
+
export function valueFeedbackPathGates(policy) {
|
|
115
|
+
return {
|
|
116
|
+
emitEvents: isValueFeedbackPathAllowed("emitEvents", policy),
|
|
117
|
+
sessionLine: isValueFeedbackPathAllowed("sessionLine", policy),
|
|
118
|
+
upstreamPrompt: isValueFeedbackPathAllowed("upstreamPrompt", policy),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Human-readable status line for CLI enable/show surfaces. */
|
|
122
|
+
export function formatValueFeedbackStatusLine(policy) {
|
|
123
|
+
const gates = valueFeedbackPathGates(policy);
|
|
124
|
+
return (`[deft policy] valueFeedback enabled=${String(policy.enabled)} ` +
|
|
125
|
+
`emitEvents=${String(policy.emitEvents)} (path=${String(gates.emitEvents)}) ` +
|
|
126
|
+
`sessionLine=${String(policy.sessionLine)} (path=${String(gates.sessionLine)}) ` +
|
|
127
|
+
`upstreamPrompt=${String(policy.upstreamPrompt)} (path=${String(gates.upstreamPrompt)}).`);
|
|
128
|
+
}
|
|
129
|
+
function defaultInspectorConfig() {
|
|
130
|
+
const defaults = defaultResolved("default");
|
|
131
|
+
return {
|
|
132
|
+
enabled: defaults.enabled,
|
|
133
|
+
emitEvents: defaults.emitEvents,
|
|
134
|
+
sessionLine: defaults.sessionLine,
|
|
135
|
+
upstreamPrompt: defaults.upstreamPrompt,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function buildDefaultInspectorField() {
|
|
139
|
+
return {
|
|
140
|
+
name: FIELD_VALUE_FEEDBACK,
|
|
141
|
+
current: defaultInspectorConfig(),
|
|
142
|
+
default: {
|
|
143
|
+
enabled: DEFAULT_VALUE_FEEDBACK_ENABLED,
|
|
144
|
+
emitEvents: false,
|
|
145
|
+
sessionLine: false,
|
|
146
|
+
upstreamPrompt: false,
|
|
147
|
+
},
|
|
148
|
+
source: "default",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Inspector row for `policy:show --field=valueFeedback`. */
|
|
152
|
+
export function inspectValueFeedback(data) {
|
|
153
|
+
if (data === null) {
|
|
154
|
+
return buildDefaultInspectorField();
|
|
155
|
+
}
|
|
156
|
+
const policyBlock = readPlanPolicy(data.plan);
|
|
157
|
+
if (typeof policyBlock !== "object" ||
|
|
158
|
+
policyBlock === null ||
|
|
159
|
+
Array.isArray(policyBlock) ||
|
|
160
|
+
!("valueFeedback" in policyBlock)) {
|
|
161
|
+
return buildDefaultInspectorField();
|
|
162
|
+
}
|
|
163
|
+
const resolved = resolveFromPolicyBlock(policyBlock.valueFeedback);
|
|
164
|
+
return {
|
|
165
|
+
name: FIELD_VALUE_FEEDBACK,
|
|
166
|
+
current: {
|
|
167
|
+
enabled: resolved.enabled,
|
|
168
|
+
emitEvents: resolved.emitEvents,
|
|
169
|
+
sessionLine: resolved.sessionLine,
|
|
170
|
+
upstreamPrompt: resolved.upstreamPrompt,
|
|
171
|
+
},
|
|
172
|
+
default: {
|
|
173
|
+
enabled: DEFAULT_VALUE_FEEDBACK_ENABLED,
|
|
174
|
+
emitEvents: false,
|
|
175
|
+
sessionLine: false,
|
|
176
|
+
upstreamPrompt: false,
|
|
177
|
+
},
|
|
178
|
+
source: resolved.source,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** Persist `valueFeedback.enabled=true` after capability-cost disclosure (#1709). */
|
|
182
|
+
export function enableValueFeedback(projectRoot, options) {
|
|
183
|
+
if (!options.confirm) {
|
|
184
|
+
return {
|
|
185
|
+
exitCode: 1,
|
|
186
|
+
stdout: `${VALUE_FEEDBACK_CAPABILITY_COST_DISCLOSURE}\n\n` +
|
|
187
|
+
"Re-run with --confirm to apply: task policy:enable-value-feedback -- --confirm\n",
|
|
188
|
+
changed: false,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const path = projectDefinitionPath(projectRoot);
|
|
192
|
+
try {
|
|
193
|
+
const { changed } = projectDefinitionMutationLock(projectRoot, () => {
|
|
194
|
+
const parsed = JSON.parse(readFileSync(path, { encoding: "utf8" }));
|
|
195
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
196
|
+
throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
|
|
197
|
+
}
|
|
198
|
+
const data = parsed;
|
|
199
|
+
if (typeof data.plan !== "object" || data.plan === null || Array.isArray(data.plan)) {
|
|
200
|
+
if (data.plan === undefined) {
|
|
201
|
+
data.plan = {};
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
throw new Error("PROJECT-DEFINITION 'plan' is not an object");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const plan = data.plan;
|
|
208
|
+
migrateLegacyPolicyKey(plan);
|
|
209
|
+
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
210
|
+
if (typeof existingPolicy !== "object" ||
|
|
211
|
+
existingPolicy === null ||
|
|
212
|
+
Array.isArray(existingPolicy)) {
|
|
213
|
+
if (existingPolicy === undefined) {
|
|
214
|
+
plan[PLAN_POLICY_KEY] = {};
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
throw new Error("plan.policy is not an object");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const policyBlock = plan[PLAN_POLICY_KEY];
|
|
221
|
+
const previous = policyBlock.valueFeedback;
|
|
222
|
+
const prevObj = typeof previous === "object" && previous !== null && !Array.isArray(previous)
|
|
223
|
+
? previous
|
|
224
|
+
: {};
|
|
225
|
+
const sub = options.subFlags ?? {};
|
|
226
|
+
const readPersistedSubFlag = (key) => {
|
|
227
|
+
if (key in sub && typeof sub[key] === "boolean") {
|
|
228
|
+
return sub[key];
|
|
229
|
+
}
|
|
230
|
+
if (key in prevObj && typeof prevObj[key] === "boolean") {
|
|
231
|
+
return prevObj[key];
|
|
232
|
+
}
|
|
233
|
+
return VALUE_FEEDBACK_SUBFLAG_DEFAULTS_WHEN_ENABLED[key];
|
|
234
|
+
};
|
|
235
|
+
const nextBlock = {
|
|
236
|
+
enabled: true,
|
|
237
|
+
emitEvents: readPersistedSubFlag("emitEvents"),
|
|
238
|
+
sessionLine: readPersistedSubFlag("sessionLine"),
|
|
239
|
+
upstreamPrompt: readPersistedSubFlag("upstreamPrompt"),
|
|
240
|
+
};
|
|
241
|
+
const previousNormalized = resolveFromPolicyBlock(previous);
|
|
242
|
+
const changedFlag = previousNormalized.enabled !== nextBlock.enabled ||
|
|
243
|
+
previousNormalized.emitEvents !== nextBlock.emitEvents ||
|
|
244
|
+
previousNormalized.sessionLine !== nextBlock.sessionLine ||
|
|
245
|
+
previousNormalized.upstreamPrompt !== nextBlock.upstreamPrompt;
|
|
246
|
+
policyBlock.valueFeedback = nextBlock;
|
|
247
|
+
if (changedFlag) {
|
|
248
|
+
atomicWriteProjectDefinition(path, data);
|
|
249
|
+
}
|
|
250
|
+
const actor = options.actor ?? "task policy:enable-value-feedback";
|
|
251
|
+
const note = options.note ?? "";
|
|
252
|
+
const parts = [
|
|
253
|
+
`actor=${actor}`,
|
|
254
|
+
"valueFeedback.enabled=true",
|
|
255
|
+
`emitEvents=${String(nextBlock.emitEvents)}`,
|
|
256
|
+
`sessionLine=${String(nextBlock.sessionLine)}`,
|
|
257
|
+
`upstreamPrompt=${String(nextBlock.upstreamPrompt)}`,
|
|
258
|
+
`previous=${JSON.stringify(previous ?? null)}`,
|
|
259
|
+
];
|
|
260
|
+
if (note) {
|
|
261
|
+
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
262
|
+
}
|
|
263
|
+
appendAuditLog(projectRoot, parts.join(" "));
|
|
264
|
+
return { changed: changedFlag };
|
|
265
|
+
});
|
|
266
|
+
const resolved = resolveValueFeedback(projectRoot);
|
|
267
|
+
const lines = [
|
|
268
|
+
`\u2713 ${FIELD_VALUE_FEEDBACK}.enabled=true (value-feedback ON).`,
|
|
269
|
+
changed
|
|
270
|
+
? " audit: meta/policy-changes.log updated."
|
|
271
|
+
: " no-op: value already matched (audit entry still appended for trail).",
|
|
272
|
+
formatValueFeedbackStatusLine(resolved),
|
|
273
|
+
];
|
|
274
|
+
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
278
|
+
if (message.includes("PROJECT-DEFINITION not found")) {
|
|
279
|
+
return { exitCode: 2, stdout: `\u274c ${message}\n`, changed: false };
|
|
280
|
+
}
|
|
281
|
+
return { exitCode: 2, stdout: `\u274c Config error: ${message}\n`, changed: false };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=value-feedback.js.map
|
package/dist/policy/wip.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** Framework default WIP cap (#1124 / umbrella #1119). */
|
|
2
|
-
export declare const DEFAULT_WIP_CAP =
|
|
1
|
+
/** Framework default WIP cap (#2319; raised from 10 per #1124 / umbrella #1119). */
|
|
2
|
+
export declare const DEFAULT_WIP_CAP = 20;
|
|
3
3
|
/** vBRIEF lifecycle folders that count toward the WIP set. */
|
|
4
4
|
export declare const WIP_LIFECYCLE_DIRS: readonly ["pending", "active"];
|
|
5
5
|
export type WipCapSource = "typed" | "default" | "default-on-error";
|
package/dist/policy/wip.js
CHANGED
|
@@ -3,8 +3,8 @@ import { join } from "node:path";
|
|
|
3
3
|
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
4
4
|
import { readPlanPolicy } from "./plan-extensions.js";
|
|
5
5
|
import { loadProjectDefinition } from "./resolve.js";
|
|
6
|
-
/** Framework default WIP cap (#1124 / umbrella #1119). */
|
|
7
|
-
export const DEFAULT_WIP_CAP =
|
|
6
|
+
/** Framework default WIP cap (#2319; raised from 10 per #1124 / umbrella #1119). */
|
|
7
|
+
export const DEFAULT_WIP_CAP = 20;
|
|
8
8
|
/** vBRIEF lifecycle folders that count toward the WIP set. */
|
|
9
9
|
export const WIP_LIFECYCLE_DIRS = ["pending", "active"];
|
|
10
10
|
function pythonTypeName(value) {
|
|
@@ -14,15 +14,16 @@ import { execFileSync } from "node:child_process";
|
|
|
14
14
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
15
15
|
import { join, relative } from "node:path";
|
|
16
16
|
import { probeCacheDrift } from "../cache/fetch.js";
|
|
17
|
-
import {
|
|
17
|
+
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
18
18
|
import { readPlanPolicy } from "../policy/plan-extensions.js";
|
|
19
19
|
import { latestDecisionForIssue as auditLatestDecisionForIssue } from "../triage/actions/candidates-log.js";
|
|
20
|
+
import { resolveCandidatesLogPath } from "../triage/cache-path.js";
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Public constants (mirror preflight_cache.py)
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
23
24
|
export const CACHE_DIR_NAME = ".deft-cache";
|
|
24
25
|
export const DEFAULT_SOURCE = "github-issue";
|
|
25
|
-
export const CANDIDATES_RELPATH = join("vbrief", ".
|
|
26
|
+
export const CANDIDATES_RELPATH = join("vbrief", ".triage-cache", "candidates.jsonl");
|
|
26
27
|
export const DEFAULT_MAX_AGE_HOURS = 24;
|
|
27
28
|
export const ENV_MAX_AGE_HOURS = "DEFT_CACHE_MAX_AGE_HOURS";
|
|
28
29
|
export const ENV_TRIAGE_REPO = "DEFT_TRIAGE_REPO";
|
|
@@ -335,7 +336,7 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
335
336
|
DEFAULT_MAX_AGE_HOURS;
|
|
336
337
|
const cacheRoot = join(projectRoot, CACHE_DIR_NAME);
|
|
337
338
|
// Layout-aware (#2109 part 2a): resolve under xbrief/.eval when migrated.
|
|
338
|
-
const candidatesPath =
|
|
339
|
+
const candidatesPath = resolveCandidatesLogPath(projectRoot);
|
|
339
340
|
// Step 1: Resolve repo slug
|
|
340
341
|
const resolvedRepo = resolveRepo(projectRoot, cacheRoot, source, options.repo ?? null);
|
|
341
342
|
// Step 2: Check cache dir exists
|
package/dist/scope/audit-log.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync, writeSync, } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveTriageCachePath } from "../triage/cache-path.js";
|
|
5
5
|
import { utcNowIso } from "./vbrief-json.js";
|
|
6
6
|
export class ScopeAuditLogError extends Error {
|
|
7
7
|
constructor(message) {
|
|
@@ -94,8 +94,8 @@ export function newDecisionId() {
|
|
|
94
94
|
return randomUUID();
|
|
95
95
|
}
|
|
96
96
|
export function canonicalLogPath(projectRoot) {
|
|
97
|
-
// Layout-aware (#
|
|
98
|
-
return
|
|
97
|
+
// Layout-aware (#1703): triage working-set under `.triage-cache/`.
|
|
98
|
+
return resolveTriageCachePath(resolve(projectRoot), "scope-lifecycle.jsonl");
|
|
99
99
|
}
|
|
100
100
|
function withAppendLock(logPath, fn) {
|
|
101
101
|
const lockPath = `${logPath}.lock`;
|
package/dist/scope/transition.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { InstrumentedVbriefCrud, persistCrudMetrics } from "../eval/crud-telemetry.js";
|
|
3
4
|
import { hasArtifactSuffix } from "../layout/resolve.js";
|
|
4
5
|
import { append, canonicalLogPath, newDecisionId } from "./audit-log.js";
|
|
5
6
|
import { stampCompletionMetadata } from "./capacity-stamp.js";
|
|
@@ -78,14 +79,24 @@ export function runTransition(action, filePath, now = new Date()) {
|
|
|
78
79
|
const nowIso = utcNowIso(now);
|
|
79
80
|
planObj.status = targetStatus;
|
|
80
81
|
planObj.updated = nowIso;
|
|
82
|
+
const vbriefRoot = dirname(dirname(resolvedPath));
|
|
83
|
+
const projectRoot = dirname(vbriefRoot);
|
|
81
84
|
if (act === "complete") {
|
|
82
|
-
const vbriefRoot = dirname(dirname(resolvedPath));
|
|
83
|
-
const projectRoot = dirname(vbriefRoot);
|
|
84
85
|
stampCompletionMetadata(planObj, projectRoot, nowIso);
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
+
const formatted = formatVbriefJson(data);
|
|
88
|
+
const crud = new InstrumentedVbriefCrud({ now: () => now });
|
|
89
|
+
const writeResult = crud.update(resolvedPath, formatted, { trustedWrite: true });
|
|
90
|
+
if (!writeResult.ok) {
|
|
91
|
+
return { ok: false, message: writeResult.error ?? `CRUD update failed for ${resolvedPath}` };
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
persistCrudMetrics(projectRoot, crud.getMetrics());
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* best-effort telemetry persistence */
|
|
98
|
+
}
|
|
87
99
|
if (targetFolder !== null) {
|
|
88
|
-
const vbriefRoot = dirname(dirname(resolvedPath));
|
|
89
100
|
const destDir = join(vbriefRoot, targetFolder);
|
|
90
101
|
mkdirSync(destDir, { recursive: true });
|
|
91
102
|
const destPath = join(destDir, basename);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { runningInsideDeftRepo } from "../doctor/paths.js";
|
|
3
|
+
import { emitSessionEvalReadback } from "../eval/readback.js";
|
|
3
4
|
import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposit/migrate.js";
|
|
4
5
|
import { disclosureLine } from "../policy/disclosure.js";
|
|
5
6
|
import { resolvePolicy } from "../policy/resolve.js";
|
|
6
7
|
import { runDefaultMode } from "../triage/welcome/default-mode.js";
|
|
7
8
|
import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
|
|
9
|
+
import { emitSessionValueReadback } from "../value/readback.js";
|
|
8
10
|
import { verifyRequiredTools } from "../verify-env/verify-tools.js";
|
|
9
11
|
import { defaultGitRunner, gitHead, worktreePath } from "./git.js";
|
|
10
12
|
import { newRitualStatePayload, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
@@ -289,6 +291,24 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
289
291
|
if (!runningInsideDeftRepo(projectRoot) && shouldEmitMigrateNudge(projectRoot)) {
|
|
290
292
|
lines.push(MIGRATE_COMPLETION_NUDGE);
|
|
291
293
|
}
|
|
294
|
+
try {
|
|
295
|
+
emitSessionValueReadback(projectRoot, {
|
|
296
|
+
output: (line) => lines.push(line),
|
|
297
|
+
writeHistory: options.writeHistory !== false,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// observability only — session start must not abort on transient readback I/O
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
emitSessionEvalReadback(projectRoot, {
|
|
305
|
+
output: (line) => lines.push(line),
|
|
306
|
+
writeHistory: options.writeHistory !== false,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
// observability only — session start must not abort on transient eval readback I/O
|
|
311
|
+
}
|
|
292
312
|
const payload = newRitualStatePayload({
|
|
293
313
|
sessionId: (options.newSessionId ?? randomUUID)(),
|
|
294
314
|
gitHead: gitHeadValue,
|
package/dist/slice/record.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { appendFileSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveTriageCachePath } from "../triage/cache-path.js";
|
|
5
5
|
import { pythonJsonStringify } from "./json.js";
|
|
6
6
|
import { withAppendLock } from "./lock.js";
|
|
7
7
|
import { validateRecord } from "./validate.js";
|
|
@@ -16,7 +16,7 @@ const defaultDeps = {
|
|
|
16
16
|
withLock: withAppendLock,
|
|
17
17
|
};
|
|
18
18
|
function resolvePath(path) {
|
|
19
|
-
return path !== undefined ? resolve(path) :
|
|
19
|
+
return path !== undefined ? resolve(path) : resolveTriageCachePath(process.cwd(), "slices.jsonl");
|
|
20
20
|
}
|
|
21
21
|
export function newSliceId() {
|
|
22
22
|
return randomUUID();
|
|
@@ -134,6 +134,6 @@ export function findByUmbrella(umbrella, options = {}) {
|
|
|
134
134
|
return readAll(options).filter((record) => record.umbrella === umbrella);
|
|
135
135
|
}
|
|
136
136
|
export function slicesPath(projectRoot) {
|
|
137
|
-
return
|
|
137
|
+
return resolveTriageCachePath(projectRoot, "slices.jsonl");
|
|
138
138
|
}
|
|
139
139
|
//# sourceMappingURL=record.js.map
|
package/dist/swarm/routing.js
CHANGED
|
@@ -186,7 +186,9 @@ export function resolveDispatchProvider(environ = process.env) {
|
|
|
186
186
|
* Keys that would mutate the prototype chain rather than set an own property
|
|
187
187
|
* if used as a computed object key. Rejected for provider/role names so a
|
|
188
188
|
* malicious routing input cannot pollute `Object.prototype` (CodeQL
|
|
189
|
-
* js/prototype-polluting-assignment).
|
|
189
|
+
* js/prototype-polluting-assignment). This validation guard produces the
|
|
190
|
+
* caller-facing error; the null-prototype write targets in
|
|
191
|
+
* `writeModelDecision` are the structural backstop CodeQL recognizes.
|
|
190
192
|
*/
|
|
191
193
|
const FORBIDDEN_ROUTING_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
192
194
|
function assertSafeRoutingKey(kind, key) {
|
|
@@ -203,9 +205,14 @@ export function writeModelDecision(path, provider, role, decision) {
|
|
|
203
205
|
assertSafeRoutingKey("provider", provider);
|
|
204
206
|
assertSafeRoutingKey("role", role);
|
|
205
207
|
const { data } = loadRoutingFile(path);
|
|
206
|
-
|
|
208
|
+
// Null-prototype write targets so a computed provider/role key can only ever
|
|
209
|
+
// set an own property and can never reach `Object.prototype`, even if the
|
|
210
|
+
// validation guard above regresses. CodeQL's js/prototype-polluting-assignment
|
|
211
|
+
// barrier does not track the interprocedural `assertSafeRoutingKey` guard, so
|
|
212
|
+
// this structural sink is what closes alert #52.
|
|
213
|
+
const file = Object.assign(Object.create(null), data ?? {});
|
|
207
214
|
const existing = providerBlockOf(file, provider);
|
|
208
|
-
const block = existing ?? {};
|
|
215
|
+
const block = Object.assign(Object.create(null), existing ?? {});
|
|
209
216
|
block[role] = {
|
|
210
217
|
model: decision.model,
|
|
211
218
|
mode: decision.mode ??
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { resolveCandidatesLogPath } from "../cache-path.js";
|
|
1
2
|
import type { AuditEntry, CandidatesLog } from "./types.js";
|
|
2
|
-
/** Display/back-compat constant; resolution flows through
|
|
3
|
-
export declare const AUDIT_LOG_REL_PATH = "vbrief/.
|
|
4
|
-
|
|
5
|
-
export declare function resolveCandidatesLogPath(projectRoot: string): string;
|
|
3
|
+
/** Display/back-compat constant; resolution flows through resolveTriageCachePath (#1703). */
|
|
4
|
+
export declare const AUDIT_LOG_REL_PATH = "vbrief/.triage-cache/candidates.jsonl";
|
|
5
|
+
export { resolveCandidatesLogPath };
|
|
6
6
|
/** Read every well-formed audit-log row in insertion order (#1698 shared reader). */
|
|
7
7
|
export declare function readAuditLog(logPath: string, repo?: string | null): AuditEntry[];
|
|
8
8
|
/** Return every entry for ``(repo, issue_number)`` in insertion order. */
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveCandidatesLogPath, TRIAGE_CANDIDATES_LOG_REL_PATH } from "../cache-path.js";
|
|
5
5
|
import { CandidatesLogError } from "./errors.js";
|
|
6
|
-
/** Display/back-compat constant; resolution flows through
|
|
7
|
-
export const AUDIT_LOG_REL_PATH =
|
|
8
|
-
|
|
9
|
-
export function resolveCandidatesLogPath(projectRoot) {
|
|
10
|
-
return resolveEvalPath(projectRoot, "candidates.jsonl");
|
|
11
|
-
}
|
|
6
|
+
/** Display/back-compat constant; resolution flows through resolveTriageCachePath (#1703). */
|
|
7
|
+
export const AUDIT_LOG_REL_PATH = TRIAGE_CANDIDATES_LOG_REL_PATH;
|
|
8
|
+
export { resolveCandidatesLogPath };
|
|
12
9
|
const VALID_DECISIONS = new Set([
|
|
13
10
|
"accept",
|
|
14
11
|
"reject",
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import type { StepOutcome } from "./types.js";
|
|
2
2
|
export declare const GITIGNORE_LINE = ".deft-cache/";
|
|
3
3
|
export declare const GITIGNORE_DEFT_RUNTIME_SENTINELS: readonly string[];
|
|
4
|
+
/** Legacy static vbrief paths kept for tests referencing the pre-#1703 constant shape. */
|
|
4
5
|
export declare const GITIGNORE_EVAL_ENTRIES: readonly string[];
|
|
5
|
-
|
|
6
|
+
/** Layout-aware gitignore lines for triage working-set files (#1703). */
|
|
7
|
+
export declare function gitignoreTriageCacheEntries(projectRoot: string): readonly string[];
|
|
8
|
+
export declare function gitattributesTriageCacheGlob(projectRoot: string): string;
|
|
9
|
+
export declare const GITATTRIBUTES_EVAL_RULE = "vbrief/.triage-cache/*.jsonl merge=union";
|
|
6
10
|
export declare const FORBIDDEN_BLANKET_EVAL_LINES: readonly string[];
|
|
7
|
-
export declare const EVAL_README_BODY = "# `vbrief/.
|
|
11
|
+
export declare const EVAL_README_BODY = "# `vbrief/.triage-cache/` -- triage working-set artefacts\n\nThis directory holds the append-only JSON-lines logs that the triage and\nslicing skills emit. The framework governs which files in here are tracked\nby git versus gitignored using a **hybrid policy** (#1144, child of #1119).\n\n## Tracking policy\n\n| File | Tracked? | Why |\n| --- | --- | --- |\n| `slices.jsonl` | Yes -- **committed** | Team-shared cohort records produced by slicing skills (D13 / #1132). New operators joining the team need to see prior cohort outputs to detect orphans and avoid re-slicing the same scope. |\n| `candidates.jsonl` | No -- **gitignored** | Operator-private triage decisions (#845 Story 2). Each operator's local accept / defer / reject stream is per-machine state; sharing it would conflate operators' timing + identity across the team. Re-derive on a fresh clone via `task triage:bootstrap`. |\n| `summary-history.jsonl` | No -- **gitignored** | Operator-private observability for `task triage:summary` output time-series. Not load-bearing for any decision. |\n| `scope-lifecycle.jsonl` | No -- **gitignored** | Operator-private scope-lifecycle audit decisions (D1 / #1121). Each demote (`task scope:demote`) appends one entry including a `demote_meta` block (`was_promoted`, `original_promotion_decision_id`, `days_in_pending`, `demote_reason`, `demoted_from`). Per-operator stream; sharing would conflate operators' demote timing across the team. Lightweight metrics over this log are tracked separately at #1180. |\n| `decompositions/` | No -- **gitignored** | Temporary story-decomposition proposal drafts. These JSON drafts are local scratch artifacts, not vBRIEFs; generated child story vBRIEFs are created by `task scope:decompose` in lifecycle folders, defaulting to `vbrief/pending/`. |\n| `doctor-state.json` | No -- **gitignored** | Per-machine `task doctor` throttle state (last exit code + timestamps) persisted to gate the 24h/4h re-probe window (#1308 / #1464). Local to each clone; never committed. |\n\nThe gitignore lines live in the repo-root `.gitignore` (`vbrief/.triage-cache/candidates.jsonl`,\n`vbrief/.triage-cache/summary-history.jsonl`, `vbrief/.triage-cache/scope-lifecycle.jsonl`,\n`vbrief/.triage-cache/decompositions/`, and `vbrief/.triage-cache/doctor-state.json`). All paths\nnot listed above remain committed by default.\n\n## Fresh-clone regeneration\n\nOn a fresh clone (or any machine that has never run triage), `candidates.jsonl`\nis absent. Regenerate it with:\n\n```\ntask triage:bootstrap\n```\n\nThe bootstrap path detects the missing file, runs the auto-classifier, and\nwrites a fresh `vbrief/.triage-cache/candidates.jsonl`. It does NOT touch the tracked\n`slices.jsonl`; cohort records remain a team-shared resource.\n\n## `merge=union` policy for `*.jsonl`\n\nThe repo-root `.gitattributes` declares:\n\n```\nvbrief/.triage-cache/*.jsonl merge=union\n```\n\nThe `union` merge driver concatenates both sides' appended lines on\nauto-merge, so two branches that each appended a different record to the\nsame JSON-lines file rebase cleanly without operator surgery. Two things\noperators should know:\n\n- **Concatenation, not set-union.** When two branches append DIFFERENT\n records to the file, the merge driver concatenates both sides' lines\n -- there is no smart deduplication of \"semantically similar\" records.\n (Identical line-for-line appends collapse because git's three-way\n merge sees them as the same change, but distinct records always\n survive verbatim, even if a downstream reader would consider them\n redundant.) The append-only writers in `scripts/candidates_log.py`\n mint a fresh `decision_id` per call, so genuinely duplicate records\n are not the expected case, but downstream readers MUST tolerate\n multiple records describing the same logical decision.\n- **Single-operator scope only.** This is the foundational rebase\n ergonomic for the single-operator case (operator A rebases their\n feature branch onto a master that grew while they were AFK).\n Multi-operator merge-conflict resolution is explicitly out of scope per\n #1119 R4 (tracked separately as M1-M4 in #1183).\n\n## See also\n\n- Current Shape comment on #1144 for the canonical decisions (the source\n of truth this README documents).\n- `.gitignore` -- selective gitignore entries for the operator-private\n files.\n- `.gitattributes` -- the `merge=union` rule.\n- `scripts/candidates_log.py` -- the writer for `candidates.jsonl`.\n";
|
|
8
12
|
/** Strip an inline `# ...` comment from a gitignore line. */
|
|
9
13
|
export declare function stripGitignoreInlineComment(line: string): string;
|
|
10
14
|
/** Append `.deft-cache/` to `.gitignore` when absent. */
|
|
11
15
|
export declare function stepEnsureGitignoreEntry(projectRoot: string): StepOutcome;
|
|
12
16
|
/** Ensure the #1144 hybrid policy is encoded in the repo (idempotent). */
|
|
13
17
|
export declare function stepEnsureGitignoreEvalEntries(projectRoot: string): StepOutcome;
|
|
14
|
-
/** Ensure `vbrief/.
|
|
18
|
+
/** Ensure `vbrief/.triage-cache/candidates.jsonl` exists (#1240 option A). */
|
|
15
19
|
export declare function stepSeedCandidatesLog(projectRoot: string): StepOutcome;
|
|
16
20
|
//# sourceMappingURL=gitignore.d.ts.map
|