@deftai/directive-core 0.87.0 → 0.89.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/scanner.d.ts +11 -1
- package/dist/cache/scanner.js +29 -4
- 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/check/gate-lists.js +1 -0
- package/dist/codebase/default-extractor.js +3 -0
- package/dist/codebase/map.js +11 -4
- package/dist/content-contracts/skills/helpers.d.ts +10 -0
- package/dist/content-contracts/skills/helpers.js +35 -0
- package/dist/deposit/copy-tree.d.ts +19 -1
- package/dist/deposit/copy-tree.js +134 -5
- package/dist/doctor/doctor-state.js +28 -4
- package/dist/doctor/main.d.ts +10 -0
- package/dist/doctor/main.js +208 -0
- package/dist/doctor/types.d.ts +11 -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/fs/projection-containment.d.ts +18 -0
- package/dist/fs/projection-containment.js +40 -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 +30 -15
- package/dist/hooks/dispatcher.js +326 -134
- 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/hooks/tools.d.ts +31 -0
- package/dist/hooks/tools.js +74 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +65 -10
- package/dist/init-deposit/gitignore.js +11 -3
- package/dist/init-deposit/headless-manifest.js +12 -4
- package/dist/init-deposit/hygiene.d.ts +16 -0
- package/dist/init-deposit/hygiene.js +26 -0
- package/dist/init-deposit/init-dispatch.js +28 -0
- package/dist/init-deposit/migrate.d.ts +1 -1
- package/dist/init-deposit/migrate.js +13 -3
- package/dist/init-deposit/prettierignore.js +12 -5
- package/dist/init-deposit/refresh.js +38 -7
- package/dist/init-deposit/scaffold.js +42 -29
- package/dist/init-deposit/xbrief-projections.js +17 -10
- 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/intake/issue-ingest.js +11 -2
- package/dist/lifecycle/event-detect.js +12 -4
- package/dist/lifecycle/events.js +29 -20
- package/dist/lifecycle/lifecycle-hygiene.js +9 -3
- package/dist/packs/pack-render.d.ts +33 -0
- package/dist/packs/pack-render.js +172 -13
- package/dist/packs/quarantine-ext.d.ts +10 -0
- package/dist/packs/quarantine-ext.js +26 -2
- 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 +5 -0
- package/dist/policy/index.js +32 -1
- package/dist/policy/intent-ceiling.d.ts +79 -0
- package/dist/policy/intent-ceiling.js +181 -0
- package/dist/policy/no-deft-directive.d.ts +59 -0
- package/dist/policy/no-deft-directive.js +110 -0
- package/dist/policy/org-force-on-migration.d.ts +52 -0
- package/dist/policy/org-force-on-migration.js +269 -27
- 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 +56 -2
- package/dist/policy/runtime-authority.js +297 -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-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/release-availability.js +26 -6
- package/dist/session/ritual-sentinel.js +19 -12
- package/dist/session/session-start-hook.d.ts +3 -0
- package/dist/session/session-start-hook.js +15 -0
- package/dist/session/session-start.js +37 -0
- 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/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/cursor-tier1.js +7 -2
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/openclaw-tier1.js +19 -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 +16 -3
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { renderFinding, scanLine } from "../encoding/scan.js";
|
|
4
|
+
import { pythonSplitlines, stripMarkdownQuotes } from "../encoding/text.js";
|
|
5
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
2
6
|
import { defaultWhich } from "../scm/binary.js";
|
|
3
7
|
import { call } from "../scm/call.js";
|
|
4
8
|
export const DEFAULT_TIMEOUT_SECONDS = 60;
|
|
9
|
+
/** Stable error code for CP1252/CP437-as-UTF-8 / U+FFFD body corruption (#2960). */
|
|
10
|
+
export const SCM_BODY_ENCODING_CODE = "scm-body-encoding";
|
|
5
11
|
export class GitHubBodyError extends Error {
|
|
6
|
-
|
|
12
|
+
code;
|
|
13
|
+
constructor(message, options) {
|
|
7
14
|
super(message);
|
|
8
15
|
this.name = "GitHubBodyError";
|
|
16
|
+
this.code = options?.code;
|
|
9
17
|
}
|
|
10
18
|
}
|
|
11
19
|
function splitRepo(repo) {
|
|
@@ -24,9 +32,54 @@ export function resolveLiveGh() {
|
|
|
24
32
|
}
|
|
25
33
|
export function readBody(bodyFile, stdinText) {
|
|
26
34
|
if (bodyFile === "-") {
|
|
27
|
-
return stdinText ?? "";
|
|
35
|
+
return stripUtf8Bom(stdinText ?? "");
|
|
28
36
|
}
|
|
29
|
-
return readFileSync(bodyFile, "utf8");
|
|
37
|
+
return stripUtf8Bom(readFileSync(bodyFile, "utf8"));
|
|
38
|
+
}
|
|
39
|
+
/** Strip a leading UTF-8 BOM so body-file writers stay UTF-8 no BOM (#2960 / #798). */
|
|
40
|
+
export function stripUtf8Bom(text) {
|
|
41
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Scan body text with the same mojibake pattern set as verify:encoding
|
|
45
|
+
* (`packages/core/src/encoding/patterns.ts` / scanLine). Issue/PR bodies are
|
|
46
|
+
* Markdown: strip fenced blocks and inline code spans so documented examples
|
|
47
|
+
* of the corruption class (e.g. `#2960` fixtures) are not false positives —
|
|
48
|
+
* matches `scanFile` .md behavior.
|
|
49
|
+
*/
|
|
50
|
+
export function scanBodyText(body, source = "body") {
|
|
51
|
+
const findings = [];
|
|
52
|
+
const scanText = stripMarkdownQuotes(body);
|
|
53
|
+
const originalLines = pythonSplitlines(body);
|
|
54
|
+
const strippedLines = pythonSplitlines(scanText);
|
|
55
|
+
if (originalLines.length === 0 && body.length > 0) {
|
|
56
|
+
findings.push(...scanLine(source, 1, scanText, body));
|
|
57
|
+
return findings;
|
|
58
|
+
}
|
|
59
|
+
// Preserve line alignment when strip blanks fenced blocks (newline-for-newline).
|
|
60
|
+
while (strippedLines.length < originalLines.length) {
|
|
61
|
+
strippedLines.push("");
|
|
62
|
+
}
|
|
63
|
+
originalLines.forEach((orig, idx) => {
|
|
64
|
+
const stripped = strippedLines[idx] ?? "";
|
|
65
|
+
findings.push(...scanLine(source, idx + 1, stripped, orig));
|
|
66
|
+
});
|
|
67
|
+
return findings;
|
|
68
|
+
}
|
|
69
|
+
function formatEncodingFindings(findings) {
|
|
70
|
+
return findings.map((f) => renderFinding(f).trim()).join("; ");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Fail closed when body text already contains CP1252/CP437-as-UTF-8 mojibake
|
|
74
|
+
* or U+FFFD (before PATCH and as a live-body diagnostic).
|
|
75
|
+
*/
|
|
76
|
+
export function assertBodyEncoding(body, phase) {
|
|
77
|
+
const findings = scanBodyText(body, phase);
|
|
78
|
+
if (findings.length === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const detail = formatEncodingFindings(findings);
|
|
82
|
+
throw new GitHubBodyError(`body encoding failed (${phase}): ${detail}. Repair: task scm:body:issue:fetch -- --repo OWNER/REPO --issue N --out-file <path>, fix as UTF-8 (no BOM), task scm:body:issue:edit -- --body-file <path> (#2960 / #2948 / #2944).`, { code: SCM_BODY_ENCODING_CODE });
|
|
30
83
|
}
|
|
31
84
|
function jsonInput(payload) {
|
|
32
85
|
return JSON.stringify(payload);
|
|
@@ -73,10 +126,14 @@ function requireIntField(obj, field) {
|
|
|
73
126
|
}
|
|
74
127
|
return value;
|
|
75
128
|
}
|
|
76
|
-
|
|
77
|
-
|
|
129
|
+
/** Body may be null on empty GitHub issues/PRs — treat as empty string for lint/fetch. */
|
|
130
|
+
function bodyFieldOrEmpty(obj) {
|
|
131
|
+
const value = obj.body;
|
|
132
|
+
if (value === null || value === undefined) {
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
78
135
|
if (typeof value !== "string") {
|
|
79
|
-
throw new GitHubBodyError(`response did not include string field
|
|
136
|
+
throw new GitHubBodyError(`response did not include string field "body"`);
|
|
80
137
|
}
|
|
81
138
|
return value;
|
|
82
139
|
}
|
|
@@ -91,7 +148,7 @@ function countNewlines(text) {
|
|
|
91
148
|
function normalizeNewlines(text) {
|
|
92
149
|
return text.replace(/\r\n/g, "\n");
|
|
93
150
|
}
|
|
94
|
-
/** Fail closed when live read-back body is flattened or mojibaked vs intended payload (#2607). */
|
|
151
|
+
/** Fail closed when live read-back body is flattened or mojibaked vs intended payload (#2607 / #2960). */
|
|
95
152
|
export function verifyBodyPostcondition(intended, live) {
|
|
96
153
|
if (intended === live) {
|
|
97
154
|
return;
|
|
@@ -103,6 +160,11 @@ export function verifyBodyPostcondition(intended, live) {
|
|
|
103
160
|
if (live.includes("\uFFFD") && !intended.includes("\uFFFD")) {
|
|
104
161
|
diagnoses.push("live body contains U+FFFD replacement character not present in intended payload");
|
|
105
162
|
}
|
|
163
|
+
const liveFindings = scanBodyText(live, "live");
|
|
164
|
+
const intendedFindings = scanBodyText(intended, "intended");
|
|
165
|
+
if (liveFindings.length > 0 && intendedFindings.length === 0) {
|
|
166
|
+
diagnoses.push(`live body contains mojibake not present in intended payload (${formatEncodingFindings(liveFindings)})`);
|
|
167
|
+
}
|
|
106
168
|
const intendedNl = countNewlines(intended);
|
|
107
169
|
const liveNl = countNewlines(live);
|
|
108
170
|
if (intendedNl > 0 && liveNl < intendedNl) {
|
|
@@ -112,12 +174,19 @@ export function verifyBodyPostcondition(intended, live) {
|
|
|
112
174
|
diagnoses.push("live body looks like intended newlines were collapsed to spaces (PowerShell string[] join)");
|
|
113
175
|
}
|
|
114
176
|
if (diagnoses.length > 0) {
|
|
115
|
-
|
|
177
|
+
const isEncoding = diagnoses.some((d) => d.includes("U+FFFD") || d.includes("mojibake") || d.includes("encoding"));
|
|
178
|
+
throw new GitHubBodyError(`body postcondition failed: ${diagnoses.join("; ")}`, {
|
|
179
|
+
code: isEncoding ? SCM_BODY_ENCODING_CODE : undefined,
|
|
180
|
+
});
|
|
116
181
|
}
|
|
117
182
|
throw new GitHubBodyError(`body postcondition failed: re-fetched body does not match intended payload (length intended=${intended.length}, live=${live.length})`);
|
|
118
183
|
}
|
|
119
184
|
function mutateWithReadback(mutationEndpoint, method, payload, readbackEndpoint, options = {}) {
|
|
120
185
|
const intendedBody = typeof payload.body === "string" ? payload.body : undefined;
|
|
186
|
+
if (intendedBody !== undefined) {
|
|
187
|
+
// Fail closed before PATCH when the intended payload is already corrupted (#2960).
|
|
188
|
+
assertBodyEncoding(intendedBody, "pre-write");
|
|
189
|
+
}
|
|
121
190
|
const mutation = runGhApiJson([mutationEndpoint, "--method", method, "--input", "-"], {
|
|
122
191
|
inputText: jsonInput(payload),
|
|
123
192
|
binary: options.binary,
|
|
@@ -126,7 +195,10 @@ function mutateWithReadback(mutationEndpoint, method, payload, readbackEndpoint,
|
|
|
126
195
|
const endpoint = typeof readbackEndpoint === "function" ? readbackEndpoint(mutation) : readbackEndpoint;
|
|
127
196
|
const readback = runGhApiJson([endpoint], { binary: options.binary, runFn: options.runFn });
|
|
128
197
|
if (intendedBody !== undefined) {
|
|
129
|
-
|
|
198
|
+
const liveBody = bodyFieldOrEmpty(readback);
|
|
199
|
+
verifyBodyPostcondition(intendedBody, liveBody);
|
|
200
|
+
// Defense in depth: live body must also be free of encoding corruption.
|
|
201
|
+
assertBodyEncoding(liveBody, "post-write-readback");
|
|
130
202
|
}
|
|
131
203
|
return readback;
|
|
132
204
|
}
|
|
@@ -168,14 +240,52 @@ export function fetchIssueBody(repo, issue, options = {}) {
|
|
|
168
240
|
const [owner, name] = splitRepo(repo);
|
|
169
241
|
const endpoint = `repos/${owner}/${name}/issues/${issue}`;
|
|
170
242
|
const response = runGhApiJson([endpoint], { binary: options.binary, runFn: options.runFn });
|
|
171
|
-
return
|
|
243
|
+
return bodyFieldOrEmpty(response);
|
|
244
|
+
}
|
|
245
|
+
export function fetchPrBody(repo, pr, options = {}) {
|
|
246
|
+
const [owner, name] = splitRepo(repo);
|
|
247
|
+
const endpoint = `repos/${owner}/${name}/pulls/${pr}`;
|
|
248
|
+
const response = runGhApiJson([endpoint], { binary: options.binary, runFn: options.runFn });
|
|
249
|
+
return bodyFieldOrEmpty(response);
|
|
172
250
|
}
|
|
173
251
|
export function writeIssueBodyToFile(repo, issue, outFile, options = {}) {
|
|
174
252
|
const body = fetchIssueBody(repo, issue, options);
|
|
175
|
-
|
|
253
|
+
// UTF-8 no BOM (Node default for encoding: "utf8").
|
|
254
|
+
// #2980 wave B: product write sink routes through containedWrite under out-file parent.
|
|
255
|
+
const abs = resolve(outFile);
|
|
256
|
+
const parent = dirname(abs);
|
|
257
|
+
mkdirSync(parent, { recursive: true });
|
|
258
|
+
containedWrite({
|
|
259
|
+
root: parent,
|
|
260
|
+
target: abs,
|
|
261
|
+
data: body,
|
|
262
|
+
mode: "replace",
|
|
263
|
+
});
|
|
176
264
|
return body;
|
|
177
265
|
}
|
|
178
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Lint a live issue body for mojibake. Returns findings (empty = clean).
|
|
268
|
+
* Does not throw on hits — callers decide exit code.
|
|
269
|
+
*/
|
|
270
|
+
export function lintIssueBody(repo, issue, options = {}) {
|
|
271
|
+
const body = fetchIssueBody(repo, issue, options);
|
|
272
|
+
return scanBodyText(body, `issue/${issue}`);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Lint a live PR body for mojibake. Returns findings (empty = clean).
|
|
276
|
+
*/
|
|
277
|
+
export function lintPrBody(repo, pr, options = {}) {
|
|
278
|
+
const body = fetchPrBody(repo, pr, options);
|
|
279
|
+
return scanBodyText(body, `pr/${pr}`);
|
|
280
|
+
}
|
|
281
|
+
function writeLintFailure(kind, id, findings) {
|
|
282
|
+
process.stderr.write(`error [${SCM_BODY_ENCODING_CODE}]: ${kind} #${id} body contains encoding mojibake (${findings.length} hit(s)):\n`);
|
|
283
|
+
for (const f of findings) {
|
|
284
|
+
process.stderr.write(`${renderFinding(f)}\n`);
|
|
285
|
+
}
|
|
286
|
+
process.stderr.write(`Repair: task scm:body:issue:fetch -- --repo OWNER/REPO --issue ${id} --out-file <path>, fix as UTF-8 (no BOM), task scm:body:issue:edit -- --body-file <path>. After non-scm:body mutations, re-run lint (#2960; recurrence #2948/#2944).\n`);
|
|
287
|
+
}
|
|
288
|
+
export function githubBodyMain(args, options = {}) {
|
|
179
289
|
try {
|
|
180
290
|
switch (args.command) {
|
|
181
291
|
case "issue-fetch": {
|
|
@@ -183,7 +293,42 @@ export function githubBodyMain(args) {
|
|
|
183
293
|
process.stderr.write("error: issue-fetch requires --repo, --issue, and --out-file\n");
|
|
184
294
|
return 1;
|
|
185
295
|
}
|
|
186
|
-
writeIssueBodyToFile(args.repo, args.issue, args.outFile
|
|
296
|
+
writeIssueBodyToFile(args.repo, args.issue, args.outFile, {
|
|
297
|
+
runFn: options.runFn,
|
|
298
|
+
binary: options.binary,
|
|
299
|
+
});
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
case "issue-lint": {
|
|
303
|
+
if (args.repo === undefined || args.issue === undefined) {
|
|
304
|
+
process.stderr.write("error: issue-lint requires --repo and --issue\n");
|
|
305
|
+
return 1;
|
|
306
|
+
}
|
|
307
|
+
const findings = lintIssueBody(args.repo, args.issue, {
|
|
308
|
+
runFn: options.runFn,
|
|
309
|
+
binary: options.binary,
|
|
310
|
+
});
|
|
311
|
+
if (findings.length > 0) {
|
|
312
|
+
writeLintFailure("issue", args.issue, findings);
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
process.stdout.write(`ok: issue #${args.issue} body has no CP1252/CP437-as-UTF-8 mojibake\n`);
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
case "pr-lint": {
|
|
319
|
+
if (args.repo === undefined || args.pr === undefined) {
|
|
320
|
+
process.stderr.write("error: pr-lint requires --repo and --pr\n");
|
|
321
|
+
return 1;
|
|
322
|
+
}
|
|
323
|
+
const findings = lintPrBody(args.repo, args.pr, {
|
|
324
|
+
runFn: options.runFn,
|
|
325
|
+
binary: options.binary,
|
|
326
|
+
});
|
|
327
|
+
if (findings.length > 0) {
|
|
328
|
+
writeLintFailure("pr", args.pr, findings);
|
|
329
|
+
return 1;
|
|
330
|
+
}
|
|
331
|
+
process.stdout.write(`ok: pr #${args.pr} body has no CP1252/CP437-as-UTF-8 mojibake\n`);
|
|
187
332
|
return 0;
|
|
188
333
|
}
|
|
189
334
|
default: {
|
|
@@ -191,19 +336,40 @@ export function githubBodyMain(args) {
|
|
|
191
336
|
let result;
|
|
192
337
|
switch (args.command) {
|
|
193
338
|
case "issue-create":
|
|
194
|
-
result = createIssue(args.repo, {
|
|
339
|
+
result = createIssue(args.repo, {
|
|
340
|
+
title: args.title,
|
|
341
|
+
body,
|
|
342
|
+
runFn: options.runFn,
|
|
343
|
+
binary: options.binary,
|
|
344
|
+
});
|
|
195
345
|
break;
|
|
196
346
|
case "issue-edit":
|
|
197
|
-
result = editIssueBody(args.repo, args.issue, {
|
|
347
|
+
result = editIssueBody(args.repo, args.issue, {
|
|
348
|
+
body,
|
|
349
|
+
runFn: options.runFn,
|
|
350
|
+
binary: options.binary,
|
|
351
|
+
});
|
|
198
352
|
break;
|
|
199
353
|
case "comment-create":
|
|
200
|
-
result = createIssueComment(args.repo, args.issue, {
|
|
354
|
+
result = createIssueComment(args.repo, args.issue, {
|
|
355
|
+
body,
|
|
356
|
+
runFn: options.runFn,
|
|
357
|
+
binary: options.binary,
|
|
358
|
+
});
|
|
201
359
|
break;
|
|
202
360
|
case "comment-edit":
|
|
203
|
-
result = editIssueCommentBody(args.repo, args.comment, {
|
|
361
|
+
result = editIssueCommentBody(args.repo, args.comment, {
|
|
362
|
+
body,
|
|
363
|
+
runFn: options.runFn,
|
|
364
|
+
binary: options.binary,
|
|
365
|
+
});
|
|
204
366
|
break;
|
|
205
367
|
case "pr-edit":
|
|
206
|
-
result = editPrBody(args.repo, args.pr, {
|
|
368
|
+
result = editPrBody(args.repo, args.pr, {
|
|
369
|
+
body,
|
|
370
|
+
runFn: options.runFn,
|
|
371
|
+
binary: options.binary,
|
|
372
|
+
});
|
|
207
373
|
break;
|
|
208
374
|
default:
|
|
209
375
|
process.stderr.write(`error: unknown command ${JSON.stringify(args.command)}\n`);
|
|
@@ -215,7 +381,12 @@ export function githubBodyMain(args) {
|
|
|
215
381
|
}
|
|
216
382
|
}
|
|
217
383
|
catch (exc) {
|
|
218
|
-
|
|
384
|
+
if (exc instanceof GitHubBodyError && exc.code !== undefined) {
|
|
385
|
+
process.stderr.write(`error [${exc.code}]: ${exc.message}\n`);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
process.stderr.write(`error: ${String(exc)}\n`);
|
|
389
|
+
}
|
|
219
390
|
return 1;
|
|
220
391
|
}
|
|
221
392
|
}
|
|
@@ -44,6 +44,7 @@ export declare function assertVbriefWriteTargetSafe(path: string, projectRoot?:
|
|
|
44
44
|
/**
|
|
45
45
|
* Persist an xBRIEF/vBRIEF JSON document. Gates the write target so a leaf or
|
|
46
46
|
* parent-directory symlink cannot divert the stamped file outside the project (#2869).
|
|
47
|
+
* #2980 wave B: product write sink routes through containedWrite.
|
|
47
48
|
*/
|
|
48
49
|
export declare function writeVbrief(path: string, data: Record<string, unknown>, projectRoot?: string | null): void;
|
|
49
50
|
export declare function vbriefTitle(data: Record<string, unknown>): string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { chmodSync,
|
|
2
|
+
import { chmodSync, globSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
5
|
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
6
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
6
7
|
import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
7
8
|
import { call } from "../scm/call.js";
|
|
8
9
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
@@ -83,7 +84,8 @@ export function recoverySidecarPath(vbriefAbsPath) {
|
|
|
83
84
|
}
|
|
84
85
|
/**
|
|
85
86
|
* Write recovery sidecar without following symlinks (#2880 Greptile P1).
|
|
86
|
-
* Trusted per-uid dir (ownership + mode) + O_EXCL|O_NOFOLLOW
|
|
87
|
+
* Trusted per-uid dir (ownership + mode) + containedWrite create (O_EXCL|O_NOFOLLOW).
|
|
88
|
+
* #2980 wave B: product write sink routes through containedWrite.
|
|
87
89
|
*/
|
|
88
90
|
function writeRecoverySidecarSafe(sidePath, payload) {
|
|
89
91
|
const dir = ensureTrustedRecoveryDir();
|
|
@@ -107,17 +109,12 @@ function writeRecoverySidecarSafe(sidePath, payload) {
|
|
|
107
109
|
throw err;
|
|
108
110
|
}
|
|
109
111
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
writeSync(fd, payload, undefined, "utf8");
|
|
117
|
-
}
|
|
118
|
-
finally {
|
|
119
|
-
closeSync(fd);
|
|
120
|
-
}
|
|
112
|
+
containedWrite({
|
|
113
|
+
root: dir,
|
|
114
|
+
target: sidePath,
|
|
115
|
+
data: payload,
|
|
116
|
+
mode: "create",
|
|
117
|
+
});
|
|
121
118
|
}
|
|
122
119
|
/**
|
|
123
120
|
* Always-on recovery after remote create: process memory + private OS-temp sidecar.
|
|
@@ -250,15 +247,20 @@ export function loadPendingEmitUrls(projectRoot) {
|
|
|
250
247
|
return {};
|
|
251
248
|
}
|
|
252
249
|
export function savePendingEmitUrl(projectRoot, vbriefAbsPath, url) {
|
|
250
|
+
const root = resolve(projectRoot);
|
|
253
251
|
const ledger = pendingEmitLedgerPath(projectRoot);
|
|
254
|
-
assertWriteTargetSafe(projectRoot, ledger);
|
|
255
|
-
mkdirSync(dirname(ledger), { recursive: true });
|
|
256
252
|
const map = loadPendingEmitUrls(projectRoot);
|
|
257
253
|
map[resolve(vbriefAbsPath)] = url;
|
|
258
|
-
|
|
259
|
-
|
|
254
|
+
// #2980 wave B: product write sink routes through containedWrite.
|
|
255
|
+
containedWrite({
|
|
256
|
+
root,
|
|
257
|
+
target: ledger,
|
|
258
|
+
data: `${JSON.stringify(map, null, 2)}\n`,
|
|
259
|
+
mode: "replace",
|
|
260
|
+
});
|
|
260
261
|
}
|
|
261
262
|
export function clearPendingEmitUrl(projectRoot, vbriefAbsPath) {
|
|
263
|
+
const root = resolve(projectRoot);
|
|
262
264
|
const ledger = pendingEmitLedgerPath(projectRoot);
|
|
263
265
|
const key = resolve(vbriefAbsPath);
|
|
264
266
|
const map = loadPendingEmitUrls(projectRoot);
|
|
@@ -267,9 +269,13 @@ export function clearPendingEmitUrl(projectRoot, vbriefAbsPath) {
|
|
|
267
269
|
}
|
|
268
270
|
delete map[key];
|
|
269
271
|
try {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
// #2980 wave B: product write sink routes through containedWrite.
|
|
273
|
+
containedWrite({
|
|
274
|
+
root,
|
|
275
|
+
target: ledger,
|
|
276
|
+
data: `${JSON.stringify(map, null, 2)}\n`,
|
|
277
|
+
mode: "replace",
|
|
278
|
+
});
|
|
273
279
|
}
|
|
274
280
|
catch {
|
|
275
281
|
// Best-effort clear; next successful stamp still works via existingGithubIssueRef.
|
|
@@ -310,10 +316,16 @@ export function assertVbriefWriteTargetSafe(path, projectRoot) {
|
|
|
310
316
|
/**
|
|
311
317
|
* Persist an xBRIEF/vBRIEF JSON document. Gates the write target so a leaf or
|
|
312
318
|
* parent-directory symlink cannot divert the stamped file outside the project (#2869).
|
|
319
|
+
* #2980 wave B: product write sink routes through containedWrite.
|
|
313
320
|
*/
|
|
314
321
|
export function writeVbrief(path, data, projectRoot) {
|
|
315
|
-
assertVbriefWriteTargetSafe(path, projectRoot);
|
|
316
|
-
|
|
322
|
+
const root = assertVbriefWriteTargetSafe(path, projectRoot);
|
|
323
|
+
containedWrite({
|
|
324
|
+
root,
|
|
325
|
+
target: resolve(path),
|
|
326
|
+
data: `${JSON.stringify(data, null, 2)}\n`,
|
|
327
|
+
mode: "replace",
|
|
328
|
+
});
|
|
317
329
|
}
|
|
318
330
|
export function vbriefTitle(data) {
|
|
319
331
|
const plan = (data.plan ?? {});
|
|
@@ -417,7 +429,13 @@ export function fileIssue(repo, title, body, scmCall = call) {
|
|
|
417
429
|
const tmpDir = mkdtempSync(join(tmpdir(), "deft-issue-emit-"));
|
|
418
430
|
const bodyPath = join(tmpDir, "body.md");
|
|
419
431
|
try {
|
|
420
|
-
|
|
432
|
+
// #2980 wave B: temp body file via containedWrite under the OS-temp dir.
|
|
433
|
+
containedWrite({
|
|
434
|
+
root: tmpDir,
|
|
435
|
+
target: "body.md",
|
|
436
|
+
data: body,
|
|
437
|
+
mode: "create",
|
|
438
|
+
});
|
|
421
439
|
const result = scmCall("github-issue", "issue", ["create", "--repo", repo, "--title", title, "--body-file", bodyPath], { timeout: 60 });
|
|
422
440
|
if (result.returncode !== 0) {
|
|
423
441
|
throw new IssueEmitError(`gh issue create failed (exit ${result.returncode}): ${(result.stderr ?? "").trim()}`);
|
|
@@ -330,15 +330,24 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
|
|
|
330
330
|
const labelNames = [];
|
|
331
331
|
if (Array.isArray(labelsRaw)) {
|
|
332
332
|
for (const lbl of labelsRaw) {
|
|
333
|
+
let rawName;
|
|
333
334
|
if (typeof lbl === "string") {
|
|
334
|
-
|
|
335
|
+
rawName = lbl;
|
|
335
336
|
}
|
|
336
337
|
else if (lbl !== null && typeof lbl === "object" && !Array.isArray(lbl)) {
|
|
337
338
|
const name = lbl.name;
|
|
338
339
|
if (typeof name === "string" && name.length > 0) {
|
|
339
|
-
|
|
340
|
+
rawName = name;
|
|
340
341
|
}
|
|
341
342
|
}
|
|
343
|
+
if (rawName === undefined || rawName.length === 0) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
// #2916: label names copy unchanged into narratives.Labels and plan.tags --
|
|
347
|
+
// agent-authoritative fields. Quarantine-scan them under the same contract as
|
|
348
|
+
// titles/body: hard-fail closed on credential-shaped labels, fence/omit
|
|
349
|
+
// injection-shaped labels. (cache-quarantine-06, tracker #2904)
|
|
350
|
+
labelNames.push(scanUntrustedIngestText(number, rawName));
|
|
342
351
|
}
|
|
343
352
|
}
|
|
344
353
|
const [folder, planStatus] = STATUS_MAP[status];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { contentRoot } from "../content-root.js";
|
|
5
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
5
6
|
import { findSkillPathsInText } from "../text/redos-safe.js";
|
|
6
7
|
/** Sentinel used by SKILL.md redirect stubs. */
|
|
7
8
|
export const DEPRECATED_SKILL_REDIRECT_SENTINEL = "<!-- deft:deprecated-skill-redirect -->";
|
|
@@ -87,9 +88,16 @@ export function emit(name, payload = null, options = {}) {
|
|
|
87
88
|
const logTarget = process.env[logPathEnv];
|
|
88
89
|
if (logTarget !== undefined && logTarget.length > 0) {
|
|
89
90
|
try {
|
|
91
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
90
92
|
const logPath = resolve(logTarget);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
const dir = dirname(logPath);
|
|
94
|
+
mkdirSync(dir, { recursive: true });
|
|
95
|
+
containedWrite({
|
|
96
|
+
root: dir,
|
|
97
|
+
target: basename(logPath),
|
|
98
|
+
data: `${JSON.stringify(record)}\n`,
|
|
99
|
+
mode: "append",
|
|
100
|
+
});
|
|
93
101
|
}
|
|
94
102
|
catch {
|
|
95
103
|
// Swallow log-write failures.
|
package/dist/lifecycle/events.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { contentRoot } from "../content-root.js";
|
|
6
6
|
import { ATTRIBUTION_REQUIRED_PAYLOAD } from "../events/attribution-constants.js";
|
|
7
|
-
import {
|
|
7
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
8
|
+
import { ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
8
9
|
/** Default event log location (project-local). */
|
|
9
10
|
export const DEFAULT_EVENT_LOG = join(".deft-cache", "events.jsonl");
|
|
10
11
|
const BEHAVIORAL_CATEGORY = "behavioral";
|
|
@@ -166,22 +167,32 @@ function assertPathComponentsNotSymlinks(projectDir, targetPath) {
|
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
|
-
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Append one event log line via containedWrite (#2951 Phase 2).
|
|
172
|
+
* Project-nested logs use projectRoot as containment root; explicit external
|
|
173
|
+
* logs still refuse symlink path components, then contain under the log parent.
|
|
174
|
+
*/
|
|
175
|
+
function appendEventLogLine(projectRoot, targetPath, line) {
|
|
176
|
+
const targetAbs = resolve(targetPath);
|
|
177
|
+
if (isNestedUnder(projectRoot, targetAbs)) {
|
|
178
|
+
containedWrite({
|
|
179
|
+
root: resolve(projectRoot),
|
|
180
|
+
target: targetAbs,
|
|
181
|
+
data: line,
|
|
182
|
+
mode: "append",
|
|
183
|
+
});
|
|
173
184
|
return;
|
|
174
185
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
186
|
+
// Explicit --log / DEFT_EVENT_LOG outside the project tree.
|
|
187
|
+
assertPathComponentsNotSymlinks(projectRoot, targetAbs);
|
|
188
|
+
const parent = dirname(targetAbs);
|
|
189
|
+
mkdirSync(parent, { recursive: true });
|
|
190
|
+
containedWrite({
|
|
191
|
+
root: resolve(parent),
|
|
192
|
+
target: basename(targetAbs),
|
|
193
|
+
data: line,
|
|
194
|
+
mode: "append",
|
|
195
|
+
});
|
|
185
196
|
}
|
|
186
197
|
function newEventId() {
|
|
187
198
|
const wallNs = BigInt(Date.now()) * 1000000n;
|
|
@@ -210,9 +221,7 @@ export function emit(name, payload, options = {}) {
|
|
|
210
221
|
};
|
|
211
222
|
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
212
223
|
const target = resolveLogPath(options.logPath, projectRoot);
|
|
213
|
-
|
|
214
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
215
|
-
appendLineNoFollow(target, `${jsonStringifySorted(record)}\n`);
|
|
224
|
+
appendEventLogLine(projectRoot, target, `${jsonStringifySorted(record)}\n`);
|
|
216
225
|
return record;
|
|
217
226
|
}
|
|
218
227
|
/** Return all events from the log in emission order. */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
3
|
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
4
4
|
import { computeReport } from "../capacity/show.js";
|
|
5
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
5
6
|
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
6
7
|
import { resolveCapacityAllocation } from "../policy/capacity.js";
|
|
7
8
|
import { readPlanPolicy } from "../policy/plan-extensions.js";
|
|
@@ -202,14 +203,19 @@ export function recordTechDebtAcceptance(projectRoot, epic, options) {
|
|
|
202
203
|
throw new Error("follow_up_ref must be a non-empty reference string");
|
|
203
204
|
}
|
|
204
205
|
const path = techDebtLedgerPath(projectRoot);
|
|
205
|
-
mkdirSync(resolve(path, ".."), { recursive: true });
|
|
206
206
|
const record = {
|
|
207
207
|
accepted_at: utcIso(options.now ?? null),
|
|
208
208
|
actor: options.actor ?? DEFAULT_ACTOR,
|
|
209
209
|
epic: epicKey,
|
|
210
210
|
follow_up_ref: followUpRef.trim(),
|
|
211
211
|
};
|
|
212
|
-
|
|
212
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
213
|
+
containedWrite({
|
|
214
|
+
root: resolve(projectRoot),
|
|
215
|
+
target: path,
|
|
216
|
+
data: `${JSON.stringify(sortKeys(record))}\n`,
|
|
217
|
+
mode: "append",
|
|
218
|
+
});
|
|
213
219
|
return path;
|
|
214
220
|
}
|
|
215
221
|
/** Return the set of epic basenames already accepted as tech-debt. */
|
|
@@ -17,11 +17,44 @@ export interface RenderRegistryEntry {
|
|
|
17
17
|
readonly name_field?: string;
|
|
18
18
|
readonly description_field?: string;
|
|
19
19
|
readonly path_field?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Pack-schema path pattern (mirrors content/vbrief/schemas/<pack>-pack.schema.json
|
|
22
|
+
* `path.pattern`). Enforced fail-closed before render so a malicious pack JSON
|
|
23
|
+
* cannot smuggle an off-shape projection path. Refs #2914 / #2904.
|
|
24
|
+
*/
|
|
25
|
+
readonly path_pattern?: RegExp;
|
|
20
26
|
}
|
|
27
|
+
/** Non-zero exit code for a pack-render containment / schema refusal. */
|
|
28
|
+
export declare const PACK_RENDER_REFUSED_EXIT_CODE = 2;
|
|
29
|
+
/**
|
|
30
|
+
* Thrown, fail-closed, when a pack record's projection path escapes CONTENT_ROOT,
|
|
31
|
+
* violates the pack path schema, or a frontmatter field carries an injection
|
|
32
|
+
* payload (null byte / newline). Remediates AppSec finding skill-pack-supply-01
|
|
33
|
+
* from tracker #2904. Refs #2914.
|
|
34
|
+
*/
|
|
35
|
+
export declare class PackRenderContainmentError extends Error {
|
|
36
|
+
readonly pack: string;
|
|
37
|
+
readonly field: string;
|
|
38
|
+
readonly offendingValue: string;
|
|
39
|
+
constructor(message: string, details: {
|
|
40
|
+
pack: string;
|
|
41
|
+
field: string;
|
|
42
|
+
offendingValue: string;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a pack record's projection path against CONTENT_ROOT and refuse,
|
|
47
|
+
* fail-closed, any value that: is missing/empty/non-string, carries a null
|
|
48
|
+
* byte, is absolute, violates the pack's schema path pattern, or resolves
|
|
49
|
+
* outside CONTENT_ROOT (a `..` escape). Returns the contained absolute output
|
|
50
|
+
* path. Mirrors deposit/contain.ts segment containment. Refs #2914 / #2904.
|
|
51
|
+
*/
|
|
52
|
+
export declare function containedOutPath(packName: string, rawPath: unknown, cfg: RenderRegistryEntry): string;
|
|
21
53
|
export declare const RENDER_REGISTRY: Record<string, RenderRegistryEntry>;
|
|
22
54
|
export declare function renderCollection(pack: Record<string, unknown>, cfg: RenderRegistryEntry): string;
|
|
23
55
|
export declare function renderSkillDocument(entry: Record<string, unknown>, cfg: RenderRegistryEntry): string;
|
|
24
56
|
export declare function renderMarkdownDocument(entry: Record<string, unknown>, cfg: RenderRegistryEntry): string;
|
|
57
|
+
export declare function targetsForPack(packName: string, pack: Record<string, unknown>, cfg: RenderRegistryEntry): Array<[string, string]>;
|
|
25
58
|
export declare function collectTargets(packFilter?: string | null): Array<[string, string, string]>;
|
|
26
59
|
export declare function render(pack: Record<string, unknown>): string;
|
|
27
60
|
export declare function renderFile(source: string): string;
|