@deftai/directive-core 0.95.0 → 0.97.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.
Files changed (116) hide show
  1. package/dist/cache/archive.d.ts +134 -0
  2. package/dist/cache/archive.js +636 -0
  3. package/dist/cache/index.d.ts +1 -0
  4. package/dist/cache/index.js +1 -0
  5. package/dist/cache/main.js +298 -1
  6. package/dist/check/gate-lists.js +8 -0
  7. package/dist/consumer-check-contract/evaluate.d.ts +124 -0
  8. package/dist/consumer-check-contract/evaluate.js +699 -0
  9. package/dist/consumer-check-contract/index.d.ts +5 -0
  10. package/dist/consumer-check-contract/index.js +5 -0
  11. package/dist/content-contracts/skills/helpers.d.ts +1 -1
  12. package/dist/content-contracts/skills/helpers.js +1 -0
  13. package/dist/delivery-attempt/disk-begin.d.ts +51 -0
  14. package/dist/delivery-attempt/disk-begin.js +68 -0
  15. package/dist/delivery-attempt/evaluate.d.ts +26 -0
  16. package/dist/delivery-attempt/evaluate.js +443 -0
  17. package/dist/delivery-attempt/fingerprint.d.ts +32 -0
  18. package/dist/delivery-attempt/fingerprint.js +100 -0
  19. package/dist/delivery-attempt/handoff.d.ts +25 -0
  20. package/dist/delivery-attempt/handoff.js +102 -0
  21. package/dist/delivery-attempt/index.d.ts +17 -0
  22. package/dist/delivery-attempt/index.js +17 -0
  23. package/dist/delivery-attempt/ledger.d.ts +169 -0
  24. package/dist/delivery-attempt/ledger.js +758 -0
  25. package/dist/delivery-attempt/material-delta.d.ts +38 -0
  26. package/dist/delivery-attempt/material-delta.js +126 -0
  27. package/dist/delivery-attempt/types.d.ts +210 -0
  28. package/dist/delivery-attempt/types.js +77 -0
  29. package/dist/doctor/index.d.ts +1 -0
  30. package/dist/doctor/index.js +1 -0
  31. package/dist/doctor/main.js +12 -0
  32. package/dist/doctor/openclaw-soft-rebind.d.ts +26 -0
  33. package/dist/doctor/openclaw-soft-rebind.js +164 -0
  34. package/dist/hooks/dispatcher.d.ts +2 -1
  35. package/dist/hooks/dispatcher.js +63 -9
  36. package/dist/index.d.ts +5 -0
  37. package/dist/index.js +5 -0
  38. package/dist/init-deposit/gitignore.js +7 -0
  39. package/dist/init-deposit/hygiene.d.ts +1 -1
  40. package/dist/init-deposit/hygiene.js +16 -4
  41. package/dist/init-deposit/init-deposit.js +5 -0
  42. package/dist/init-deposit/refresh.js +3 -0
  43. package/dist/init-deposit/scaffold.js +6 -5
  44. package/dist/parent-turn-shape/evaluate.d.ts +84 -0
  45. package/dist/parent-turn-shape/evaluate.js +353 -0
  46. package/dist/parent-turn-shape/index.d.ts +8 -0
  47. package/dist/parent-turn-shape/index.js +8 -0
  48. package/dist/pr-merge-readiness/ci-gate.d.ts +29 -1
  49. package/dist/pr-merge-readiness/ci-gate.js +191 -24
  50. package/dist/pr-merge-readiness/compute.js +10 -1
  51. package/dist/pr-merge-readiness/index.d.ts +2 -1
  52. package/dist/pr-merge-readiness/index.js +2 -1
  53. package/dist/pr-merge-readiness/output.js +14 -0
  54. package/dist/pr-merge-readiness/platform-status.d.ts +29 -0
  55. package/dist/pr-merge-readiness/platform-status.js +49 -0
  56. package/dist/pr-watch/constants.d.ts +10 -0
  57. package/dist/pr-watch/constants.js +12 -1
  58. package/dist/pr-watch/main.js +16 -1
  59. package/dist/pr-watch/probe.js +13 -1
  60. package/dist/pr-watch/types.d.ts +3 -2
  61. package/dist/pr-watch/watch.js +14 -7
  62. package/dist/review-monitor/constants.js +3 -2
  63. package/dist/review-monitor/tier-detection.d.ts +7 -3
  64. package/dist/review-monitor/tier-detection.js +18 -1
  65. package/dist/review-monitor/verify.js +18 -0
  66. package/dist/scope/index.d.ts +2 -0
  67. package/dist/scope/index.js +2 -0
  68. package/dist/scope/main.d.ts +10 -0
  69. package/dist/scope/main.js +109 -24
  70. package/dist/scope/promote-from-issue.d.ts +49 -0
  71. package/dist/scope/promote-from-issue.js +367 -0
  72. package/dist/scope/promote-path.d.ts +39 -0
  73. package/dist/scope/promote-path.js +105 -0
  74. package/dist/scope-provenance/digest.d.ts +67 -0
  75. package/dist/scope-provenance/digest.js +188 -0
  76. package/dist/scope-provenance/evaluate.d.ts +82 -0
  77. package/dist/scope-provenance/evaluate.js +528 -0
  78. package/dist/scope-provenance/index.d.ts +6 -0
  79. package/dist/scope-provenance/index.js +6 -0
  80. package/dist/session/compact-ritual.d.ts +96 -0
  81. package/dist/session/compact-ritual.js +237 -0
  82. package/dist/session/compact-ritual.spec.d.ts +2 -0
  83. package/dist/session/compact-ritual.spec.js +21 -0
  84. package/dist/session/index.d.ts +2 -0
  85. package/dist/session/index.js +2 -0
  86. package/dist/session/openclaw-soft-rebind-deposit.d.ts +46 -0
  87. package/dist/session/openclaw-soft-rebind-deposit.js +165 -0
  88. package/dist/swarm/routing.d.ts +4 -2
  89. package/dist/swarm/routing.js +26 -4
  90. package/dist/test-boundary/evaluate.d.ts +54 -0
  91. package/dist/test-boundary/evaluate.js +368 -0
  92. package/dist/test-boundary/index.d.ts +6 -0
  93. package/dist/test-boundary/index.js +6 -0
  94. package/dist/test-boundary/policy.d.ts +52 -0
  95. package/dist/test-boundary/policy.js +182 -0
  96. package/dist/triage/actions/index.js +62 -2
  97. package/dist/triage/actions/types.d.ts +8 -1
  98. package/dist/triage/author-filter.d.ts +51 -0
  99. package/dist/triage/author-filter.js +152 -0
  100. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  101. package/dist/triage/bootstrap/gitignore.js +15 -1
  102. package/dist/triage/classify/index.d.ts +2 -2
  103. package/dist/triage/classify/index.js +2 -2
  104. package/dist/triage/classify/label-mirror.d.ts +68 -5
  105. package/dist/triage/classify/label-mirror.js +261 -31
  106. package/dist/triage/help/registry-data.d.ts +49 -38
  107. package/dist/triage/help/registry-data.js +115 -40
  108. package/dist/triage/index.d.ts +1 -0
  109. package/dist/triage/index.js +1 -0
  110. package/dist/triage/queue/index.d.ts +1 -0
  111. package/dist/triage/queue/index.js +1 -0
  112. package/dist/triage/queue/render.d.ts +2 -0
  113. package/dist/triage/queue/render.js +6 -0
  114. package/dist/vbrief-activate/activate.js +22 -6
  115. package/dist/xbrief/styles.js +33 -17
  116. package/package.json +11 -3
@@ -170,9 +170,9 @@ export const registryData = {
170
170
  "task triage:queue": {
171
171
  name: "task triage:queue",
172
172
  summary: "Ranked candidate list",
173
- refs: "(D11 / #1128)",
174
- description: "Print the ranked triage queue from the local cache. Groups (display order): [ORPHAN] -> [RESUME] -> [URGENT] -> untriaged -> other -> [BLOCKED]. Within-group default = updated_at desc; consumer plan.policy.triageRankingLabels[] re-orders within-group by matched-label declared order. Items whose linked vBRIEF is blocked (status:blocked / unresolved depends_on) are demoted into [BLOCKED] unless --include-blocked is passed (#1286).",
175
- usage: "task triage:queue [-- --limit=N] [--include-blocked] [--repo=owner/name]",
173
+ refs: "(D11 / #1128, #3129 / #1318 Layer 1)",
174
+ description: "Print the ranked triage queue from the local cache. Groups (display order): [ORPHAN] -> [RESUME] -> [URGENT] -> untriaged -> other -> [BLOCKED]. Within-group default = updated_at desc; consumer plan.policy.triageRankingLabels[] re-orders within-group by matched-label declared order. Items whose linked vBRIEF is blocked (status:blocked / unresolved depends_on) are demoted into [BLOCKED] unless --include-blocked is passed (#1286). Optional --author LOGIN filters to cache author.login (exact; @me resolves to authenticated login; comma allow-list; missing author counted as unknown and disclosed in the header) (#3129 / #1318 Layer 1).",
175
+ usage: "task triage:queue [-- --limit=N] [--include-blocked] [--repo=owner/name] [--author LOGIN|@me] [--author-mine]",
176
176
  flags: [
177
177
  ["--limit N", "10", "Max rows to print."],
178
178
  [
@@ -181,13 +181,26 @@ export const registryData = {
181
181
  "Re-surface blocked items into their natural group (#1286).",
182
182
  ],
183
183
  ["--repo owner/name", "(git remote)", "Explicit repo override."],
184
+ [
185
+ "--author LOGIN|@me",
186
+ "(none)",
187
+ "Only issues whose cache author.login matches (exact; @me = gh user; comma allow-list) (#3129).",
188
+ ],
189
+ ["--author-mine", "(off)", "Alias for --author @me (#1318 / #3129)."],
184
190
  ],
185
191
  examples: [
186
192
  "task triage:queue",
187
- "task triage:queue -- --limit=20 --state=accept",
188
- "task triage:queue -- --format=json | jq '.[] | select(.score > 5)'",
193
+ "task triage:queue -- --limit=20",
194
+ "task triage:queue -- --author @me",
195
+ "task triage:queue -- --author alice,bob --limit=20",
196
+ ],
197
+ see_also: [
198
+ "task triage:show",
199
+ "task triage:audit",
200
+ "task triage:classify",
201
+ "#1119 / D11",
202
+ "#3129",
189
203
  ],
190
- see_also: ["task triage:show", "task triage:audit", "#1119 / D11"],
191
204
  placeholder: false,
192
205
  },
193
206
  "task triage:audit": {
@@ -264,28 +277,49 @@ export const registryData = {
264
277
  },
265
278
  "task triage:classify": {
266
279
  name: "task triage:classify",
267
- summary: "Inspect / validate auto-classification surface; Tier-1 SCM label mirror",
268
- refs: "(D10 / #1129, #1423 Wave 1)",
269
- description: "Inspect or validate the auto-classification rule set. --list renders effective rules (framework universal first, consumer overrides next). --validate exits non-zero on a malformed plan.policy.triageAutoClassify or triageLabelMirror. --mirror runs the Tier-1 deterministic SCM label mirror over the github-issue cache (dry-run default; --apply writes via SCM boundary). Never calls triage:accept / never writes proposed/ xBRIEFs.",
270
- usage: "task triage:classify -- [--list | --validate | --mirror [--apply] [--repo owner/name] [--json] [--allow-cross-repo]]",
280
+ summary: "Inspect / validate auto-classification; bootstrap mass-triage label mirror",
281
+ refs: "(D10 / #1129, #1423 Wave 1–2 / #3125, #3129)",
282
+ description: "Inspect or validate the auto-classification rule set. --list renders effective rules (framework universal first, consumer overrides next). --validate exits non-zero on a malformed plan.policy.triageAutoClassify or triageLabelMirror. --mirror is the bootstrap mass-triage entrypoint (#3125): classifies the github-issue cache and mirrors outcomes as SCM labels. Open-only by default (opt-in --include-closed); optional --author LOGIN scopes plan/apply to matching author.login (AND with open-only; #3129); dry-run digest shows totals + by state/rule/action + samples and surfaces the author filter; --apply batches writes with rate-limit delay. Never calls triage:accept / never writes proposed/ xBRIEFs.",
283
+ usage: "task triage:classify -- [--list | --validate | --mirror [--apply] [--include-closed] [--author LOGIN|@me] [--repo owner/name] [--batch-size N] [--delay-ms N] [--sample-limit N] [--json] [--allow-cross-repo]]",
271
284
  flags: [
272
285
  ["--list", "(default)", "Print effective rules + hold markers."],
273
286
  ["--validate", "(off)", "Validate plan.policy.triageAutoClassify + triageLabelMirror."],
274
287
  [
275
288
  "--mirror",
276
289
  "(off)",
277
- "Tier-1 label mirror: classify cache → planned/applied labels (dry-run default).",
290
+ "Bootstrap mass-triage label mirror: classify cache → planned/applied labels (dry-run default, open-only).",
291
+ ],
292
+ [
293
+ "--apply",
294
+ "(off)",
295
+ "With --mirror: write labels via SCM (batched; requires github SCM boundary).",
296
+ ],
297
+ [
298
+ "--include-closed",
299
+ "(off)",
300
+ "With --mirror: include closed issues (default open-only avoids archive mass-stamp).",
301
+ ],
302
+ [
303
+ "--author LOGIN|@me",
304
+ "(none)",
305
+ "With --mirror: only plan/apply issues whose cache author.login matches (exact; @me; comma allow-list) (#3129).",
278
306
  ],
279
- ["--apply", "(off)", "With --mirror: write labels via SCM (requires github SCM boundary)."],
307
+ ["--author-mine", "(off)", "With --mirror: alias for --author @me (#3129)."],
280
308
  ["--repo owner/name", "(all cached)", "Limit mirror to one repo."],
281
- ["--json", "(off)", "With --mirror: structured JSON outcome."],
309
+ ["--batch-size N", "10", "With --mirror --apply: writes per batch before delay."],
310
+ ["--delay-ms N", "1000", "With --mirror --apply: ms sleep between batches (rate-limit)."],
311
+ ["--sample-limit N", "15", "With --mirror: max samples in human digest."],
312
+ ["--json", "(off)", "With --mirror: structured JSON outcome including digest aggregates."],
282
313
  ["--allow-cross-repo", "(off)", "With --mirror --apply: allow non-project repos (#2601)."],
283
314
  ],
284
315
  examples: [
285
316
  "task triage:classify -- --list",
286
317
  "task triage:classify -- --validate",
287
318
  "task triage:classify -- --mirror",
288
- "task triage:classify -- --mirror --apply --repo owner/name",
319
+ "task triage:classify -- --mirror --author @me --repo owner/name",
320
+ "task triage:classify -- --mirror --repo owner/name --json",
321
+ "task triage:classify -- --mirror --apply --repo owner/name --batch-size 10 --delay-ms 1000",
322
+ "task triage:classify -- --mirror --include-closed --repo owner/name",
289
323
  ],
290
324
  see_also: [
291
325
  "task triage:bootstrap",
@@ -293,6 +327,8 @@ export const registryData = {
293
327
  "task vbrief:reconcile:labels",
294
328
  "#1119 / D10",
295
329
  "#1423",
330
+ "#3125",
331
+ "#3129",
296
332
  ],
297
333
  placeholder: false,
298
334
  },
@@ -493,40 +529,79 @@ export const registryData = {
493
529
  see_also: ["task triage:subscribe", "task triage:scope", "#1119 / D14"],
494
530
  placeholder: false,
495
531
  },
496
- "task triage:audit:prune": {
497
- name: "task triage:audit:prune",
498
- summary: "Operator-invoked archive of closed-terminal entries",
499
- refs: "(D19, coming)",
500
- description: "Move closed-terminal audit entries (accept/reject/mark-duplicate after the upstream issue is closed) into the archive sidecar so the live cache stays focused on open work.",
501
- usage: "task triage:audit:prune [-- --dry-run] [--older-than-days N]",
532
+ "task triage:cache-archive": {
533
+ name: "task triage:cache-archive",
534
+ summary: "Reversible archive of closed github-issue cache entries",
535
+ refs: "(#1137)",
536
+ description: "Move closed-and-aged live `.deft-cache/github-issue/` entries under `.deft-cache/archived/github-issue/...` with archive-meta.json. Operator-only; never auto on check/session/sync. Distinct from task cache:prune (TTL hard-delete). Skips issues referenced in xbrief/{proposed,pending,active}. Alias: cache:archive-closed.",
537
+ usage: "task triage:cache-archive -- [--dry-run] [--older-than-days 30] [--repo owner/name] [--json] [--terminal-decision-only]",
502
538
  flags: [
503
- ["--dry-run", "(off)", "Preview eligible entries without writing."],
504
- ["--older-than-days N", "30", "Age threshold."],
539
+ ["--dry-run", "(off)", "Report eligible entries without moving."],
540
+ ["--older-than-days N", "30", "Minimum age of closed_at (or fetched_at/mtime fallback)."],
541
+ ["--repo owner/name", "(all)", "Limit to one repo."],
542
+ ["--json", "(off)", "Structured summary."],
543
+ [
544
+ "--terminal-decision-only",
545
+ "(off)",
546
+ "Only archive when latest candidates decision is reject or mark-duplicate.",
547
+ ],
505
548
  ],
506
- examples: ["task triage:audit:prune -- --dry-run"],
507
- see_also: ["task triage:archive-list", "task triage:restore-from-archive", "#1119 / D19"],
508
- placeholder: true,
549
+ examples: [
550
+ "task triage:cache-archive -- --dry-run",
551
+ "task triage:cache-archive -- --older-than-days 30 --repo deftai/directive",
552
+ ],
553
+ see_also: [
554
+ "task triage:archive-list",
555
+ "task triage:restore-from-archive",
556
+ "task cache:prune",
557
+ "#1137",
558
+ ],
559
+ placeholder: false,
509
560
  },
510
561
  "task triage:archive-list": {
511
562
  name: "task triage:archive-list",
512
- summary: "List archived entries",
513
- refs: "(D19, coming)",
514
- description: "Read-only listing of archived audit entries from the archive sidecar. Useful before restoring an entry.",
515
- usage: "task triage:archive-list [-- --since=WINDOW]",
516
- flags: [["--since WINDOW", "(all)", "Time window (e.g. '30d', '6mo')."]],
517
- examples: ["task triage:archive-list"],
518
- see_also: ["task triage:audit:prune", "#1119 / D19"],
519
- placeholder: true,
563
+ summary: "List archived github-issue cache entries",
564
+ refs: "(#1137)",
565
+ description: "List `.deft-cache/archived/github-issue/...` entries newest archived_at first. Alias: cache:archive-list.",
566
+ usage: "task triage:archive-list -- [--repo owner/name] [--format=json] [--since ISO] [--limit N]",
567
+ flags: [
568
+ ["--repo owner/name", "(all)", "Limit to one repo."],
569
+ ["--format=json", "text", "JSON listing."],
570
+ ["--since ISO", "(none)", "Only entries archived at/after this timestamp."],
571
+ ["--limit N", "(none)", "Cap result count."],
572
+ ],
573
+ examples: ["task triage:archive-list -- --format=json"],
574
+ see_also: ["task triage:cache-archive", "task triage:restore-from-archive", "#1137"],
575
+ placeholder: false,
520
576
  },
521
577
  "task triage:restore-from-archive": {
522
578
  name: "task triage:restore-from-archive",
523
- summary: "Restore archived entry to live cache",
524
- refs: "(D19, coming)",
525
- description: "Move an archived entry back to the live audit log so it becomes visible to triage:queue / triage:audit again.",
526
- usage: "task triage:restore-from-archive -- <N>",
527
- flags: [["<N>", "(required)", "Archived entry number to restore."]],
528
- examples: ["task triage:restore-from-archive -- 42"],
529
- see_also: ["task triage:archive-list", "#1119 / D19"],
579
+ summary: "Restore archived issue entry to live cache",
580
+ refs: "(#1137)",
581
+ description: "Move an archived github-issue entry back to the live key path. Idempotent if already live with matching content; refuse live conflict unless --force. Alias: cache:restore-from-archive.",
582
+ usage: "task triage:restore-from-archive -- --issue N --repo owner/name [--force]",
583
+ flags: [
584
+ ["--issue N", "(required unless --key)", "Issue number."],
585
+ ["--repo owner/name", "(required with --issue)", "Upstream repo."],
586
+ ["--key owner/repo/N", "(alt)", "Full cache key."],
587
+ ["--force", "(off)", "Replace live path when content differs."],
588
+ ],
589
+ examples: ["task triage:restore-from-archive -- --issue 1234 --repo deftai/directive"],
590
+ see_also: ["task triage:cache-archive", "task triage:show", "#1137"],
591
+ placeholder: false,
592
+ },
593
+ "task triage:audit:prune": {
594
+ name: "task triage:audit:prune",
595
+ summary: "Legacy name — use triage:cache-archive",
596
+ refs: "(#1137 supersedes D19 name)",
597
+ description: "Historical D19 placeholder name. Implemented surface is task triage:cache-archive (reversible closed github-issue cache archive). Do not confuse with task cache:prune (TTL hard-delete).",
598
+ usage: "task triage:cache-archive -- [--dry-run] [--older-than-days N]",
599
+ flags: [
600
+ ["--dry-run", "(off)", "Preview eligible entries without writing."],
601
+ ["--older-than-days N", "30", "Age threshold."],
602
+ ],
603
+ examples: ["task triage:cache-archive -- --dry-run"],
604
+ see_also: ["task triage:cache-archive", "task triage:archive-list", "#1137"],
530
605
  placeholder: true,
531
606
  },
532
607
  "task triage:audit-log:rotate": {
@@ -7,6 +7,7 @@
7
7
  * ...) without colliding.
8
8
  */
9
9
  export * as actions from "./actions/index.js";
10
+ export * as authorFilter from "./author-filter.js";
10
11
  export * as bootstrap from "./bootstrap/index.js";
11
12
  export * as bulk from "./bulk/index.js";
12
13
  export * as cachePath from "./cache-path.js";
@@ -7,6 +7,7 @@
7
7
  * ...) without colliding.
8
8
  */
9
9
  export * as actions from "./actions/index.js";
10
+ export * as authorFilter from "./author-filter.js";
10
11
  export * as bootstrap from "./bootstrap/index.js";
11
12
  export * as bulk from "./bulk/index.js";
12
13
  export * as cachePath from "./cache-path.js";
@@ -1,3 +1,4 @@
1
+ export { type AuthorFilter, type AuthorFilterResolveResult, type AuthorPartitionResult, authorLoginFromRawIssue, defaultResolveAuthenticatedLogin, formatAuthorFilterLine, matchesAuthorFilter, normalizeAuthorLogin, parseAuthorTokens, partitionByAuthorFilter, type ResolveAuthenticatedLogin, resolveAuthorFilter, } from "../author-filter.js";
1
2
  export * from "./audit.js";
2
3
  export * from "./build-queue.js";
3
4
  export * from "./cache.js";
@@ -1,3 +1,4 @@
1
+ export { authorLoginFromRawIssue, defaultResolveAuthenticatedLogin, formatAuthorFilterLine, matchesAuthorFilter, normalizeAuthorLogin, parseAuthorTokens, partitionByAuthorFilter, resolveAuthorFilter, } from "../author-filter.js";
1
2
  export * from "./audit.js";
2
3
  export * from "./build-queue.js";
3
4
  export * from "./cache.js";
@@ -5,5 +5,7 @@ export declare function renderQueue(options: {
5
5
  readonly repo: string;
6
6
  readonly limit?: number | null;
7
7
  readonly rankingLabels?: readonly string[];
8
+ /** Active author filter disclosure line (#3129 / #1318 Layer 1). */
9
+ readonly authorFilterLine?: string | null;
8
10
  }): string;
9
11
  //# sourceMappingURL=render.d.ts.map
@@ -16,6 +16,12 @@ export function renderQueue(options) {
16
16
  else {
17
17
  lines.push(" consumer ranking labels: <empty> (framework default; within-group = updated_at desc)");
18
18
  }
19
+ if (options.authorFilterLine !== null && options.authorFilterLine !== undefined) {
20
+ const line = options.authorFilterLine.trim();
21
+ if (line.length > 0) {
22
+ lines.push(` ${line}`);
23
+ }
24
+ }
19
25
  if (options.limit !== null && options.limit !== undefined) {
20
26
  lines.push(` limit: ${options.limit}`);
21
27
  }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, } from "node:fs";
2
2
  import { basename, dirname, resolve } from "node:path";
3
3
  import { containedWrite } from "../fs/contained-write.js";
4
+ import { assertProjectionContained, ProjectionContainmentError, } from "../fs/projection-containment.js";
4
5
  import { utcNowIso } from "../scope/vbrief-json.js";
5
6
  import { pythonJsonPretty } from "../vbrief-build/json.js";
6
7
  import { ACTIVE_FOLDER, ELIGIBLE_STATUSES_FOR_FLIP, formatEligibleStatusList, SOURCE_FOLDERS, TARGET_STATUS, } from "./constants.js";
@@ -120,6 +121,22 @@ export function activate(vbriefPath, options = {}) {
120
121
  `'${TARGET_STATUS}'.`,
121
122
  };
122
123
  }
124
+ // Resolve destination early so containment can refuse before any mutation.
125
+ // Parity with scope:activate / #2447: projectRoot is parent of the xbrief/ root.
126
+ const vbriefDir = dirname(dirname(vbriefPath));
127
+ const projectRoot = dirname(vbriefDir);
128
+ const activeDir = `${vbriefDir}/${ACTIVE_FOLDER}`;
129
+ try {
130
+ // #3147 / #2447: refuse when active/ (or a parent) is a symlink escaping the checkout.
131
+ // Run before mutating the source payload or unlinking pending so a refusal leaves state intact.
132
+ assertProjectionContained(projectRoot, activeDir);
133
+ }
134
+ catch (err) {
135
+ if (err instanceof ProjectionContainmentError) {
136
+ return { exitCode: 1, message: err.message };
137
+ }
138
+ throw err;
139
+ }
123
140
  planObj.status = TARGET_STATUS;
124
141
  let info = payload.vBRIEFInfo;
125
142
  if (info === undefined) {
@@ -133,8 +150,6 @@ export function activate(vbriefPath, options = {}) {
133
150
  };
134
151
  }
135
152
  info.updated = utcNowIso(now);
136
- const vbriefDir = dirname(dirname(vbriefPath));
137
- const activeDir = `${vbriefDir}/${ACTIVE_FOLDER}`;
138
153
  try {
139
154
  mkdirSync(activeDir, { recursive: true });
140
155
  }
@@ -151,13 +166,14 @@ export function activate(vbriefPath, options = {}) {
151
166
  "collision manually before re-running `task vbrief:activate`.",
152
167
  };
153
168
  }
154
- const tmpBase = `${fileName}.tmp`;
155
169
  const tmp = `${dest}.tmp`;
156
170
  try {
157
- // #2980 wave D: product write sink routes through containedWrite.
171
+ // #2980 wave D + #3147: product write sink routes through containedWrite with
172
+ // checkout root as containment root (not resolve(activeDir), which treats an
173
+ // escaping active/ realpath as "contained" relative to itself).
158
174
  containedWrite({
159
- root: resolve(activeDir),
160
- target: tmpBase,
175
+ root: resolve(projectRoot),
176
+ target: resolve(tmp),
161
177
  data: pythonJsonPretty(payload),
162
178
  mode: "create",
163
179
  });
@@ -4,6 +4,19 @@
4
4
  * One schema spine (xBRIEF v0.8); markdown is a dense render of the same plan,
5
5
  * not a second dialect.
6
6
  */
7
+ import { parseMarkdownHeading } from "../text/redos-safe.js";
8
+ /**
9
+ * Linear frontmatter ``key: value`` parse (no nested quantifiers).
10
+ * Replaces ``/^key:\s*(.+)\s*$/`` flagged as CodeQL js/polynomial-redos (#3174).
11
+ */
12
+ function frontmatterField(line, key) {
13
+ const prefix = `${key}:`;
14
+ if (!line.startsWith(prefix)) {
15
+ return null;
16
+ }
17
+ const value = line.slice(prefix.length).trim();
18
+ return value.length > 0 ? value : null;
19
+ }
7
20
  /** Required H2 section titles in the markdown form per style. */
8
21
  export const MD_REQUIRED_SECTIONS = {
9
22
  scope: ["Title", "Status", "Overview", "Items"],
@@ -200,25 +213,28 @@ export function parseMarkdownMeta(md) {
200
213
  continue;
201
214
  }
202
215
  if (inFront) {
203
- const idMatch = /^id:\s*(.+)\s*$/.exec(line);
204
- if (idMatch?.[1] !== undefined)
205
- id = idMatch[1].trim();
206
- const styleMatch = /^style:\s*(.+)\s*$/.exec(line);
207
- if (styleMatch?.[1] !== undefined)
208
- style = styleMatch[1].trim();
216
+ // Linear field parse — avoids js/polynomial-redos on \s*(.+)\s* (#3174 alerts #84-#85).
217
+ const idValue = frontmatterField(line, "id");
218
+ if (idValue !== null)
219
+ id = idValue;
220
+ const styleValue = frontmatterField(line, "style");
221
+ if (styleValue !== null)
222
+ style = styleValue;
209
223
  continue;
210
224
  }
211
- const h1 = /^#\s+(.+)$/.exec(line);
212
- if (h1?.[1] !== undefined && title === null) {
213
- title = h1[1].trim();
214
- }
215
- const h2 = /^##\s+(.+)$/.exec(line);
216
- if (h2?.[1] !== undefined) {
217
- const name = h2[1].trim();
218
- sections.add(name);
219
- afterTitleH2 = name === "Title";
220
- afterStatusH2 = name === "Status";
221
- continue;
225
+ // Linear ATX heading parse — avoids js/polynomial-redos on ^#+\s+(.+)$ (#3174 alerts #86-#87).
226
+ const heading = parseMarkdownHeading(line);
227
+ if (heading !== null) {
228
+ const name = heading.text.trim();
229
+ if (heading.hashes === "#" && title === null) {
230
+ title = name;
231
+ }
232
+ else if (heading.hashes === "##") {
233
+ sections.add(name);
234
+ afterTitleH2 = name === "Title";
235
+ afterStatusH2 = name === "Status";
236
+ continue;
237
+ }
222
238
  }
223
239
  if (afterTitleH2 && line.trim().length > 0 && !line.startsWith("#")) {
224
240
  // Prefer ## Title body over H1 when present.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.95.0",
3
+ "version": "0.97.0",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -247,6 +247,14 @@
247
247
  "types": "./dist/freshness/index.d.ts",
248
248
  "default": "./dist/freshness/index.js"
249
249
  },
250
+ "./parent-turn-shape": {
251
+ "types": "./dist/parent-turn-shape/index.d.ts",
252
+ "default": "./dist/parent-turn-shape/index.js"
253
+ },
254
+ "./delivery-attempt": {
255
+ "types": "./dist/delivery-attempt/index.d.ts",
256
+ "default": "./dist/delivery-attempt/index.js"
257
+ },
250
258
  "./intake": {
251
259
  "types": "./dist/intake/index.d.ts",
252
260
  "default": "./dist/intake/index.js"
@@ -346,8 +354,8 @@
346
354
  "provenance": true
347
355
  },
348
356
  "dependencies": {
349
- "@deftai/directive-content": "^0.95.0",
350
- "@deftai/directive-types": "^0.95.0",
357
+ "@deftai/directive-content": "^0.97.0",
358
+ "@deftai/directive-types": "^0.97.0",
351
359
  "archiver": "^8.0.0"
352
360
  },
353
361
  "scripts": {