@deftai/directive-core 0.88.0 → 0.90.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/authz/actions.d.ts +55 -0
- package/dist/authz/actions.js +125 -0
- package/dist/authz/classify.d.ts +23 -0
- package/dist/authz/classify.js +217 -0
- package/dist/authz/closed-verb.d.ts +42 -0
- package/dist/authz/closed-verb.js +279 -0
- package/dist/authz/evaluate.d.ts +41 -0
- package/dist/authz/evaluate.js +298 -0
- package/dist/authz/index.d.ts +15 -0
- package/dist/authz/index.js +15 -0
- package/dist/authz/origin.d.ts +28 -0
- package/dist/authz/origin.js +64 -0
- package/dist/authz/paths.d.ts +6 -0
- package/dist/authz/paths.js +19 -0
- package/dist/authz/store.d.ts +40 -0
- package/dist/authz/store.js +317 -0
- package/dist/authz/templates.d.ts +139 -0
- package/dist/authz/templates.js +241 -0
- package/dist/authz/types.d.ts +111 -0
- package/dist/authz/types.js +41 -0
- package/dist/authz/verb-classification.d.ts +44 -0
- package/dist/authz/verb-classification.js +237 -0
- package/dist/branch/evaluate.js +6 -1
- package/dist/cache/fetch.js +10 -5
- package/dist/cache/io.d.ts +9 -2
- package/dist/cache/io.js +30 -10
- package/dist/cache/task-cache/store.js +11 -7
- package/dist/capacity/backfill.js +10 -4
- package/dist/category-b-namespace/index.js +10 -4
- package/dist/codebase/default-extractor.js +3 -0
- package/dist/codebase/map.js +11 -4
- package/dist/doctor/constants.d.ts +1 -1
- package/dist/doctor/constants.js +2 -0
- package/dist/doctor/doctor-state.js +28 -4
- package/dist/doctor/flags.js +21 -1
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.d.ts +10 -0
- package/dist/doctor/main.js +182 -0
- package/dist/doctor/openclaw-skills.d.ts +109 -0
- package/dist/doctor/openclaw-skills.js +463 -0
- package/dist/doctor/types.d.ts +22 -0
- package/dist/escalation/actions.d.ts +63 -0
- package/dist/escalation/actions.js +137 -0
- package/dist/escalation/index.d.ts +8 -0
- package/dist/escalation/index.js +8 -0
- package/dist/escalation/paths.d.ts +3 -0
- package/dist/escalation/paths.js +10 -0
- package/dist/escalation/store.d.ts +17 -0
- package/dist/escalation/store.js +172 -0
- package/dist/escalation/types.d.ts +73 -0
- package/dist/escalation/types.js +43 -0
- package/dist/eval/crud-telemetry.js +30 -10
- package/dist/eval/health.js +22 -7
- package/dist/eval/readback.js +11 -10
- package/dist/eval/run.js +32 -16
- package/dist/events/attribution-enrichment.js +10 -4
- package/dist/finish-loop/directive-finish-loop.d.ts +37 -0
- package/dist/finish-loop/directive-finish-loop.js +239 -0
- package/dist/finish-loop/grant-gate.d.ts +31 -0
- package/dist/finish-loop/grant-gate.js +169 -0
- package/dist/finish-loop/index.d.ts +11 -0
- package/dist/finish-loop/index.js +11 -0
- package/dist/finish-loop/main.d.ts +35 -0
- package/dist/finish-loop/main.js +365 -0
- package/dist/finish-loop/pr-finish-loop.d.ts +34 -0
- package/dist/finish-loop/pr-finish-loop.js +217 -0
- package/dist/finish-loop/progress.d.ts +17 -0
- package/dist/finish-loop/progress.js +45 -0
- package/dist/finish-loop/queue.d.ts +16 -0
- package/dist/finish-loop/queue.js +65 -0
- package/dist/finish-loop/types.d.ts +52 -0
- package/dist/finish-loop/types.js +10 -0
- package/dist/fs/contained-write.d.ts +101 -0
- package/dist/fs/contained-write.js +281 -0
- package/dist/hooks/classify/classify.d.ts +10 -0
- package/dist/hooks/classify/classify.js +37 -0
- package/dist/hooks/classify/index.d.ts +14 -0
- package/dist/hooks/classify/index.js +13 -0
- package/dist/hooks/classify/paths.d.ts +22 -0
- package/dist/hooks/classify/paths.js +72 -0
- package/dist/hooks/classify/payload.d.ts +16 -0
- package/dist/hooks/classify/payload.js +45 -0
- package/dist/hooks/classify/stdin.d.ts +12 -0
- package/dist/hooks/classify/stdin.js +63 -0
- package/dist/hooks/classify/tool-name.d.ts +18 -0
- package/dist/hooks/classify/tool-name.js +85 -0
- package/dist/hooks/classify/types.d.ts +41 -0
- package/dist/hooks/classify/types.js +7 -0
- package/dist/hooks/dispatcher.d.ts +21 -18
- package/dist/hooks/dispatcher.js +232 -168
- package/dist/hooks/fixtures/cases.d.ts +44 -0
- package/dist/hooks/fixtures/cases.js +595 -0
- package/dist/hooks/fixtures/index.d.ts +2 -0
- package/dist/hooks/fixtures/index.js +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/init-deposit/agent-hooks.js +27 -8
- package/dist/init-deposit/gitignore.js +11 -3
- package/dist/init-deposit/headless-manifest.js +12 -4
- package/dist/init-deposit/migrate.d.ts +1 -1
- package/dist/init-deposit/migrate.js +13 -3
- package/dist/init-deposit/prettierignore.js +10 -3
- package/dist/init-deposit/scaffold.js +35 -26
- package/dist/init-deposit/xbrief-projections.js +11 -4
- package/dist/intake/candidates-log.js +46 -35
- package/dist/intake/github-body-cli.d.ts +6 -0
- package/dist/intake/github-body-cli.js +17 -0
- package/dist/intake/github-body.d.ts +46 -3
- package/dist/intake/github-body.js +191 -20
- package/dist/intake/issue-emit.d.ts +1 -0
- package/dist/intake/issue-emit.js +41 -23
- package/dist/lifecycle/event-detect.js +12 -4
- package/dist/lifecycle/events.js +31 -20
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/lifecycle/lifecycle-hygiene.js +9 -3
- package/dist/lifecycle/stats.d.ts +53 -0
- package/dist/lifecycle/stats.js +286 -0
- package/dist/packs/pack-render.js +19 -6
- package/dist/plan-sequence/store.js +11 -4
- package/dist/policy/hotfix-criteria.d.ts +79 -0
- package/dist/policy/hotfix-criteria.js +197 -0
- package/dist/policy/index.d.ts +4 -0
- package/dist/policy/index.js +31 -1
- package/dist/policy/intent-ceiling.d.ts +79 -0
- package/dist/policy/intent-ceiling.js +181 -0
- package/dist/policy/no-deft-directive.js +10 -3
- package/dist/policy/org-force-on-migration.js +9 -5
- package/dist/policy/require-human-merge.d.ts +75 -0
- package/dist/policy/require-human-merge.js +324 -0
- package/dist/policy/resolve.js +17 -6
- package/dist/policy/runtime-authority.d.ts +15 -2
- package/dist/policy/runtime-authority.js +23 -3
- package/dist/policy/write-fence.d.ts +78 -0
- package/dist/policy/write-fence.js +164 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +10 -0
- package/dist/pr-wait-mergeable/cascade.js +28 -0
- package/dist/preflight/evaluate.js +10 -0
- package/dist/product-signal/consent.js +21 -5
- package/dist/product-signal/submit.js +22 -12
- package/dist/release/build-dist-runner.js +5 -2
- package/dist/release/build-dist.d.ts +19 -1
- package/dist/release/build-dist.js +50 -2
- package/dist/release/native-steps.js +7 -2
- package/dist/release/spawn.js +13 -0
- package/dist/release-e2e/git-ops.d.ts +7 -0
- package/dist/release-e2e/git-ops.js +35 -2
- package/dist/release-publish/pipeline.d.ts +13 -0
- package/dist/release-publish/pipeline.js +46 -1
- package/dist/scope/audit-log.js +19 -24
- package/dist/scope/decompose.js +10 -5
- package/dist/scope/decomposed-refs.js +18 -3
- package/dist/scope/demote.js +9 -2
- package/dist/scope/undo.js +9 -2
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/process-cost-constants.d.ts +9 -0
- package/dist/session/process-cost-constants.js +11 -0
- package/dist/session/process-cost.d.ts +53 -0
- package/dist/session/process-cost.js +82 -0
- package/dist/session/release-availability.js +26 -6
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +31 -13
- package/dist/session/session-ready.d.ts +67 -0
- package/dist/session/session-ready.js +264 -0
- package/dist/session/session-start.d.ts +56 -1
- package/dist/session/session-start.js +384 -23
- package/dist/session/verify-session-ritual.d.ts +8 -0
- package/dist/session/verify-session-ritual.js +95 -35
- package/dist/staleness-tickler/state.js +26 -6
- package/dist/swarm/launch.js +13 -3
- package/dist/swarm/routing.js +9 -3
- package/dist/task-surface/index.js +24 -9
- package/dist/tool-events/classify.d.ts +20 -0
- package/dist/tool-events/classify.js +634 -0
- package/dist/tool-events/index.d.ts +10 -0
- package/dist/tool-events/index.js +10 -0
- package/dist/tool-events/summarize.d.ts +34 -0
- package/dist/tool-events/summarize.js +93 -0
- package/dist/tool-events/types.d.ts +52 -0
- package/dist/tool-events/types.js +13 -0
- package/dist/triage/bootstrap/gitignore.js +53 -10
- package/dist/triage/cache-path.js +10 -3
- package/dist/triage/welcome/summary.js +11 -4
- package/dist/triage/welcome/writers.js +45 -16
- package/dist/validate-content/validate-links.js +5 -0
- package/dist/value/readback.js +11 -6
- package/dist/vbrief-activate/activate.js +12 -4
- package/dist/vbrief-build/project-definition-io.js +14 -6
- package/dist/verify-source/contained-writes.d.ts +59 -0
- package/dist/verify-source/contained-writes.js +272 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/openclaw-tier1.js +14 -1
- package/dist/verify-source/verify-stubs.js +3 -0
- package/dist/xbrief-migrate/migrate-project.js +26 -12
- package/dist/xbrief-migrate/transforms.d.ts +4 -2
- package/dist/xbrief-migrate/transforms.js +37 -14
- package/package.json +19 -3
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregate tool-event classifications into counts, anomalies, and status lines (#2967).
|
|
3
|
+
* Pure: no I/O.
|
|
4
|
+
*/
|
|
5
|
+
import { classifyToolEvent } from "./classify.js";
|
|
6
|
+
import { TOOL_EVENT_BUCKETS, } from "./types.js";
|
|
7
|
+
export function emptyBucketCounts() {
|
|
8
|
+
return {
|
|
9
|
+
explore: 0,
|
|
10
|
+
commit: 0,
|
|
11
|
+
verify: 0,
|
|
12
|
+
coordinate: 0,
|
|
13
|
+
unknown: 0,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Count classified buckets for a sequence of tool events. */
|
|
17
|
+
export function countToolEventBuckets(events) {
|
|
18
|
+
const counts = {
|
|
19
|
+
explore: 0,
|
|
20
|
+
commit: 0,
|
|
21
|
+
verify: 0,
|
|
22
|
+
coordinate: 0,
|
|
23
|
+
unknown: 0,
|
|
24
|
+
};
|
|
25
|
+
for (const event of events) {
|
|
26
|
+
const { bucket } = classifyToolEvent(event);
|
|
27
|
+
counts[bucket] += 1;
|
|
28
|
+
}
|
|
29
|
+
return counts;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Detect operator-facing anomalies from bucket counts.
|
|
33
|
+
*
|
|
34
|
+
* Rules (conservative — only fire when the mix is clearly wrong):
|
|
35
|
+
* - commit-without-explore: commit > 0 and explore === 0
|
|
36
|
+
* - verify-skipped: commit > 0 and verify === 0 (ship without gates)
|
|
37
|
+
* - explore-only: explore > 0, commit === 0, verify === 0, and total >= 3
|
|
38
|
+
* (thrash / stuck-reading signal; small explore-only sessions stay quiet)
|
|
39
|
+
*/
|
|
40
|
+
export function detectToolEventAnomalies(counts) {
|
|
41
|
+
const anomalies = [];
|
|
42
|
+
const total = counts.explore + counts.commit + counts.verify + counts.coordinate + counts.unknown;
|
|
43
|
+
if (counts.commit > 0 && counts.explore === 0) {
|
|
44
|
+
anomalies.push({
|
|
45
|
+
code: "commit-without-explore",
|
|
46
|
+
message: `commit-without-explore: ${counts.commit} commit event(s) with 0 explore`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (counts.commit > 0 && counts.verify === 0) {
|
|
50
|
+
anomalies.push({
|
|
51
|
+
code: "verify-skipped",
|
|
52
|
+
message: `verify-skipped: ${counts.commit} commit event(s) with 0 verify`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (counts.explore > 0 && counts.commit === 0 && counts.verify === 0 && total >= 3) {
|
|
56
|
+
anomalies.push({
|
|
57
|
+
code: "explore-only",
|
|
58
|
+
message: `explore-only: ${counts.explore} explore event(s) with 0 commit and 0 verify (possible thrash)`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return anomalies;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Compact status line for swarm monitor / review-cycle batch brief.
|
|
65
|
+
* Example: `tools: explore=3 commit=2 verify=1 coordinate=0 unknown=0`
|
|
66
|
+
*/
|
|
67
|
+
export function formatToolEventStatusLine(counts) {
|
|
68
|
+
const parts = TOOL_EVENT_BUCKETS.map((b) => `${b}=${counts[b]}`);
|
|
69
|
+
return `tools: ${parts.join(" ")}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One-line anomaly suffix for status surfaces.
|
|
73
|
+
* Empty string when no anomalies.
|
|
74
|
+
*/
|
|
75
|
+
export function formatToolEventAnomalyLine(anomalies) {
|
|
76
|
+
if (anomalies.length === 0)
|
|
77
|
+
return "";
|
|
78
|
+
return `anomalies: ${anomalies.map((a) => a.code).join(",")}`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Full summary for a tool-event sequence — counts, anomalies, status line.
|
|
82
|
+
* When anomalies exist, statusLine appends `| anomalies: …`.
|
|
83
|
+
*/
|
|
84
|
+
export function summarizeToolEvents(events) {
|
|
85
|
+
const counts = countToolEventBuckets(events);
|
|
86
|
+
const anomalies = detectToolEventAnomalies(counts);
|
|
87
|
+
const total = counts.explore + counts.commit + counts.verify + counts.coordinate + counts.unknown;
|
|
88
|
+
const base = formatToolEventStatusLine(counts);
|
|
89
|
+
const anomalyLine = formatToolEventAnomalyLine(anomalies);
|
|
90
|
+
const statusLine = anomalyLine.length > 0 ? `${base} | ${anomalyLine}` : base;
|
|
91
|
+
return { total, counts, anomalies, statusLine };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=summarize.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-event taxonomy types for explore/commit/verify classification (#2967).
|
|
3
|
+
* Pure types only — no I/O.
|
|
4
|
+
*/
|
|
5
|
+
/** Deterministic activity buckets for swarm/review operator skim. */
|
|
6
|
+
export type ToolEventBucket = "explore" | "commit" | "verify" | "coordinate" | "unknown";
|
|
7
|
+
/** All buckets in stable report order (unknown last). */
|
|
8
|
+
export declare const TOOL_EVENT_BUCKETS: readonly ToolEventBucket[];
|
|
9
|
+
/**
|
|
10
|
+
* Minimal tool-event input. Prefer tool name + coarse args; never require
|
|
11
|
+
* full host payloads. Shell tools should pass `command` when known.
|
|
12
|
+
*/
|
|
13
|
+
export interface ToolEventInput {
|
|
14
|
+
/** Host tool name (e.g. `Read`, `Shell`, `Write`, `Task`). */
|
|
15
|
+
readonly name: string;
|
|
16
|
+
/**
|
|
17
|
+
* Coarse args bag — common keys: `command`, `cmd`, `path`, `file_path`,
|
|
18
|
+
* `query`, `pattern`. Values may be strings or other JSON-ish primitives.
|
|
19
|
+
*/
|
|
20
|
+
readonly args?: Readonly<Record<string, unknown>> | null;
|
|
21
|
+
/**
|
|
22
|
+
* Explicit shell/command string when not nested under `args`.
|
|
23
|
+
* Wins over `args.command` / `args.cmd` when both are set.
|
|
24
|
+
*/
|
|
25
|
+
readonly command?: string | null;
|
|
26
|
+
}
|
|
27
|
+
/** Result of classifying one tool event. */
|
|
28
|
+
export interface ClassifyToolEventResult {
|
|
29
|
+
readonly bucket: ToolEventBucket;
|
|
30
|
+
/** Stable machine-oriented reason code for tests and debug. */
|
|
31
|
+
readonly reason: string;
|
|
32
|
+
}
|
|
33
|
+
/** Per-bucket event counts (always includes every bucket key). */
|
|
34
|
+
export type ToolEventBucketCounts = Readonly<Record<ToolEventBucket, number>>;
|
|
35
|
+
/**
|
|
36
|
+
* Operator-facing anomaly codes derived from a sequence of classified events.
|
|
37
|
+
* Detection is conservative: only fire when the mix is clearly wrong.
|
|
38
|
+
*/
|
|
39
|
+
export type ToolEventAnomalyCode = "commit-without-explore" | "verify-skipped" | "explore-only";
|
|
40
|
+
export interface ToolEventAnomaly {
|
|
41
|
+
readonly code: ToolEventAnomalyCode;
|
|
42
|
+
readonly message: string;
|
|
43
|
+
}
|
|
44
|
+
/** Aggregate summary for monitor status lines and batch briefs. */
|
|
45
|
+
export interface ToolEventSummary {
|
|
46
|
+
readonly total: number;
|
|
47
|
+
readonly counts: ToolEventBucketCounts;
|
|
48
|
+
readonly anomalies: readonly ToolEventAnomaly[];
|
|
49
|
+
/** Compact one-liner: `tools: explore=3 commit=2 verify=1 coordinate=0 unknown=0`. */
|
|
50
|
+
readonly statusLine: string;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-event taxonomy types for explore/commit/verify classification (#2967).
|
|
3
|
+
* Pure types only — no I/O.
|
|
4
|
+
*/
|
|
5
|
+
/** All buckets in stable report order (unknown last). */
|
|
6
|
+
export const TOOL_EVENT_BUCKETS = [
|
|
7
|
+
"explore",
|
|
8
|
+
"commit",
|
|
9
|
+
"verify",
|
|
10
|
+
"coordinate",
|
|
11
|
+
"unknown",
|
|
12
|
+
];
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync
|
|
2
|
-
import { dirname, relative } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, relative, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../../fs/contained-write.js";
|
|
3
4
|
import { assertProjectionContained, ProjectionContainmentError, } from "../../fs/projection-containment.js";
|
|
4
5
|
import { MIGRATED_ARTIFACT_DIR, resolveLifecycleLayout } from "../../layout/resolve.js";
|
|
5
6
|
import { resolveCandidatesLogPath, resolveTriageCachePath, TRIAGE_CACHE_DIR_NAME, triageCacheRelPath, } from "../cache-path.js";
|
|
@@ -197,7 +198,14 @@ function ensureGitignoreLine(projectRoot, gitignorePath, line, stepName, createI
|
|
|
197
198
|
return stepOutcome(stepName, false, `.gitignore not present after the prior gitignore step; ${line} not written -- re-run bootstrap to retry`, { created: false, appended: false, skipped: "no-gitignore" }, "prior gitignore step did not create .gitignore");
|
|
198
199
|
}
|
|
199
200
|
try {
|
|
200
|
-
|
|
201
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
202
|
+
const root = resolve(projectRoot);
|
|
203
|
+
containedWrite({
|
|
204
|
+
root,
|
|
205
|
+
target: gitignorePath,
|
|
206
|
+
data: `${line}\n`,
|
|
207
|
+
mode: "create",
|
|
208
|
+
});
|
|
201
209
|
}
|
|
202
210
|
catch (exc) {
|
|
203
211
|
return stepOutcome(stepName, false, "could not create .gitignore", {}, String(exc));
|
|
@@ -232,7 +240,13 @@ function ensureGitignoreLine(projectRoot, gitignorePath, line, stepName, createI
|
|
|
232
240
|
const suffix = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
233
241
|
const newContent = `${existing + suffix + rationaleBlock + line}\n`;
|
|
234
242
|
try {
|
|
235
|
-
|
|
243
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
244
|
+
containedWrite({
|
|
245
|
+
root: resolve(projectRoot),
|
|
246
|
+
target: gitignorePath,
|
|
247
|
+
data: newContent,
|
|
248
|
+
mode: "replace",
|
|
249
|
+
});
|
|
236
250
|
}
|
|
237
251
|
catch (exc) {
|
|
238
252
|
return stepOutcome(stepName, false, "could not write .gitignore", {}, String(exc));
|
|
@@ -303,7 +317,13 @@ function ensureGitignoreSelectiveEntries(projectRoot, stepName, entries) {
|
|
|
303
317
|
: `${EVAL_ENTRIES_RATIONALE}${missing.join("\n")}\n`;
|
|
304
318
|
const newContent = existing + suffix + appendedBlock;
|
|
305
319
|
try {
|
|
306
|
-
|
|
320
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
321
|
+
containedWrite({
|
|
322
|
+
root: resolve(projectRoot),
|
|
323
|
+
target: gitignorePath,
|
|
324
|
+
data: newContent,
|
|
325
|
+
mode: "replace",
|
|
326
|
+
});
|
|
307
327
|
}
|
|
308
328
|
catch (exc) {
|
|
309
329
|
return stepOutcome(stepName, false, "could not write .gitignore", { gitignore_appended_lines: 0 }, String(exc));
|
|
@@ -341,7 +361,13 @@ function ensureGitattributesMergeUnion(projectRoot, stepName, glob, ruleLine) {
|
|
|
341
361
|
const suffix = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
342
362
|
const newContent = `${existing + suffix + GITATTRIBUTES_EVAL_RATIONALE + ruleLine}\n`;
|
|
343
363
|
try {
|
|
344
|
-
|
|
364
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
365
|
+
containedWrite({
|
|
366
|
+
root: resolve(projectRoot),
|
|
367
|
+
target: gitattributesPath,
|
|
368
|
+
data: newContent,
|
|
369
|
+
mode: "replace",
|
|
370
|
+
});
|
|
345
371
|
}
|
|
346
372
|
catch (exc) {
|
|
347
373
|
return stepOutcome(stepName, false, "could not write .gitattributes", { gitattributes_appended: false }, String(exc));
|
|
@@ -353,7 +379,13 @@ function ensureGitattributesMergeUnion(projectRoot, stepName, glob, ruleLine) {
|
|
|
353
379
|
}
|
|
354
380
|
const newContent = `${GITATTRIBUTES_EVAL_RATIONALE + ruleLine}\n`;
|
|
355
381
|
try {
|
|
356
|
-
|
|
382
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
383
|
+
containedWrite({
|
|
384
|
+
root: resolve(projectRoot),
|
|
385
|
+
target: gitattributesPath,
|
|
386
|
+
data: newContent,
|
|
387
|
+
mode: "create",
|
|
388
|
+
});
|
|
357
389
|
}
|
|
358
390
|
catch (exc) {
|
|
359
391
|
return stepOutcome(stepName, false, "could not create .gitattributes", { gitattributes_appended: false }, String(exc));
|
|
@@ -382,8 +414,13 @@ function ensureEvalReadme(options) {
|
|
|
382
414
|
// create below
|
|
383
415
|
}
|
|
384
416
|
try {
|
|
385
|
-
|
|
386
|
-
|
|
417
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
418
|
+
containedWrite({
|
|
419
|
+
root: resolve(projectRoot),
|
|
420
|
+
target: readmePath,
|
|
421
|
+
data: generateTriageCacheReadmeBody(projectRoot),
|
|
422
|
+
mode: "create",
|
|
423
|
+
});
|
|
387
424
|
}
|
|
388
425
|
catch (exc) {
|
|
389
426
|
return stepOutcome(stepName, false, `could not create ${readmePath}`, { readme_created: false }, String(exc));
|
|
@@ -474,7 +511,13 @@ export function stepSeedCandidatesLog(projectRoot) {
|
|
|
474
511
|
// create below
|
|
475
512
|
}
|
|
476
513
|
try {
|
|
477
|
-
|
|
514
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
515
|
+
containedWrite({
|
|
516
|
+
root: resolve(projectRoot),
|
|
517
|
+
target: auditPath,
|
|
518
|
+
data: "",
|
|
519
|
+
mode: "create",
|
|
520
|
+
});
|
|
478
521
|
}
|
|
479
522
|
catch (exc) {
|
|
480
523
|
return stepOutcome("seed_candidates_log", false, `could not seed ${auditPath}`, {}, String(exc));
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Triage append-only logs and scratch dirs live under `.triage-cache/` so the
|
|
5
5
|
* `.eval/` namespace can be reclaimed for version-eval results (#1703 Tier 2).
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, mkdirSync, renameSync, unlinkSync
|
|
8
|
-
import { join, relative } from "node:path";
|
|
7
|
+
import { existsSync, mkdirSync, renameSync, unlinkSync } from "node:fs";
|
|
8
|
+
import { join, relative, resolve } from "node:path";
|
|
9
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
9
10
|
import { assertProjectionContained } from "../fs/projection-containment.js";
|
|
10
11
|
import { MIGRATED_ARTIFACT_DIR, resolveEvalDir, resolveLifecycleLayout, resolveLifecycleRoot, } from "../layout/resolve.js";
|
|
11
12
|
import { generateTriageCacheReadmeBody } from "./bootstrap/gitignore.js";
|
|
@@ -99,7 +100,13 @@ export function migrateLegacyTriageCacheFromEval(projectRoot) {
|
|
|
99
100
|
removedLegacyFiles.push(name);
|
|
100
101
|
}
|
|
101
102
|
else {
|
|
102
|
-
|
|
103
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
104
|
+
containedWrite({
|
|
105
|
+
root: resolve(projectRoot),
|
|
106
|
+
target: targetPath,
|
|
107
|
+
data: generateTriageCacheReadmeBody(projectRoot),
|
|
108
|
+
mode: "create",
|
|
109
|
+
});
|
|
103
110
|
unlinkSync(legacyPath);
|
|
104
111
|
regeneratedFiles.push(name);
|
|
105
112
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../../fs/contained-write.js";
|
|
3
4
|
import { hasArtifactSuffix, resolveLifecycleFolder, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
|
|
4
5
|
import { readPlanPolicy } from "../../policy/plan-extensions.js";
|
|
5
6
|
import { countVbriefWip, resolveWipCap } from "../../policy/wip.js";
|
|
@@ -296,8 +297,14 @@ export function appendHistory(historyPath, result, line) {
|
|
|
296
297
|
reconcilable: result.reconcilable,
|
|
297
298
|
};
|
|
298
299
|
try {
|
|
299
|
-
|
|
300
|
-
|
|
300
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
301
|
+
const dir = dirname(historyPath);
|
|
302
|
+
containedWrite({
|
|
303
|
+
root: resolve(dir),
|
|
304
|
+
target: basename(historyPath),
|
|
305
|
+
data: `${JSON.stringify(record)}\n`,
|
|
306
|
+
mode: "append",
|
|
307
|
+
});
|
|
301
308
|
}
|
|
302
309
|
catch {
|
|
303
310
|
// observability only
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { containedWrite } from "../../fs/contained-write.js";
|
|
4
4
|
import { hasArtifactSuffix, resolveLifecycleFolder, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
|
|
5
5
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY } from "../../policy/plan-extensions.js";
|
|
6
6
|
import { projectDefinitionMutationLock } from "../../vbrief-build/project-definition-io.js";
|
|
@@ -12,22 +12,51 @@ function utcIso() {
|
|
|
12
12
|
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13
13
|
}
|
|
14
14
|
export function appendAuditEntry(projectRoot, entry) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const root = resolve(projectRoot);
|
|
16
|
+
const logPath = join(root, AUDIT_LOG_REL_PATH);
|
|
17
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
18
|
+
mkdirSync(root, { recursive: true });
|
|
19
19
|
if (!existsSync(logPath)) {
|
|
20
|
-
|
|
20
|
+
containedWrite({
|
|
21
|
+
root,
|
|
22
|
+
target: AUDIT_LOG_REL_PATH,
|
|
23
|
+
data: "# meta/policy-changes.log -- audit trail for PROJECT-DEFINITION plan.policy.* mutations (#746 / #1143)\n",
|
|
24
|
+
mode: "create",
|
|
25
|
+
});
|
|
21
26
|
}
|
|
22
|
-
|
|
27
|
+
containedWrite({
|
|
28
|
+
root,
|
|
29
|
+
target: AUDIT_LOG_REL_PATH,
|
|
30
|
+
data: `${utcIso()} ${entry}\n`,
|
|
31
|
+
mode: "append",
|
|
32
|
+
});
|
|
23
33
|
return logPath;
|
|
24
34
|
}
|
|
25
|
-
function atomicWrite(path, data) {
|
|
26
|
-
|
|
35
|
+
function atomicWrite(_projectRoot, path, data) {
|
|
36
|
+
const dir = dirname(path);
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
27
38
|
const payload = `${JSON.stringify(data, null, 2)}\n`;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
const tmpName = `.${Date.now()}.tmp`;
|
|
40
|
+
const tmp = join(dir, tmpName);
|
|
41
|
+
try {
|
|
42
|
+
// #2980 wave D: temp payload write uses containedWrite under the parent dir.
|
|
43
|
+
containedWrite({
|
|
44
|
+
root: resolve(dir),
|
|
45
|
+
target: tmpName,
|
|
46
|
+
data: payload,
|
|
47
|
+
mode: "create",
|
|
48
|
+
});
|
|
49
|
+
renameSync(tmp, path);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
try {
|
|
53
|
+
rmSync(tmp, { force: true });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
/* best-effort cleanup */
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
31
60
|
}
|
|
32
61
|
export function writeTriageScope(projectRoot, rules, options = { presetLabel: "custom" }) {
|
|
33
62
|
// Serialise read-modify-write + audit append under the shared lock (#1260).
|
|
@@ -54,7 +83,7 @@ export function writeTriageScope(projectRoot, rules, options = { presetLabel: "c
|
|
|
54
83
|
const policyRec = policy;
|
|
55
84
|
const previous = policyRec.triageScope;
|
|
56
85
|
policyRec.triageScope = rules;
|
|
57
|
-
atomicWrite(path, data);
|
|
86
|
+
atomicWrite(projectRoot, path, data);
|
|
58
87
|
const changed = JSON.stringify(previous) !== JSON.stringify(rules);
|
|
59
88
|
const actor = options.actor ?? WELCOME_AUDIT_TAG;
|
|
60
89
|
const auditEntry = [
|
|
@@ -101,14 +130,14 @@ export function writeWipCap(projectRoot, wipCap, options = {}) {
|
|
|
101
130
|
}
|
|
102
131
|
if (previous !== undefined && wipCap === DEFAULT_WIP_CAP) {
|
|
103
132
|
delete policyRec.wipCap;
|
|
104
|
-
atomicWrite(path, data);
|
|
133
|
+
atomicWrite(projectRoot, path, data);
|
|
105
134
|
const auditEntry = `actor=${actor} field=plan.policy.wipCap action=cleared-to-default value=${wipCap} ` +
|
|
106
135
|
`previous=${JSON.stringify(previous)} changed=true`;
|
|
107
136
|
appendAuditEntry(projectRoot, auditEntry);
|
|
108
137
|
return [true, auditEntry];
|
|
109
138
|
}
|
|
110
139
|
policyRec.wipCap = wipCap;
|
|
111
|
-
atomicWrite(path, data);
|
|
140
|
+
atomicWrite(projectRoot, path, data);
|
|
112
141
|
const changed = previous !== wipCap;
|
|
113
142
|
const auditEntry = `actor=${actor} field=plan.policy.wipCap value=${wipCap} previous=${JSON.stringify(previous)} ` +
|
|
114
143
|
`changed=${changed ? "true" : "false"}`;
|
|
@@ -10,6 +10,11 @@ const EXCLUDE_DIRS = new Set([
|
|
|
10
10
|
"dist",
|
|
11
11
|
".planning",
|
|
12
12
|
"specs",
|
|
13
|
+
// Swarm / agent worktrees are not framework content; walking them
|
|
14
|
+
// during release Step 5 / task check is pure wall-clock waste (#2953, #1656).
|
|
15
|
+
".deft-scratch",
|
|
16
|
+
// Legacy swarm worktree root name (pre-.deft-scratch layout).
|
|
17
|
+
"swarm-worktrees",
|
|
13
18
|
]);
|
|
14
19
|
function shouldSkipPath(parts) {
|
|
15
20
|
if (parts.some((p) => EXCLUDE_DIRS.has(p)))
|
package/dist/value/readback.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, 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 {
|
|
5
|
+
import { ContainedWriteError, containedWrite } from "../fs/contained-write.js";
|
|
6
|
+
import { ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
6
7
|
import { DEFAULT_EVENT_LOG, readEvents } from "../lifecycle/events.js";
|
|
7
8
|
import { policyColonInvocation } from "../policy/policy-invocation.js";
|
|
8
9
|
import { isValueFeedbackPathAllowed, resolveValueFeedback, } from "../policy/value-feedback.js";
|
|
@@ -230,12 +231,16 @@ function appendReadbackHistory(projectRoot, eventId, line, options = {}) {
|
|
|
230
231
|
line,
|
|
231
232
|
};
|
|
232
233
|
try {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
235
|
+
containedWrite({
|
|
236
|
+
root: resolve(projectRoot),
|
|
237
|
+
target: path,
|
|
238
|
+
data: `${JSON.stringify(record)}\n`,
|
|
239
|
+
mode: "append",
|
|
240
|
+
});
|
|
236
241
|
}
|
|
237
242
|
catch (err) {
|
|
238
|
-
if (err instanceof ProjectionContainmentError) {
|
|
243
|
+
if (err instanceof ProjectionContainmentError || err instanceof ContainedWriteError) {
|
|
239
244
|
throw err;
|
|
240
245
|
}
|
|
241
246
|
// observability only
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync,
|
|
2
|
-
import { basename, dirname } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, } from "node:fs";
|
|
2
|
+
import { basename, dirname, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
3
4
|
import { utcNowIso } from "../scope/vbrief-json.js";
|
|
4
5
|
import { pythonJsonPretty } from "../vbrief-build/json.js";
|
|
5
6
|
import { ACTIVE_FOLDER, ELIGIBLE_STATUSES_FOR_FLIP, formatEligibleStatusList, SOURCE_FOLDERS, TARGET_STATUS, } from "./constants.js";
|
|
@@ -150,15 +151,22 @@ export function activate(vbriefPath, options = {}) {
|
|
|
150
151
|
"collision manually before re-running `task vbrief:activate`.",
|
|
151
152
|
};
|
|
152
153
|
}
|
|
154
|
+
const tmpBase = `${fileName}.tmp`;
|
|
153
155
|
const tmp = `${dest}.tmp`;
|
|
154
156
|
try {
|
|
155
|
-
|
|
157
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
158
|
+
containedWrite({
|
|
159
|
+
root: resolve(activeDir),
|
|
160
|
+
target: tmpBase,
|
|
161
|
+
data: pythonJsonPretty(payload),
|
|
162
|
+
mode: "create",
|
|
163
|
+
});
|
|
156
164
|
renameSync(tmp, dest);
|
|
157
165
|
}
|
|
158
166
|
catch (err) {
|
|
159
167
|
const e = err;
|
|
160
168
|
try {
|
|
161
|
-
|
|
169
|
+
rmSync(tmp, { force: true });
|
|
162
170
|
}
|
|
163
171
|
catch {
|
|
164
172
|
/* best-effort cleanup */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync,
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeSync, } from "node:fs";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
4
5
|
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
5
6
|
import { pythonJsonPretty } from "./json.js";
|
|
6
7
|
import { ProjectDefinitionIOError } from "./types.js";
|
|
@@ -105,12 +106,19 @@ export function loadProjectDefinitionForMutation(projectRoot) {
|
|
|
105
106
|
}
|
|
106
107
|
/** Atomically write ``data`` to ``path`` as pretty-printed JSON. */
|
|
107
108
|
export function atomicWriteProjectDefinition(path, data) {
|
|
108
|
-
|
|
109
|
+
// #2980 wave C: product write sink routes through containedWrite (temp under parent).
|
|
110
|
+
const dir = dirname(path);
|
|
111
|
+
mkdirSync(dir, { recursive: true });
|
|
109
112
|
const payload = pythonJsonPretty(data).replace(/\n$/, "");
|
|
110
|
-
const
|
|
113
|
+
const body = payload.endsWith("\n") ? payload : `${payload}\n`;
|
|
114
|
+
const tmpName = `${basename(path)}.${randomBytes(4).toString("hex")}.tmp`;
|
|
115
|
+
const tmp = join(dir, tmpName);
|
|
111
116
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
containedWrite({
|
|
118
|
+
root: resolve(dir),
|
|
119
|
+
target: tmpName,
|
|
120
|
+
data: body,
|
|
121
|
+
mode: "create",
|
|
114
122
|
});
|
|
115
123
|
renameSync(tmp, path);
|
|
116
124
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* contained-writes.ts — inventory gate for raw product write sinks (#2951).
|
|
3
|
+
*
|
|
4
|
+
* Scans TypeScript under packages/core/src (and optional roots) for raw write
|
|
5
|
+
* call patterns (`writeFileSync`, `appendFileSync`, `fs.writeFile`, etc.)
|
|
6
|
+
* outside an allowlist. Default remains **fail-open** (advisory exit 0) while
|
|
7
|
+
* mass migration continues; pass `--enforce` to fail closed (Phase 2+).
|
|
8
|
+
*
|
|
9
|
+
* Allowlist is the residual set of modules not yet migrated onto containedWrite.
|
|
10
|
+
* Phase 2 removed cache/io + lifecycle/events after sink migration.
|
|
11
|
+
*
|
|
12
|
+
* Exit codes:
|
|
13
|
+
* 0 — clean OR fail-open with findings (default)
|
|
14
|
+
* 1 — findings under `--enforce` (fail-closed)
|
|
15
|
+
* 2 — config error (project root missing)
|
|
16
|
+
*
|
|
17
|
+
* Refs #2951, #2980 (e2e/parity harness exclusions).
|
|
18
|
+
*/
|
|
19
|
+
/** Patterns that indicate a raw filesystem write sink in product code. */
|
|
20
|
+
export declare const RAW_WRITE_PATTERNS: readonly RegExp[];
|
|
21
|
+
/**
|
|
22
|
+
* Relative path prefixes (posix-style) allowed to use raw write sinks.
|
|
23
|
+
* Shrink as sinks migrate onto containedWrite (#2951 Phase 2+).
|
|
24
|
+
*/
|
|
25
|
+
export declare const CONTAINED_WRITES_ALLOWLIST: readonly string[];
|
|
26
|
+
/**
|
|
27
|
+
* Non-product harness path markers excluded from the product inventory (#2980).
|
|
28
|
+
* Prefer exclusion over permanent CONTAINED_WRITES_ALLOWLIST entries for e2e/parity noise.
|
|
29
|
+
* Paths are matched as posix substrings after `toPosix` (always `/` separators).
|
|
30
|
+
*/
|
|
31
|
+
export declare const NON_PRODUCT_HARNESS_PATH_MARKERS: readonly string[];
|
|
32
|
+
export interface ContainedWriteFinding {
|
|
33
|
+
readonly path: string;
|
|
34
|
+
readonly line: number;
|
|
35
|
+
readonly match: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ContainedWritesOptions {
|
|
38
|
+
readonly projectRoot: string;
|
|
39
|
+
/** When true, exit 1 if any non-allowlisted raw write is found. Default false (fail-open). */
|
|
40
|
+
readonly enforce?: boolean;
|
|
41
|
+
/** Extra relative path prefixes to allow. */
|
|
42
|
+
readonly extraAllowlist?: readonly string[];
|
|
43
|
+
/** Roots to scan relative to projectRoot. Default packages/core/src. */
|
|
44
|
+
readonly scanRoots?: readonly string[];
|
|
45
|
+
}
|
|
46
|
+
export interface ContainedWritesResult {
|
|
47
|
+
readonly code: 0 | 1 | 2;
|
|
48
|
+
readonly message: string;
|
|
49
|
+
readonly stream: "stdout" | "stderr";
|
|
50
|
+
readonly findings: readonly ContainedWriteFinding[];
|
|
51
|
+
readonly enforce: boolean;
|
|
52
|
+
readonly failOpen: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Inventory raw write sinks under scan roots; fail-open by default (#2951).
|
|
56
|
+
* Pass `enforce: true` / `--enforce` to fail closed on findings (Phase 2 path).
|
|
57
|
+
*/
|
|
58
|
+
export declare function evaluateContainedWrites(options: ContainedWritesOptions): ContainedWritesResult;
|
|
59
|
+
//# sourceMappingURL=contained-writes.d.ts.map
|