@deftai/directive-core 0.108.0 → 0.109.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/operations.js +1 -1
- package/dist/check/gate-lists.d.ts +14 -0
- package/dist/check/gate-lists.js +26 -3
- package/dist/check/session-completed-ac.d.ts +1 -1
- package/dist/check/session-completed-ac.js +1 -1
- package/dist/consumer-check-contract/evaluate.d.ts +47 -0
- package/dist/consumer-check-contract/evaluate.js +181 -15
- package/dist/delivery-attempt/evaluate.d.ts +9 -1
- package/dist/delivery-attempt/evaluate.js +69 -0
- package/dist/delivery-attempt/index.d.ts +1 -1
- package/dist/delivery-attempt/index.js +1 -1
- package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
- package/dist/deposit/live-procedure-exclusions.js +110 -0
- package/dist/deposit/live-procedure-targets.d.ts +45 -0
- package/dist/deposit/live-procedure-targets.js +274 -0
- package/dist/deposit/python-free.d.ts +6 -0
- package/dist/deposit/python-free.js +15 -0
- package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
- package/dist/deposit/rewrite-deposit-links.js +148 -0
- package/dist/deposit/run-stage-content-pack.d.ts +2 -0
- package/dist/deposit/run-stage-content-pack.js +3 -0
- package/dist/deposit/stage-content-pack.d.ts +17 -0
- package/dist/deposit/stage-content-pack.js +91 -0
- package/dist/design-critique/citation-grammar.d.ts +7 -0
- package/dist/design-critique/citation-grammar.js +1 -1
- package/dist/design-critique/completed-arc-record.d.ts +1 -1
- package/dist/design-critique/completed-arc-record.js +59 -14
- package/dist/hooks/classify/host-session-identity.d.ts +24 -11
- package/dist/hooks/classify/host-session-identity.js +46 -49
- package/dist/hooks/classify/index.d.ts +2 -2
- package/dist/hooks/classify/index.js +2 -2
- package/dist/hooks/classify/paths.d.ts +2 -0
- package/dist/hooks/classify/paths.js +8 -4
- package/dist/hooks/classify/stdin.js +69 -3
- package/dist/hooks/dest-form.d.ts +20 -1
- package/dist/hooks/dest-form.js +158 -21
- package/dist/hooks/dispatcher.d.ts +6 -2
- package/dist/hooks/dispatcher.js +258 -62
- package/dist/hooks/fixtures/cases.d.ts +6 -0
- package/dist/hooks/fixtures/cases.js +53 -0
- package/dist/hooks/git-destructive-log.d.ts +32 -0
- package/dist/hooks/git-destructive-log.js +46 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/shell-write-targets.d.ts +10 -0
- package/dist/hooks/shell-write-targets.js +274 -0
- package/dist/hooks/tools.d.ts +20 -2
- package/dist/hooks/tools.js +30 -1
- package/dist/init-deposit/gitignore.d.ts +7 -0
- package/dist/init-deposit/gitignore.js +24 -0
- package/dist/init-deposit/init-deposit.js +3 -0
- package/dist/init-deposit/refresh.js +7 -0
- package/dist/init-deposit/runtime-writers.d.ts +14 -0
- package/dist/init-deposit/runtime-writers.js +33 -0
- package/dist/intake/issue-ingest.d.ts +6 -1
- package/dist/intake/issue-ingest.js +21 -2
- package/dist/intake/reconcile-issues.js +13 -13
- package/dist/lifecycle/brief-envelope.d.ts +25 -0
- package/dist/lifecycle/brief-envelope.js +42 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/evaluate.js +14 -5
- package/dist/literal-acceptance/index.d.ts +1 -1
- package/dist/literal-acceptance/index.js +1 -1
- package/dist/literal-acceptance/run.d.ts +2 -0
- package/dist/literal-acceptance/run.js +19 -1
- package/dist/orphan-active/candidate-scope.d.ts +53 -0
- package/dist/orphan-active/candidate-scope.js +157 -0
- package/dist/orphan-active/evaluate.d.ts +27 -0
- package/dist/orphan-active/evaluate.js +63 -6
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/policy/merge-approval-head.js +7 -6
- package/dist/pr-closing-keywords/gh.js +32 -9
- package/dist/pr-closing-keywords/main.d.ts +1 -0
- package/dist/pr-closing-keywords/main.js +68 -3
- package/dist/pr-closing-keywords/types.d.ts +2 -0
- package/dist/pr-merge-readiness/gh.js +32 -9
- package/dist/pr-protected-issues/gh.js +36 -9
- package/dist/pr-wait-mergeable/wrappers.js +3 -3
- package/dist/product-first-done-gate/evaluate.js +6 -2
- package/dist/product-first-done-gate/types.js +2 -0
- package/dist/release/consumer-hard-stops.d.ts +48 -0
- package/dist/release/consumer-hard-stops.js +140 -0
- package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
- package/dist/release/consumer-readiness-disclosure.js +51 -0
- package/dist/release/index.d.ts +2 -0
- package/dist/release/index.js +2 -0
- package/dist/release/pipeline.js +55 -0
- package/dist/release/run-consumer-readiness.d.ts +15 -0
- package/dist/release/run-consumer-readiness.js +24 -0
- package/dist/release/types.d.ts +6 -0
- package/dist/review-monitor/github-lease.js +4 -3
- package/dist/run-summary/types.d.ts +2 -2
- package/dist/scm/build-command.d.ts +2 -2
- package/dist/scm/build-command.js +2 -2
- package/dist/scm/call-shape.d.ts +25 -0
- package/dist/scm/call-shape.js +59 -0
- package/dist/scm/call.d.ts +7 -4
- package/dist/scm/call.js +41 -11
- package/dist/scm/gh-rest.d.ts +25 -12
- package/dist/scm/gh-rest.js +65 -15
- package/dist/scm/index.d.ts +2 -0
- package/dist/scm/index.js +2 -0
- package/dist/scm/spawn-status.d.ts +33 -0
- package/dist/scm/spawn-status.js +53 -0
- package/dist/scope/acceptance-evidence.d.ts +1 -1
- package/dist/scope/transition.js +4 -14
- package/dist/session/ac-pass-banking.d.ts +2 -2
- package/dist/session/ac-pass-banking.js +2 -2
- package/dist/session/host-session-owner.d.ts +53 -0
- package/dist/session/host-session-owner.js +89 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/occupancy.d.ts +21 -1
- package/dist/session/occupancy.js +70 -12
- package/dist/session/verify-ac-session-cache.d.ts +2 -2
- package/dist/session/verify-ac-session-cache.js +2 -2
- package/dist/triage/evaluate/worktrees.js +153 -6
- package/dist/umbrella-current-shape/index.d.ts +51 -3
- package/dist/umbrella-current-shape/index.js +106 -18
- package/dist/validate-content/deposit-required.d.ts +39 -0
- package/dist/validate-content/deposit-required.js +147 -0
- package/dist/validate-content/index.d.ts +1 -0
- package/dist/validate-content/index.js +1 -0
- package/dist/validate-content/validate-links.d.ts +2 -3
- package/dist/validate-content/validate-links.js +29 -2
- package/dist/vbrief-activate/activate.d.ts +7 -2
- package/dist/vbrief-activate/activate.js +26 -13
- package/dist/verify-ac/evaluate.d.ts +9 -0
- package/dist/verify-ac/evaluate.js +32 -9
- package/dist/verify-source/deposit-closure.d.ts +23 -0
- package/dist/verify-source/deposit-closure.js +162 -0
- package/dist/verify-source/index.d.ts +2 -0
- package/dist/verify-source/index.js +2 -0
- package/dist/verify-source/semantic-single-source.d.ts +36 -0
- package/dist/verify-source/semantic-single-source.js +349 -0
- package/package.json +3 -3
|
@@ -40,6 +40,7 @@ import { containedRemove, containedWrite } from "../fs/contained-write.js";
|
|
|
40
40
|
import { assertWriteTargetSafe } from "../fs/projection-containment.js";
|
|
41
41
|
import { assertAppendLockOwned, withAppendLock } from "../slice/lock.js";
|
|
42
42
|
import { SWARM_WORKER_ROLES } from "../swarm/routing.js";
|
|
43
|
+
import { ambientHostSessionOwner } from "./host-session-owner.js";
|
|
43
44
|
import { stableJson } from "./json.js";
|
|
44
45
|
import { parseTimestamp, timestampIso } from "./time.js";
|
|
45
46
|
export const OCCUPANCY_SCHEMA_VERSION = 1;
|
|
@@ -166,6 +167,19 @@ export function occupancyAdmission(record, sessionId, now = new Date()) {
|
|
|
166
167
|
return "owner";
|
|
167
168
|
return occupancyGrantFor(record, presented, now) === null ? "stranger" : "member";
|
|
168
169
|
}
|
|
170
|
+
// Session ids reach remediation text from operator flags, host environments and
|
|
171
|
+
// whatever a peer wrote into the lease, so a value can carry whitespace or shell
|
|
172
|
+
// metacharacters. Only a value a shell would take as one bare token is inlined
|
|
173
|
+
// into a printed command; anything else keeps its placeholder, because the right
|
|
174
|
+
// quoting differs per shell and a mis-parsed copyable command is worse than one
|
|
175
|
+
// the reader has to fill in. The id itself is still named in the prose above.
|
|
176
|
+
// A leading dash is excluded as well: every CLI parser here reads such a value
|
|
177
|
+
// as another option, so `--occupant --weird-id` fails argument parsing even
|
|
178
|
+
// though the shell itself would have passed the token through intact.
|
|
179
|
+
const SHELL_SAFE_SESSION_ID = /^(?!-)[A-Za-z0-9_.:+=,/-]+$/;
|
|
180
|
+
function commandSessionId(sessionId, placeholder) {
|
|
181
|
+
return SHELL_SAFE_SESSION_ID.test(sessionId) ? sessionId : placeholder;
|
|
182
|
+
}
|
|
169
183
|
function occupancyClockLine(record) {
|
|
170
184
|
const lastWrite = record.lastWriteAt === null ? "" : ` last_write_at=${timestampIso(record.lastWriteAt)}`;
|
|
171
185
|
return `claimed_at=${timestampIso(record.claimedAt)} heartbeat_at=${timestampIso(record.heartbeatAt)}${lastWrite}`;
|
|
@@ -178,7 +192,7 @@ export function formatOccupancyStaleWarning(record, now = new Date(), ttlMs = OC
|
|
|
178
192
|
const age = heartbeatAgeSeconds(record, now);
|
|
179
193
|
return (`Occupancy lease for session ${record.sessionId} has not beaten for ${age}s of its ` +
|
|
180
194
|
`${Math.round(ttlMs / 1000)}s window; another session may read it as abandoned. ` +
|
|
181
|
-
`Refresh it with \`deft occupancy:heartbeat --session-id=${record.sessionId}\`.`);
|
|
195
|
+
`Refresh it with \`deft occupancy:heartbeat --session-id=${commandSessionId(record.sessionId, "<your-session-id>")}\`.`);
|
|
182
196
|
}
|
|
183
197
|
/**
|
|
184
198
|
* Tell the holder its lease aged out of the absolute cap (#3599). Distinct
|
|
@@ -191,17 +205,45 @@ export function formatOccupancyAgeCapRemediation(record, now = new Date(), maxLe
|
|
|
191
205
|
`(claimed ${leaseAgeSeconds(record, now)}s ago, ${occupancyClockLine(record)}), so this ` +
|
|
192
206
|
"worktree is no longer held and a peer may claim it at any moment. Heartbeats cannot " +
|
|
193
207
|
"extend a capped lease — re-claim the worktree with " +
|
|
194
|
-
`\`deft session:start --session-id=${record.sessionId}\` before writing again.`);
|
|
208
|
+
`\`deft session:start --session-id=${commandSessionId(record.sessionId, "<your-session-id>")}\` before writing again.`);
|
|
195
209
|
}
|
|
196
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Tell a refused caller who holds the lease and what it can actually run.
|
|
212
|
+
*
|
|
213
|
+
* `presented` is the id the refused caller offered (#3873). Without it the
|
|
214
|
+
* message can only print `<your-session-id>` placeholders, which is fine for a
|
|
215
|
+
* CLI caller that passed its own `--session-id` and useless to a hook process,
|
|
216
|
+
* which does not know what identity it presented. Passing it also keeps the
|
|
217
|
+
* message honest when there is none: a grant cannot name an empty child --
|
|
218
|
+
* `occupancy:grant --child-session-id=` is refused at parse and at membership --
|
|
219
|
+
* so that remediation is not printed to a caller who could never run it.
|
|
220
|
+
*/
|
|
221
|
+
export function formatOccupancyRemediation(record, now = new Date(), presented) {
|
|
197
222
|
const age = heartbeatAgeSeconds(record, now);
|
|
198
|
-
|
|
199
|
-
`${formatLastWritePhrase(record, now)}, ${occupancyClockLine(record)}).\n
|
|
223
|
+
const header = `Worktree occupied by session ${record.sessionId} (intent=${record.intent}, heartbeat ${age}s ago, ` +
|
|
224
|
+
`${formatLastWritePhrase(record, now)}, ${occupancyClockLine(record)}).\n`;
|
|
225
|
+
const tail = "\nThe occupant may release (`occupancy:release` / `session:end`).";
|
|
226
|
+
if (presented === undefined) {
|
|
227
|
+
return (`${header}Stay read-only (\`session:start --read-only\`), use another worktree,\n` +
|
|
228
|
+
"ask the occupant for a write grant (`occupancy:grant --child-session-id=<your-session-id> " +
|
|
229
|
+
"--role <worker-role>`, run by the occupant), or run a confirmed owner transition " +
|
|
230
|
+
`(\`session:start --steal --confirm --occupant <reported-session-id> --session-id=<your-session-id>\`).${tail}`);
|
|
231
|
+
}
|
|
232
|
+
const actor = presented.trim();
|
|
233
|
+
const occupantArg = commandSessionId(record.sessionId, "<reported-session-id>");
|
|
234
|
+
if (actor.length === 0) {
|
|
235
|
+
return (`${header}This process presented no session identity, so a write grant cannot name it ` +
|
|
236
|
+
"and an owner transition would not be recognised on its next write.\n" +
|
|
237
|
+
"Stay read-only (`session:start --read-only`), use another worktree, or ask the occupant " +
|
|
238
|
+
`to release the lease (\`occupancy:release --session-id=${occupantArg}\` / \`session:end\`).${tail}`);
|
|
239
|
+
}
|
|
240
|
+
const actorArg = commandSessionId(actor, "<your-session-id>");
|
|
241
|
+
return (`${header}This process presented session ${actor}, which neither holds that lease nor has a ` +
|
|
242
|
+
"write grant on it.\n" +
|
|
200
243
|
"Stay read-only (`session:start --read-only`), use another worktree,\n" +
|
|
201
|
-
|
|
244
|
+
`ask the occupant for a write grant (\`occupancy:grant --child-session-id=${actorArg} ` +
|
|
202
245
|
"--role <worker-role>`, run by the occupant), or run a confirmed owner transition " +
|
|
203
|
-
|
|
204
|
-
"The occupant may release (`occupancy:release` / `session:end`).");
|
|
246
|
+
`(\`session:start --steal --confirm --occupant ${occupantArg} --session-id=${actorArg}\`).${tail}`);
|
|
205
247
|
}
|
|
206
248
|
/**
|
|
207
249
|
* Refuse an administrative verb to a granted child (#3755). Named apart from
|
|
@@ -215,13 +257,26 @@ export function formatOccupancyMemberAdministrationRefusal(record, grant, verb)
|
|
|
215
257
|
"grant never escalates into administration.\n" +
|
|
216
258
|
`Ask the occupant (session ${record.sessionId}) to run it, or wait for the grant to expire.`);
|
|
217
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* The owner a claim is made under: an explicit id, then `DEFT_SESSION_ID`, then
|
|
262
|
+
* the id the running host published, then a mint.
|
|
263
|
+
*
|
|
264
|
+
* The host step is what makes an identified host's claim reachable (#3873).
|
|
265
|
+
* Minting instead binds the lease to an id no later hook process can present,
|
|
266
|
+
* so the session that claimed the worktree is refused by its own lease. The
|
|
267
|
+
* mint stays as the last resort for hosts that publish nothing.
|
|
268
|
+
*/
|
|
218
269
|
export function resolveOccupancySessionId(input = {}) {
|
|
219
270
|
const explicit = input.sessionId?.trim();
|
|
220
271
|
if (explicit)
|
|
221
272
|
return explicit;
|
|
222
|
-
const
|
|
273
|
+
const env = input.env ?? process.env;
|
|
274
|
+
const envId = env.DEFT_SESSION_ID?.trim();
|
|
223
275
|
if (envId)
|
|
224
276
|
return envId;
|
|
277
|
+
const hostOwner = ambientHostSessionOwner(env);
|
|
278
|
+
if (hostOwner !== null)
|
|
279
|
+
return hostOwner;
|
|
225
280
|
return (input.newSessionId ?? randomUUID)();
|
|
226
281
|
}
|
|
227
282
|
export function readOccupancy(projectRoot) {
|
|
@@ -596,7 +651,7 @@ export function grantOccupancyMembership(projectRoot, input = {}) {
|
|
|
596
651
|
message: capped && current !== null
|
|
597
652
|
? formatOccupancyAgeCapRemediation(current, now)
|
|
598
653
|
: "occupancy:grant found no live lease to grant on. A grant is derived authority, " +
|
|
599
|
-
`so claim the worktree first with \`deft session:start --session-id=${owner}\`.`,
|
|
654
|
+
`so claim the worktree first with \`deft session:start --session-id=${commandSessionId(owner, "<your-session-id>")}\`.`,
|
|
600
655
|
code: 1,
|
|
601
656
|
};
|
|
602
657
|
}
|
|
@@ -809,7 +864,10 @@ export function evaluateOccupancyWriteGate(projectRoot, input = {}) {
|
|
|
809
864
|
if (admission === "stranger") {
|
|
810
865
|
return {
|
|
811
866
|
allow: false,
|
|
812
|
-
|
|
867
|
+
// The refused caller is told what identity it actually presented (#3873).
|
|
868
|
+
// A hook process cannot otherwise know, and the grant this message offers
|
|
869
|
+
// is only runnable when the occupant can name a non-empty child.
|
|
870
|
+
message: formatOccupancyRemediation(live, now, incoming),
|
|
813
871
|
occupant: live,
|
|
814
872
|
refreshed: false,
|
|
815
873
|
warning: null,
|
|
@@ -974,7 +1032,7 @@ export function heartbeatOccupancy(projectRoot, input = {}) {
|
|
|
974
1032
|
message: capped && existing !== null
|
|
975
1033
|
? formatOccupancyAgeCapRemediation(existing, now)
|
|
976
1034
|
: "occupancy:heartbeat found no live lease to refresh. Claim one with " +
|
|
977
|
-
`\`deft session:start --session-id=${caller}\`.`,
|
|
1035
|
+
`\`deft session:start --session-id=${commandSessionId(caller, "<your-session-id>")}\`.`,
|
|
978
1036
|
code: 1,
|
|
979
1037
|
};
|
|
980
1038
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Disk-backed so check composition (spawned verify:ac) can serve from_cache
|
|
5
5
|
* after a green run in the same session when plan + product hash still match.
|
|
6
6
|
*/
|
|
7
|
-
export declare const VERIFY_AC_SESSION_CACHE_DIR = ".deft/verify-ac-session-cache";
|
|
8
|
-
export declare const AC_SERVED_FROM: readonly ["bank", "cache", "executed"];
|
|
7
|
+
export declare const VERIFY_AC_SESSION_CACHE_DIR = ".deft/cache/verify-ac-session-cache";
|
|
8
|
+
export declare const AC_SERVED_FROM: readonly ["bank", "cache", "executed", "refused"];
|
|
9
9
|
export type AcServedFrom = (typeof AC_SERVED_FROM)[number];
|
|
10
10
|
export interface CachedVerifyAcSnapshot {
|
|
11
11
|
readonly ok: boolean;
|
|
@@ -8,8 +8,8 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
|
8
8
|
import { join, resolve } from "node:path";
|
|
9
9
|
import { containedWrite } from "../fs/contained-write.js";
|
|
10
10
|
import { sanitizeScopeIdForFilename } from "./ac-pass-banking.js";
|
|
11
|
-
export const VERIFY_AC_SESSION_CACHE_DIR = ".deft/verify-ac-session-cache";
|
|
12
|
-
export const AC_SERVED_FROM = ["bank", "cache", "executed"];
|
|
11
|
+
export const VERIFY_AC_SESSION_CACHE_DIR = ".deft/cache/verify-ac-session-cache";
|
|
12
|
+
export const AC_SERVED_FROM = ["bank", "cache", "executed", "refused"];
|
|
13
13
|
function asRecord(value) {
|
|
14
14
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
15
15
|
return value;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { containedRemove, containedWrite } from "../../fs/contained-write.js";
|
|
3
5
|
import { defaultGitRunner as swarmGitRunner, } from "../../swarm/worktrees.js";
|
|
4
6
|
import { evaluatorWorktreePath } from "./paths.js";
|
|
5
7
|
export class EvaluatorWorktreeError extends Error {
|
|
@@ -27,17 +29,162 @@ function forceDeleteWorktreeDir(worktreePath) {
|
|
|
27
29
|
}
|
|
28
30
|
rmSync(worktreePath, { recursive: true, force: true });
|
|
29
31
|
}
|
|
32
|
+
const caseInsensitiveDirCache = new Map();
|
|
33
|
+
function slashResolve(path) {
|
|
34
|
+
return resolve(path).replace(/\\/g, "/");
|
|
35
|
+
}
|
|
36
|
+
function directoryIgnoresCase(dir) {
|
|
37
|
+
let existing = resolve(dir);
|
|
38
|
+
while (!existsSync(existing)) {
|
|
39
|
+
const parent = dirname(existing);
|
|
40
|
+
if (parent === existing) {
|
|
41
|
+
return process.platform === "win32";
|
|
42
|
+
}
|
|
43
|
+
existing = parent;
|
|
44
|
+
}
|
|
45
|
+
const cached = caseInsensitiveDirCache.get(existing);
|
|
46
|
+
if (cached !== undefined) {
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
const tag = randomBytes(6).toString("hex");
|
|
50
|
+
const lower = join(existing, `.deft-cs-${tag}a`);
|
|
51
|
+
const upper = join(existing, `.deft-cs-${tag}A`);
|
|
52
|
+
let ignores = false;
|
|
53
|
+
let probed = false;
|
|
54
|
+
try {
|
|
55
|
+
containedWrite({
|
|
56
|
+
root: existing,
|
|
57
|
+
target: lower,
|
|
58
|
+
data: "",
|
|
59
|
+
mode: "create",
|
|
60
|
+
mkdir: false,
|
|
61
|
+
mutation: false,
|
|
62
|
+
});
|
|
63
|
+
ignores = existsSync(upper);
|
|
64
|
+
probed = true;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Fail closed: do not fold unless the probe proved case-insensitivity.
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
try {
|
|
71
|
+
containedRemove({ root: existing, target: lower, mutation: false });
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// ignore
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
containedRemove({ root: existing, target: upper, mutation: false });
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// ignore
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (probed) {
|
|
84
|
+
caseInsensitiveDirCache.set(existing, ignores);
|
|
85
|
+
}
|
|
86
|
+
return ignores;
|
|
87
|
+
}
|
|
88
|
+
function canonicalizeWorktreePath(path) {
|
|
89
|
+
const resolved = slashResolve(path);
|
|
90
|
+
let existing = "";
|
|
91
|
+
if (existsSync(path)) {
|
|
92
|
+
existing = path;
|
|
93
|
+
}
|
|
94
|
+
else if (existsSync(resolved)) {
|
|
95
|
+
existing = resolved;
|
|
96
|
+
}
|
|
97
|
+
if (existing.length > 0) {
|
|
98
|
+
try {
|
|
99
|
+
return realpathSync.native(existing).replace(/\\/g, "/");
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Path vanished between exists and realpath.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const parent = dirname(resolved);
|
|
106
|
+
const base = basename(resolved);
|
|
107
|
+
let parentCanon = slashResolve(parent);
|
|
108
|
+
try {
|
|
109
|
+
if (existsSync(parent)) {
|
|
110
|
+
parentCanon = realpathSync.native(parent).replace(/\\/g, "/");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Keep slash-resolved parent.
|
|
115
|
+
}
|
|
116
|
+
const leaf = directoryIgnoresCase(parent) ? base.toLowerCase() : base;
|
|
117
|
+
return `${parentCanon}/${leaf}`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Unregister one missing worktree by deleting only its `$GIT_DIR/worktrees/<id>`
|
|
121
|
+
* admin directory. Containment root is that worktrees dir (the git common dir
|
|
122
|
+
* may sit outside a linked-worktree projectRoot). `git worktree prune` has no
|
|
123
|
+
* path argument and operates on every registration, including concurrent
|
|
124
|
+
* agents' reflogs.
|
|
125
|
+
*
|
|
126
|
+
* Path identity uses on-disk realpath when the worktree exists, and otherwise
|
|
127
|
+
* folds case only when the parent directory is case-insensitive. A case-sensitive
|
|
128
|
+
* Windows directory can host two worktrees that differ only by case; those
|
|
129
|
+
* must not share an admin entry.
|
|
130
|
+
*/
|
|
131
|
+
function pruneEvaluatorWorktreeAdmin(git, projectRoot, worktreePath) {
|
|
132
|
+
const common = git(["rev-parse", "--git-common-dir"], projectRoot);
|
|
133
|
+
const trimmed = common.stdout.trim();
|
|
134
|
+
if (common.returncode !== 0 || trimmed.length === 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const commonDir = isAbsolute(trimmed) ? resolve(trimmed) : resolve(projectRoot, trimmed);
|
|
138
|
+
const worktreesDir = join(commonDir, "worktrees");
|
|
139
|
+
if (!existsSync(worktreesDir)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const needle = canonicalizeWorktreePath(worktreePath);
|
|
143
|
+
let names = [];
|
|
144
|
+
try {
|
|
145
|
+
names = readdirSync(worktreesDir);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
for (const name of names) {
|
|
151
|
+
if (name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const gitdirFile = join(worktreesDir, name, "gitdir");
|
|
155
|
+
if (!existsSync(gitdirFile)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
let recorded = "";
|
|
159
|
+
try {
|
|
160
|
+
recorded = readFileSync(gitdirFile, "utf8").trim();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const recordedWorktree = recorded.replace(/\\/g, "/").replace(/\/\.git$/u, "");
|
|
166
|
+
if (canonicalizeWorktreePath(recordedWorktree) === needle) {
|
|
167
|
+
containedRemove({
|
|
168
|
+
root: resolve(worktreesDir),
|
|
169
|
+
target: join(worktreesDir, name),
|
|
170
|
+
recursive: true,
|
|
171
|
+
mutation: false,
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
30
177
|
function worktreeStillRegistered(git, projectRoot, worktreePath) {
|
|
31
178
|
const listed = git(["worktree", "list", "--porcelain"], projectRoot);
|
|
32
179
|
if (listed.returncode !== 0) {
|
|
33
180
|
return true;
|
|
34
181
|
}
|
|
35
|
-
const needle =
|
|
182
|
+
const needle = canonicalizeWorktreePath(worktreePath);
|
|
36
183
|
for (const line of listed.stdout.split(/\r?\n/u)) {
|
|
37
184
|
if (!line.startsWith("worktree ")) {
|
|
38
185
|
continue;
|
|
39
186
|
}
|
|
40
|
-
const listedPath = line.slice("worktree ".length)
|
|
187
|
+
const listedPath = canonicalizeWorktreePath(line.slice("worktree ".length));
|
|
41
188
|
if (listedPath === needle) {
|
|
42
189
|
return true;
|
|
43
190
|
}
|
|
@@ -53,13 +200,13 @@ export function removeEvaluatorWorktree(projectRoot, worktreePath, git = swarmGi
|
|
|
53
200
|
}
|
|
54
201
|
const firstError = proc.stderr.trim() || "<no stderr>";
|
|
55
202
|
forceDeleteWorktreeDir(worktreePath);
|
|
56
|
-
runner
|
|
203
|
+
pruneEvaluatorWorktreeAdmin(runner, projectRoot, worktreePath);
|
|
57
204
|
const retry = runner(["worktree", "remove", "--force", worktreePath], projectRoot);
|
|
58
205
|
if (retry.returncode === 0) {
|
|
59
206
|
return;
|
|
60
207
|
}
|
|
61
208
|
forceDeleteWorktreeDir(worktreePath);
|
|
62
|
-
runner
|
|
209
|
+
pruneEvaluatorWorktreeAdmin(runner, projectRoot, worktreePath);
|
|
63
210
|
if (!worktreeStillRegistered(runner, projectRoot, worktreePath) && !existsSync(worktreePath)) {
|
|
64
211
|
return;
|
|
65
212
|
}
|
|
@@ -93,6 +93,27 @@ export interface CurrentShapeSidecar {
|
|
|
93
93
|
readonly authorAssociation: string;
|
|
94
94
|
readonly body: string;
|
|
95
95
|
}
|
|
96
|
+
/** A shape-shaped comment dropped by the #2307 authorship filter (#3934). */
|
|
97
|
+
export interface DiscardedShapeCandidate {
|
|
98
|
+
readonly commentId: number;
|
|
99
|
+
/** Normalized `author_association` -- never comment text. */
|
|
100
|
+
readonly authorAssociation: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Why `selectCurrentShapeComment` returned null (#3934). Advisory: it is not
|
|
104
|
+
* consumed by any gate, exit code, or count.
|
|
105
|
+
*/
|
|
106
|
+
export interface CurrentShapeNullReason {
|
|
107
|
+
readonly kind: "no-shape-comment" | "non-maintainer-shape";
|
|
108
|
+
readonly discarded: readonly DiscardedShapeCandidate[];
|
|
109
|
+
readonly message: string;
|
|
110
|
+
}
|
|
111
|
+
/** Sidecar payload, or the reason none was selectable (#3934). */
|
|
112
|
+
export interface CurrentShapeSidecarOutcome {
|
|
113
|
+
readonly sidecar: CurrentShapeSidecar | null;
|
|
114
|
+
/** Non-null exactly when `sidecar` is null. */
|
|
115
|
+
readonly reason: CurrentShapeNullReason | null;
|
|
116
|
+
}
|
|
96
117
|
/** True when labels or sub-issue summary mark the issue as umbrella/tracker-like. */
|
|
97
118
|
export declare function isUmbrellaLikeIssue(raw: Record<string, unknown>): boolean;
|
|
98
119
|
/** Normalize a raw issue payload's comment array (REST or pre-mapped). */
|
|
@@ -104,13 +125,28 @@ export declare function commentsFromRawPayload(raw: Record<string, unknown>): Is
|
|
|
104
125
|
export declare function countMaintainerCurrentShapeComments(comments: readonly IssueComment[]): number;
|
|
105
126
|
/** Markdown section appended to cache content.md when a canonical shape exists. */
|
|
106
127
|
export declare function formatCurrentShapeSection(selected: CurrentShapeComment): string;
|
|
128
|
+
/** Advisory cache note for a selected-null thread that had discarded candidates (#3934). */
|
|
129
|
+
export declare function formatCurrentShapeNotSelectedSection(reason: CurrentShapeNullReason): string;
|
|
107
130
|
/**
|
|
108
131
|
* Append the canonical current-shape comment (if any) to a rendered cache body.
|
|
109
|
-
*
|
|
132
|
+
*
|
|
133
|
+
* When nothing is selectable but the thread carried shape-shaped comments that
|
|
134
|
+
* the #2307 authorship filter dropped, append the advisory not-selected note
|
|
135
|
+
* instead of returning the body unchanged (#3934): an agent reading content.md
|
|
136
|
+
* without invoking `umbrella:current-shape` would otherwise see only the stale
|
|
137
|
+
* body the #1152 rule tells it to distrust. A thread with no shape comment at
|
|
138
|
+
* all is still returned unchanged, so ordinary issues gain no note.
|
|
110
139
|
*/
|
|
111
140
|
export declare function appendCurrentShapeSection(baseContent: string, raw: Record<string, unknown>): string;
|
|
112
|
-
/**
|
|
113
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Build the current-shape.json sidecar payload.
|
|
143
|
+
*
|
|
144
|
+
* Reports why nothing was selectable rather than returning a bare null (#3934),
|
|
145
|
+
* so a caller can tell "no shape comment on this thread" from "a shape comment
|
|
146
|
+
* exists but its author is outside MAINTAINER_ASSOCIATIONS". Advisory only --
|
|
147
|
+
* no sidecar is written on either null kind, exactly as before.
|
|
148
|
+
*/
|
|
149
|
+
export declare function buildCurrentShapeSidecar(raw: Record<string, unknown>): CurrentShapeSidecarOutcome;
|
|
114
150
|
/** Merge `gh api --paginate` concatenated JSON array pages into comment rows. */
|
|
115
151
|
export declare function parseCommentsFromGhStdout(stdout: string): IssueComment[];
|
|
116
152
|
export declare function extractPassFromBody(body: string): number | null;
|
|
@@ -128,6 +164,18 @@ export declare function detectSections(body: string): SectionPresence;
|
|
|
128
164
|
export declare function sectionsRecord(presence: SectionPresence): Record<string, boolean>;
|
|
129
165
|
export declare const NO_CURRENT_SHAPE_MESSAGE: string;
|
|
130
166
|
export declare const NON_MAINTAINER_CURRENT_SHAPE_MESSAGE: string;
|
|
167
|
+
/** Bound the diagnostic so one forged thread cannot flood a cache note (#3934). */
|
|
168
|
+
export declare const MAX_REPORTED_DISCARDED_CANDIDATES = 5;
|
|
169
|
+
/**
|
|
170
|
+
* Classify a null return from `selectCurrentShapeComment` (#3934).
|
|
171
|
+
*
|
|
172
|
+
* Advisory only: it changes no selection, no maintainer count, and no exit code.
|
|
173
|
+
* Discarded candidates are named by comment id and normalized author
|
|
174
|
+
* association; a comment body is never reproduced, because forwarding untrusted
|
|
175
|
+
* text into a cache or xBRIEF narrative is the injection this filter exists to
|
|
176
|
+
* refuse (#2307).
|
|
177
|
+
*/
|
|
178
|
+
export declare function describeCurrentShapeNull(comments: readonly IssueComment[]): CurrentShapeNullReason;
|
|
131
179
|
export declare function fetchCurrentShape(options: {
|
|
132
180
|
repo: string;
|
|
133
181
|
issueNumber: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { scan } from "../cache/scanner.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveBinaryForArgv } from "../scm/call-shape.js";
|
|
4
4
|
import { SUBPROCESS_MAX_BUFFER } from "../subprocess/max-buffer.js";
|
|
5
5
|
import { resolveRepo } from "../triage/queue/repo.js";
|
|
6
6
|
/**
|
|
@@ -192,30 +192,64 @@ export function formatCurrentShapeSection(selected) {
|
|
|
192
192
|
"",
|
|
193
193
|
].join("\n");
|
|
194
194
|
}
|
|
195
|
+
/** Advisory cache note for a selected-null thread that had discarded candidates (#3934). */
|
|
196
|
+
export function formatCurrentShapeNotSelectedSection(reason) {
|
|
197
|
+
return [
|
|
198
|
+
"",
|
|
199
|
+
"---",
|
|
200
|
+
"",
|
|
201
|
+
"## Canonical current shape: not selected (#1152 / #2307)",
|
|
202
|
+
"",
|
|
203
|
+
`_${reason.message} Deterministic read path: \`task umbrella:current-shape <N>\`._`,
|
|
204
|
+
"",
|
|
205
|
+
].join("\n");
|
|
206
|
+
}
|
|
195
207
|
/**
|
|
196
208
|
* Append the canonical current-shape comment (if any) to a rendered cache body.
|
|
197
|
-
*
|
|
209
|
+
*
|
|
210
|
+
* When nothing is selectable but the thread carried shape-shaped comments that
|
|
211
|
+
* the #2307 authorship filter dropped, append the advisory not-selected note
|
|
212
|
+
* instead of returning the body unchanged (#3934): an agent reading content.md
|
|
213
|
+
* without invoking `umbrella:current-shape` would otherwise see only the stale
|
|
214
|
+
* body the #1152 rule tells it to distrust. A thread with no shape comment at
|
|
215
|
+
* all is still returned unchanged, so ordinary issues gain no note.
|
|
198
216
|
*/
|
|
199
217
|
export function appendCurrentShapeSection(baseContent, raw) {
|
|
200
|
-
const
|
|
218
|
+
const comments = commentsFromRawPayload(raw);
|
|
219
|
+
const selected = selectCurrentShapeComment(comments);
|
|
201
220
|
if (selected === null) {
|
|
202
|
-
|
|
221
|
+
const reason = describeCurrentShapeNull(comments);
|
|
222
|
+
if (reason.kind !== "non-maintainer-shape") {
|
|
223
|
+
return baseContent;
|
|
224
|
+
}
|
|
225
|
+
return `${baseContent.trimEnd()}\n${formatCurrentShapeNotSelectedSection(reason)}`;
|
|
203
226
|
}
|
|
204
227
|
return `${baseContent.trimEnd()}\n${formatCurrentShapeSection(selected)}`;
|
|
205
228
|
}
|
|
206
|
-
/**
|
|
229
|
+
/**
|
|
230
|
+
* Build the current-shape.json sidecar payload.
|
|
231
|
+
*
|
|
232
|
+
* Reports why nothing was selectable rather than returning a bare null (#3934),
|
|
233
|
+
* so a caller can tell "no shape comment on this thread" from "a shape comment
|
|
234
|
+
* exists but its author is outside MAINTAINER_ASSOCIATIONS". Advisory only --
|
|
235
|
+
* no sidecar is written on either null kind, exactly as before.
|
|
236
|
+
*/
|
|
207
237
|
export function buildCurrentShapeSidecar(raw) {
|
|
208
|
-
const
|
|
238
|
+
const comments = commentsFromRawPayload(raw);
|
|
239
|
+
const selected = selectCurrentShapeComment(comments);
|
|
209
240
|
if (selected === null) {
|
|
210
|
-
return null;
|
|
241
|
+
return { sidecar: null, reason: describeCurrentShapeNull(comments) };
|
|
211
242
|
}
|
|
212
243
|
return {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
244
|
+
sidecar: {
|
|
245
|
+
commentId: selected.id,
|
|
246
|
+
htmlUrl: selected.htmlUrl,
|
|
247
|
+
pass: selected.pass,
|
|
248
|
+
authorLogin: selected.authorLogin,
|
|
249
|
+
authorAssociation: selected.authorAssociation,
|
|
250
|
+
body: selected.body,
|
|
251
|
+
},
|
|
252
|
+
reason: null,
|
|
219
253
|
};
|
|
220
254
|
}
|
|
221
255
|
/** Merge `gh api --paginate` concatenated JSON array pages into comment rows. */
|
|
@@ -278,9 +312,10 @@ export function parseCommentsFromGhStdout(stdout) {
|
|
|
278
312
|
return comments;
|
|
279
313
|
}
|
|
280
314
|
function defaultFetchComments(repo, issueNumber) {
|
|
281
|
-
const binary = resolveBinary();
|
|
282
315
|
const path = `repos/${repo}/issues/${issueNumber}/comments?per_page=100`;
|
|
283
|
-
const
|
|
316
|
+
const apiArgs = ["--paginate", path];
|
|
317
|
+
const binary = resolveBinaryForArgv("api", apiArgs);
|
|
318
|
+
const proc = spawnSync(binary, ["api", ...apiArgs], {
|
|
284
319
|
encoding: "utf8",
|
|
285
320
|
maxBuffer: SUBPROCESS_MAX_BUFFER,
|
|
286
321
|
});
|
|
@@ -385,6 +420,58 @@ export const NON_MAINTAINER_CURRENT_SHAPE_MESSAGE = "A ## Current shape (as of p
|
|
|
385
420
|
"non-maintainer (author_association not in OWNER/MEMBER/COLLABORATOR) and is " +
|
|
386
421
|
"ignored per AGENTS.md ## Umbrella current-shape convention (#1152 / #2307). " +
|
|
387
422
|
"A maintainer must (re-)post the current-shape comment for it to be authoritative.";
|
|
423
|
+
/** Bound the diagnostic so one forged thread cannot flood a cache note (#3934). */
|
|
424
|
+
export const MAX_REPORTED_DISCARDED_CANDIDATES = 5;
|
|
425
|
+
const SAFE_ASSOCIATION_RE = /^[A-Z_]{1,32}$/;
|
|
426
|
+
/**
|
|
427
|
+
* GitHub sets `author_association` from a fixed enum, but a replayed or
|
|
428
|
+
* hand-built payload can carry anything. Normalizing keeps the diagnostic from
|
|
429
|
+
* smuggling arbitrary payload text into an agent-facing surface (#3934).
|
|
430
|
+
*/
|
|
431
|
+
function normalizeAssociation(raw) {
|
|
432
|
+
const upper = raw.trim().toUpperCase();
|
|
433
|
+
return SAFE_ASSOCIATION_RE.test(upper) ? upper : "UNKNOWN";
|
|
434
|
+
}
|
|
435
|
+
function formatDiscardedCandidates(discarded) {
|
|
436
|
+
const shown = discarded
|
|
437
|
+
.slice(0, MAX_REPORTED_DISCARDED_CANDIDATES)
|
|
438
|
+
.map((candidate) => `comment ${candidate.commentId} (${candidate.authorAssociation})`);
|
|
439
|
+
const hidden = discarded.length - shown.length;
|
|
440
|
+
return hidden > 0 ? `${shown.join(", ")}, and ${hidden} more` : shown.join(", ");
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Classify a null return from `selectCurrentShapeComment` (#3934).
|
|
444
|
+
*
|
|
445
|
+
* Advisory only: it changes no selection, no maintainer count, and no exit code.
|
|
446
|
+
* Discarded candidates are named by comment id and normalized author
|
|
447
|
+
* association; a comment body is never reproduced, because forwarding untrusted
|
|
448
|
+
* text into a cache or xBRIEF narrative is the injection this filter exists to
|
|
449
|
+
* refuse (#2307).
|
|
450
|
+
*/
|
|
451
|
+
export function describeCurrentShapeNull(comments) {
|
|
452
|
+
const discarded = [];
|
|
453
|
+
for (const comment of comments) {
|
|
454
|
+
if (isMaintainerAuthored(comment.authorAssociation)) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (extractPassFromBody(comment.body) === null) {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
discarded.push({
|
|
461
|
+
commentId: comment.id,
|
|
462
|
+
authorAssociation: normalizeAssociation(comment.authorAssociation),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
if (discarded.length === 0) {
|
|
466
|
+
return { kind: "no-shape-comment", discarded: [], message: NO_CURRENT_SHAPE_MESSAGE };
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
kind: "non-maintainer-shape",
|
|
470
|
+
discarded,
|
|
471
|
+
message: `${NON_MAINTAINER_CURRENT_SHAPE_MESSAGE} ` +
|
|
472
|
+
`Discarded candidate(s): ${formatDiscardedCandidates(discarded)}.`,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
388
475
|
export function fetchCurrentShape(options) {
|
|
389
476
|
const fetcher = options.fetchComments ?? defaultFetchComments;
|
|
390
477
|
const fetched = fetcher(options.repo, options.issueNumber);
|
|
@@ -393,11 +480,12 @@ export function fetchCurrentShape(options) {
|
|
|
393
480
|
}
|
|
394
481
|
const selected = selectCurrentShapeComment(fetched);
|
|
395
482
|
if (selected === null) {
|
|
396
|
-
// Distinguish provenance-filtered absence from genuine absence (#2307)
|
|
397
|
-
|
|
483
|
+
// Distinguish provenance-filtered absence from genuine absence (#2307), via
|
|
484
|
+
// the same classifier the cache-side callers use (#3934). The CLI keeps its
|
|
485
|
+
// two existing messages verbatim -- discarded ids stay off this surface.
|
|
398
486
|
return {
|
|
399
487
|
ok: false,
|
|
400
|
-
error:
|
|
488
|
+
error: describeCurrentShapeNull(fetched).kind === "non-maintainer-shape"
|
|
401
489
|
? NON_MAINTAINER_CURRENT_SHAPE_MESSAGE
|
|
402
490
|
: NO_CURRENT_SHAPE_MESSAGE,
|
|
403
491
|
kind: "not-found",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C1 declared deposit closure (#3601 / #3899).
|
|
3
|
+
*
|
|
4
|
+
* Required consumer-deposit paths are a closed typed declaration, not a
|
|
5
|
+
* regex over AGENTS.md RFC2119 prose. Existence checks reuse existsSync
|
|
6
|
+
* the same way validate-links.ts does, against a staged pack root (the
|
|
7
|
+
* prepack flatten), not the source checkout.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEPOSIT_REQUIRED_SCHEMA: "deft.deposit-required-paths.v1";
|
|
10
|
+
export declare const DEPOSIT_REQUIRED_REL = "contracts/deposit-required-paths.json";
|
|
11
|
+
export declare const DEPOSIT_PREFIX = ".deft/core/";
|
|
12
|
+
export interface DepositRequiredDeclaration {
|
|
13
|
+
readonly schema: typeof DEPOSIT_REQUIRED_SCHEMA;
|
|
14
|
+
readonly paths: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export interface DepositClosureResult {
|
|
17
|
+
readonly ok: boolean;
|
|
18
|
+
readonly missing: readonly string[];
|
|
19
|
+
readonly checked: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function extractDepositRequiredComments(source: string): string[];
|
|
22
|
+
export declare function parseDepositRequiredDeclaration(jsonText: string): DepositRequiredDeclaration;
|
|
23
|
+
export declare function packRelativeFromDepositPath(declared: string): string;
|
|
24
|
+
/** Prepack mapping used by @deftai/directive-content. */
|
|
25
|
+
export declare function sourcePathForPackRelative(repoRoot: string, packRelative: string): string;
|
|
26
|
+
export declare function evaluateDepositClosure(options: {
|
|
27
|
+
readonly packRoot: string;
|
|
28
|
+
readonly paths: readonly string[];
|
|
29
|
+
}): DepositClosureResult;
|
|
30
|
+
export declare function loadDepositRequiredDeclaration(filePath: string): DepositRequiredDeclaration;
|
|
31
|
+
export declare function resolveDeclarationFile(root: string): string | null;
|
|
32
|
+
export declare function evaluateInstalledDepositClosure(projectRoot: string): {
|
|
33
|
+
readonly skipped: boolean;
|
|
34
|
+
readonly missing: readonly string[];
|
|
35
|
+
readonly declarationPath: string | null;
|
|
36
|
+
readonly error: string | null;
|
|
37
|
+
};
|
|
38
|
+
export declare function renderDeclaredDepositClosureLine(result: ReturnType<typeof evaluateInstalledDepositClosure>): string;
|
|
39
|
+
//# sourceMappingURL=deposit-required.d.ts.map
|