@deftai/directive-core 0.98.1 → 0.100.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 (144) hide show
  1. package/dist/authz/actions.d.ts +6 -0
  2. package/dist/authz/actions.js +3 -0
  3. package/dist/authz/classify.js +401 -75
  4. package/dist/authz/decompose-apply.d.ts +67 -0
  5. package/dist/authz/decompose-apply.js +302 -0
  6. package/dist/authz/index.d.ts +3 -1
  7. package/dist/authz/index.js +3 -1
  8. package/dist/authz/store.d.ts +53 -0
  9. package/dist/authz/store.js +219 -1
  10. package/dist/authz/types.d.ts +19 -2
  11. package/dist/authz/types.js +5 -1
  12. package/dist/consumer-check-contract/evaluate.d.ts +40 -0
  13. package/dist/consumer-check-contract/evaluate.js +188 -3
  14. package/dist/consumer-check-contract/index.d.ts +1 -1
  15. package/dist/consumer-check-contract/index.js +1 -1
  16. package/dist/content-contracts/skills/greptile-detector.d.ts +42 -0
  17. package/dist/content-contracts/skills/greptile-detector.js +202 -4
  18. package/dist/decision/index.d.ts +17 -0
  19. package/dist/decision/index.js +35 -0
  20. package/dist/decision/list.d.ts +47 -0
  21. package/dist/decision/list.js +250 -0
  22. package/dist/decision/schema.d.ts +88 -0
  23. package/dist/decision/schema.js +293 -0
  24. package/dist/decision/write.d.ts +82 -0
  25. package/dist/decision/write.js +427 -0
  26. package/dist/doctor/checks.d.ts +44 -0
  27. package/dist/doctor/checks.js +428 -2
  28. package/dist/doctor/main.d.ts +6 -7
  29. package/dist/doctor/main.js +72 -79
  30. package/dist/doctor/which.d.ts +20 -1
  31. package/dist/doctor/which.js +67 -1
  32. package/dist/eval/report.d.ts +29 -0
  33. package/dist/eval/report.js +69 -0
  34. package/dist/eval/run.d.ts +9 -0
  35. package/dist/eval/run.js +40 -4
  36. package/dist/eval/version-pin.d.ts +99 -0
  37. package/dist/eval/version-pin.js +181 -0
  38. package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
  39. package/dist/finish-loop/pr-finish-loop.js +116 -5
  40. package/dist/finish-loop/types.d.ts +1 -1
  41. package/dist/hooks/dispatcher.d.ts +9 -5
  42. package/dist/hooks/dispatcher.js +23 -15
  43. package/dist/hooks/readonly.d.ts +6 -3
  44. package/dist/hooks/readonly.js +44 -25
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.js +2 -0
  47. package/dist/init-deposit/agent-hooks.d.ts +20 -0
  48. package/dist/init-deposit/agent-hooks.js +51 -31
  49. package/dist/intake/issue-ingest.js +29 -0
  50. package/dist/lifecycle/completed-consistency.d.ts +60 -0
  51. package/dist/lifecycle/completed-consistency.js +443 -0
  52. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
  53. package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
  54. package/dist/lifecycle/index.d.ts +2 -0
  55. package/dist/lifecycle/index.js +2 -0
  56. package/dist/literal-acceptance/capture.d.ts +52 -0
  57. package/dist/literal-acceptance/capture.js +658 -0
  58. package/dist/literal-acceptance/evaluate.d.ts +47 -0
  59. package/dist/literal-acceptance/evaluate.js +193 -0
  60. package/dist/literal-acceptance/index.d.ts +12 -0
  61. package/dist/literal-acceptance/index.js +12 -0
  62. package/dist/literal-acceptance/run.d.ts +38 -0
  63. package/dist/literal-acceptance/run.js +216 -0
  64. package/dist/literal-acceptance/safety.d.ts +24 -0
  65. package/dist/literal-acceptance/safety.js +251 -0
  66. package/dist/literal-acceptance/types.d.ts +82 -0
  67. package/dist/literal-acceptance/types.js +26 -0
  68. package/dist/platform/host-content-surface.d.ts +74 -0
  69. package/dist/platform/host-content-surface.js +214 -0
  70. package/dist/platform/index.d.ts +1 -0
  71. package/dist/platform/index.js +1 -0
  72. package/dist/policy/ceremony-dial.d.ts +289 -0
  73. package/dist/policy/ceremony-dial.js +980 -0
  74. package/dist/policy/deft-directive-disable.js +12 -2
  75. package/dist/policy/index.d.ts +2 -0
  76. package/dist/policy/index.js +16 -1
  77. package/dist/policy/merge-approval-head.d.ts +129 -0
  78. package/dist/policy/merge-approval-head.js +461 -0
  79. package/dist/policy/require-human-merge.d.ts +5 -0
  80. package/dist/policy/require-human-merge.js +5 -0
  81. package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
  82. package/dist/pr-merge-readiness/ci-gate.js +65 -5
  83. package/dist/pr-merge-readiness/compute.d.ts +13 -0
  84. package/dist/pr-merge-readiness/compute.js +99 -14
  85. package/dist/pr-merge-readiness/evaluate.js +10 -0
  86. package/dist/pr-merge-readiness/gh.d.ts +49 -0
  87. package/dist/pr-merge-readiness/gh.js +268 -0
  88. package/dist/pr-merge-readiness/index.d.ts +2 -2
  89. package/dist/pr-merge-readiness/index.js +2 -2
  90. package/dist/pr-merge-readiness/mergeability.js +5 -0
  91. package/dist/pr-merge-readiness/output.js +2 -0
  92. package/dist/pr-merge-readiness/parse.js +4 -0
  93. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
  94. package/dist/pr-merge-readiness/types.d.ts +6 -0
  95. package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
  96. package/dist/pr-wait-mergeable/cascade.js +115 -1
  97. package/dist/pr-wait-mergeable/main.d.ts +4 -0
  98. package/dist/pr-wait-mergeable/main.js +4 -0
  99. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  100. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
  101. package/dist/pr-wait-mergeable/wrappers.js +7 -1
  102. package/dist/preflight/evaluate.d.ts +15 -1
  103. package/dist/preflight/evaluate.js +33 -2
  104. package/dist/render/framework-commands.js +4 -0
  105. package/dist/scope/acceptance-evidence.d.ts +76 -0
  106. package/dist/scope/acceptance-evidence.js +348 -0
  107. package/dist/scope/coverage-map.d.ts +98 -0
  108. package/dist/scope/coverage-map.js +558 -0
  109. package/dist/scope/decompose.js +125 -11
  110. package/dist/scope/effort-activate-gate.d.ts +28 -0
  111. package/dist/scope/effort-activate-gate.js +64 -0
  112. package/dist/scope/index.d.ts +4 -0
  113. package/dist/scope/index.js +4 -0
  114. package/dist/scope/parent-lineage.d.ts +125 -0
  115. package/dist/scope/parent-lineage.js +626 -0
  116. package/dist/scope/transition.d.ts +8 -0
  117. package/dist/scope/transition.js +69 -2
  118. package/dist/session/active-cli.d.ts +79 -0
  119. package/dist/session/active-cli.js +382 -0
  120. package/dist/session/effort-budget.d.ts +130 -0
  121. package/dist/session/effort-budget.js +334 -0
  122. package/dist/session/index.d.ts +2 -0
  123. package/dist/session/index.js +2 -0
  124. package/dist/session/session-start.d.ts +30 -1
  125. package/dist/session/session-start.js +234 -26
  126. package/dist/session/verify-session-ritual.d.ts +14 -0
  127. package/dist/session/verify-session-ritual.js +33 -0
  128. package/dist/story-ready/evaluate.d.ts +11 -0
  129. package/dist/story-ready/evaluate.js +41 -3
  130. package/dist/swarm/index.d.ts +2 -0
  131. package/dist/swarm/index.js +2 -0
  132. package/dist/swarm/pre-dispatch-cli.d.ts +19 -0
  133. package/dist/swarm/pre-dispatch-cli.js +143 -0
  134. package/dist/swarm/pre-dispatch.d.ts +87 -0
  135. package/dist/swarm/pre-dispatch.js +373 -0
  136. package/dist/triage/actions/candidates-log.d.ts +1 -2
  137. package/dist/triage/actions/candidates-log.js +37 -6
  138. package/dist/vbrief-activate/activate.js +6 -0
  139. package/dist/vbrief-validate/constants.d.ts +2 -0
  140. package/dist/vbrief-validate/constants.js +2 -0
  141. package/dist/vbrief-validate/schema.js +4 -1
  142. package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
  143. package/dist/xbrief-migrate/migrate-project.js +92 -3
  144. package/package.json +19 -3
@@ -13,6 +13,38 @@ const TIER3_LINE_RE = /^\s*P[01]\s+--\s/gm;
13
13
  const TIER3_NEGATIONS = ["No ", "Zero ", "no ", "NO "];
14
14
  const CONFIDENCE_INLINE_RE = /Confidence Score:\s*(\d+)\s*\/\s*5/;
15
15
  const CONFIDENCE_HEADING_RE = /^#{1,6}\s*Confidence Score:\s*(\d+)\s*\/\s*5\s*$/m;
16
+ /**
17
+ * Advisory should-not-merge prose (#3225) — reviewer bots often record a block
18
+ * only in comment body text without formal Changes-Requested. Patterns are
19
+ * applied after code-fence strip; case-insensitive. Composes with
20
+ * minGreptileConfidence (#3095): either signal alone is blocking.
21
+ *
22
+ * Matching is scoped to **verdict-bearing regions** (Confidence Score detail /
23
+ * heading sections, Summary:/Decision: lines) so descriptive overview prose
24
+ * that names the detector phrases ("adds should-not-merge parsing") does not
25
+ * false-block a clean review (Greptile residual on PR #3227 / #1004 class).
26
+ */
27
+ const ADVISORY_SHOULD_NOT_MERGE_RES = [
28
+ // "not safe to merge" | "not yet safe to merge"
29
+ /\bnot\s+(?:yet\s+)?safe\s+to\s+merge\b/i,
30
+ // should-not-merge | should not merge | should–not–merge (hyphen optional each side)
31
+ /\bshould\s*[-–—]?\s*not\s*[-–—]?\s*merge\b/i,
32
+ /\bsafe\s+to\s+merge\s+once\s+corrected\b/i,
33
+ /\bdo\s+not\s+merge\b/i,
34
+ /\bnot\s+ready\s+to\s+merge\b/i,
35
+ /\bnot\s+ready\s+for\s+merge\b/i,
36
+ ];
37
+ /**
38
+ * High-signal phrases scanned outside Overview tables after fence strip.
39
+ * Soft phrases (do not merge / should not merge with spaces) stay region-only
40
+ * so explanatory Confidence residual prose naming the detector does not thrash
41
+ * indefinitely (#2881 / PR #3227 conf 4 residual).
42
+ */
43
+ const ADVISORY_HIGH_SIGNAL_RES = [
44
+ /\bnot\s+(?:yet\s+)?safe\s+to\s+merge\b/i,
45
+ /\bshould-not-merge\b/i,
46
+ /\bsafe\s+to\s+merge\s+once\s+corrected\b/i,
47
+ ];
16
48
  const NAIVE_INLINE_SHA_RE = /Last reviewed commit:\s*([0-9a-f]{7,40})/;
17
49
  const MARKDOWN_LINK_SHA_RE = /Last reviewed commit:\s*\[.*?\]\(https?:\/\/github\.com\/[^/]+\/[^/]+\/commit\/(?<sha>[0-9a-f]{7,40})/;
18
50
  function lineFor(body, pos) {
@@ -27,6 +59,109 @@ export function parseConfidence(body) {
27
59
  }
28
60
  return m ? Number.parseInt(m[1] ?? "0", 10) : null;
29
61
  }
62
+ /**
63
+ * Extract verdict-bearing text windows from a bot rolling-summary body (#3225).
64
+ * Prefers Confidence Score sections and Summary:/Decision: lines over whole-body
65
+ * Overview tables that may *describe* advisory phrases without issuing them.
66
+ */
67
+ export function extractAdvisoryVerdictRegions(body) {
68
+ const text = stripCodeFences(body);
69
+ const regions = [];
70
+ // Per-details blocks only — do not span from Overview into Confidence Score.
71
+ for (const m of text.matchAll(/<details\b[^>]*>[\s\S]*?<\/details>/gi)) {
72
+ const block = m[0] ?? "";
73
+ if (/Confidence\s+Score/i.test(block)) {
74
+ regions.push(block);
75
+ }
76
+ }
77
+ for (const m of text.matchAll(/(?:^|\n)#{1,6}\s*Confidence\s+Score\s*:[^\n]*\n([\s\S]*?)(?=\n#{1,6}\s|\n<details\b|\n---\s*$|\n\*?\*?Last reviewed|\z)/gi)) {
78
+ regions.push(m[0] ?? "");
79
+ }
80
+ for (const m of text.matchAll(/(?:^|\n)(?:\*\*)?Confidence\s+Score(?:\*\*)?\s*:\s*\d+\s*\/\s*5[^\n]*\n([\s\S]{0,800})/gi)) {
81
+ regions.push(m[0] ?? "");
82
+ }
83
+ for (const m of text.matchAll(/(?:^|\n)(?:Summary|Decision|Verdict)\s*:\s*([^\n]+(?:\n(?![A-Z][^\n]{0,40}:)[^\n]+)*)/gi)) {
84
+ regions.push(m[0] ?? "");
85
+ }
86
+ return regions.filter((r) => r.trim().length > 0).join("\n\n");
87
+ }
88
+ function anyPatternMatches(text, patterns) {
89
+ return patterns.some((re) => {
90
+ re.lastIndex = 0;
91
+ return re.test(text);
92
+ });
93
+ }
94
+ /**
95
+ * True when a line is a **verdict-shaped** advisory phrase (#3225 residual).
96
+ * Requires the match near the start of the line (after optional Summary:/Decision:
97
+ * label, bullet, or bold). Mid-sentence descriptive mentions
98
+ * ("adds should-not-merge parsing", "The PR is not yet safe to merge because…")
99
+ * do not count — those are residual discussion, not the bot's verdict line.
100
+ */
101
+ function lineHasAnchoredAdvisory(line, patterns) {
102
+ let bare = line.trim();
103
+ // Blockquote / common markdown wrappers Greptile residual (PR #3227 conf 4).
104
+ bare = bare.replace(/^>\s*/, "");
105
+ bare = bare.replace(/^(?:Summary|Decision|Verdict)\s*[:\-–—]\s*/i, "");
106
+ bare = bare.replace(/^(?:[-*•]\s+)+/, "");
107
+ bare = bare.replace(/^\*\*/, "").replace(/\*\*$/, "");
108
+ bare = bare.replace(/^_/, "").replace(/_$/, "");
109
+ // Explicit subject-prefixed verdicts Greptile uses: "The PR is not safe to merge…"
110
+ bare = bare.replace(/^(?:the\s+pr|this\s+pr|this\s+change|the\s+change|this\s+diff)\s+is\s+/i, "");
111
+ bare = bare.trim();
112
+ if (bare.length === 0) {
113
+ return false;
114
+ }
115
+ for (const re of patterns) {
116
+ re.lastIndex = 0;
117
+ const m = re.exec(bare);
118
+ // Allow small lead-in after wrappers (em-dash residual, thin markdown).
119
+ if (m !== null && (m.index ?? 0) <= 4) {
120
+ return true;
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+ function textHasAnchoredAdvisory(text, patterns) {
126
+ for (const line of text.split(/\r?\n/)) {
127
+ if (lineHasAnchoredAdvisory(line, patterns)) {
128
+ return true;
129
+ }
130
+ }
131
+ return false;
132
+ }
133
+ /**
134
+ * True when comment body prose records an advisory should-not-merge / not-safe
135
+ * verdict (#3225). Code-fence regions are stripped first so detector docs do
136
+ * not self-trigger (#1004).
137
+ *
138
+ * Line-anchored hybrid scan (PR #3227 residuals):
139
+ * 1. Soft + high-signal **line-anchored** phrases in Confidence / Summary /
140
+ * Decision regions (and whole body)
141
+ * 2. Same line-anchored high-signal set on whole body (standalone warnings)
142
+ * Mid-sentence residual discussion does not hard-block; conf floor still does (#3095).
143
+ */
144
+ export function hasShouldNotMergeProse(body) {
145
+ const text = stripCodeFences(body);
146
+ const regions = extractAdvisoryVerdictRegions(body);
147
+ if (regions.length > 0 && textHasAnchoredAdvisory(regions, ADVISORY_SHOULD_NOT_MERGE_RES)) {
148
+ return true;
149
+ }
150
+ // Soft + high line-anchored anywhere (covers standalone spaced Do not merge /
151
+ // should not merge outside Confidence regions without Overview mid-sentence hits).
152
+ if (textHasAnchoredAdvisory(text, ADVISORY_SHOULD_NOT_MERGE_RES)) {
153
+ return true;
154
+ }
155
+ return false;
156
+ }
157
+ export function parseAdvisoryReviewerVerdict(body) {
158
+ const findings = detect(body);
159
+ return {
160
+ confidence: parseConfidence(body),
161
+ shouldNotMerge: hasShouldNotMergeProse(body),
162
+ hasBlocking: findings.has_blocking,
163
+ };
164
+ }
30
165
  export function detect(body) {
31
166
  body = stripCodeFences(body);
32
167
  const tier1_p0 = (body.match(/<img alt="P0"/g) ?? []).length;
@@ -59,10 +194,9 @@ export function detect(body) {
59
194
  tier25_p1 += 1;
60
195
  }
61
196
  }
62
- let tier3_sentinel = false;
63
- if (body.includes("Not safe to merge")) {
64
- tier3_sentinel = true;
65
- }
197
+ // Tier 3: advisory should-not-merge prose (#3225 extends "Not safe to merge")
198
+ // plus count-prose and line-anchored P0/P1 sentinels (#910).
199
+ let tier3_sentinel = hasShouldNotMergeProse(body);
66
200
  if (!tier3_sentinel) {
67
201
  for (const m of body.matchAll(TIER3_COUNT_RE)) {
68
202
  const line = lineFor(body, m.index ?? 0);
@@ -133,6 +267,70 @@ Last reviewed commit: [refactor: thing](https://github.com/deftai/directive/comm
133
267
  Summary: Not safe to merge until the mocked-import test defect and the two
134
268
  previously filed P1s are resolved.
135
269
  `;
270
+ /** #3225 live-case shape: conf 3/5 + should-not-merge prose, zero badges. */
271
+ export const BODY_ADVISORY_SHOULD_NOT_MERGE_CONF3 = `Greptile review of head advisory01
272
+
273
+ ## Confidence Score: 3/5
274
+
275
+ Summary: should-not-merge — residual risk on the auth path is too high for
276
+ this change set. Formal review state is still Comment (not Changes Requested).
277
+
278
+ Last reviewed commit: [feat: advisory](https://github.com/deftai/directive/commit/advisory01abcdef12)
279
+ `;
280
+ /** Green mechanical path trap: high conf but explicit do-not-merge prose. */
281
+ export const BODY_ADVISORY_DO_NOT_MERGE_HIGH_CONF = `Greptile review of head advisory02
282
+
283
+ Confidence Score: 5/5
284
+
285
+ No P0 or P1 issues found via badges.
286
+
287
+ Summary: Do not merge until the operator documents residual risk.
288
+
289
+ Last reviewed commit: [docs: note](https://github.com/deftai/directive/commit/advisory02abcdef12)
290
+ `;
291
+ /**
292
+ * Overview prose names the detector phrase without issuing a verdict (#3225 residual).
293
+ * Confidence section is clean — must NOT set shouldNotMerge.
294
+ */
295
+ export const BODY_ADVISORY_DESCRIPTIVE_ONLY = `Greptile review of head advisory03
296
+
297
+ <details><summary><h3>Greptile Summary</h3></summary>
298
+
299
+ This PR adds should-not-merge prose detection and Not safe to merge matching to
300
+ merge-ready. Descriptive overview only — no blocking residual on the product path.
301
+
302
+ </details>
303
+
304
+ <details open><summary><h3>Confidence Score: 5/5</h3></summary>
305
+
306
+ No P0 or P1 issues found. The change looks clean and well-tested.
307
+
308
+ </details>
309
+
310
+ Last reviewed commit: [feat: detector](https://github.com/deftai/directive/commit/advisory03abcdef12)
311
+ `;
312
+ /** Standalone high-signal warning outside Confidence section must still block. */
313
+ export const BODY_ADVISORY_STANDALONE_NOT_SAFE = `Greptile review of head advisory04
314
+
315
+ <details open><summary><h3>Confidence Score: 5/5</h3></summary>
316
+
317
+ Looks solid from a findings perspective.
318
+
319
+ </details>
320
+
321
+ Not safe to merge until the operator confirms residual risk handling.
322
+
323
+ Last reviewed commit: [feat: residual](https://github.com/deftai/directive/commit/advisory04abcdef12)
324
+ `;
325
+ /** Subject-prefixed verdict Greptile commonly emits (#3225 residual). */
326
+ export const BODY_ADVISORY_SUBJECT_PREFIXED = `Greptile review of head advisory05
327
+
328
+ ## Confidence Score: 4/5
329
+
330
+ The PR is not safe to merge until residual risk is documented on the auth path.
331
+
332
+ Last reviewed commit: [feat: subject](https://github.com/deftai/directive/commit/advisory05abcdef12)
333
+ `;
136
334
  export const BODY_TIER3_COUNT_PROSE_ONLY = `Greptile review of head deadbeef
137
335
 
138
336
  Confidence Score: 4/5
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Structured agent decision log (#1396).
3
+ *
4
+ * CLI surface: task decision:write / task decision:list
5
+ */
6
+ export { type DecisionListCliArgs, type DecisionListEntry, type DecisionListOptions, type DecisionListResult, decisionListMain, parseDecisionListArgs, runDecisionList, } from "./list.js";
7
+ export { DECISION_FILE_SUFFIX, DECISION_SCHEMA_VERSION, DECISIONS_DIR_REL, type DecisionAlternative, type DecisionConfidence, type DecisionGoverningRule, type DecisionRecord, type DecisionValidationError, type DecisionValidationResult, datePrefixFromTimestamp, decisionFilename, formatDecisionValidationErrors, normalizeTimestamp, sanitizeForTerminal, slugifyDecision, validateDecisionRecord, } from "./schema.js";
8
+ export { appendScopeDecisionPointer, type DecisionWriteCliArgs, type DecisionWriteInput, type DecisionWriteOutcome, type DecisionWriteResult, decisionWriteMain, parseDecisionWriteArgs, runDecisionWrite, } from "./write.js";
9
+ /**
10
+ * Unified CLI entry for dispatch when verb is decision-write / decision-list.
11
+ * Expects first argv token to be the subcommand (write|list) when routed via
12
+ * a single decision stem; dispatch registers separate stems per verb.
13
+ */
14
+ export declare function mainEntry(argv?: string[]): number;
15
+ export declare function writeMainEntry(argv?: string[]): number;
16
+ export declare function listMainEntry(argv?: string[]): number;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Structured agent decision log (#1396).
3
+ *
4
+ * CLI surface: task decision:write / task decision:list
5
+ */
6
+ import { decisionListMain } from "./list.js";
7
+ import { decisionWriteMain } from "./write.js";
8
+ export { decisionListMain, parseDecisionListArgs, runDecisionList, } from "./list.js";
9
+ export { DECISION_FILE_SUFFIX, DECISION_SCHEMA_VERSION, DECISIONS_DIR_REL, datePrefixFromTimestamp, decisionFilename, formatDecisionValidationErrors, normalizeTimestamp, sanitizeForTerminal, slugifyDecision, validateDecisionRecord, } from "./schema.js";
10
+ export { appendScopeDecisionPointer, decisionWriteMain, parseDecisionWriteArgs, runDecisionWrite, } from "./write.js";
11
+ /**
12
+ * Unified CLI entry for dispatch when verb is decision-write / decision-list.
13
+ * Expects first argv token to be the subcommand (write|list) when routed via
14
+ * a single decision stem; dispatch registers separate stems per verb.
15
+ */
16
+ export function mainEntry(argv = process.argv.slice(2)) {
17
+ const [head, ...rest] = argv;
18
+ if (head === "write" || head === "decision:write") {
19
+ return decisionWriteMain(rest);
20
+ }
21
+ if (head === "list" || head === "decision:list") {
22
+ return decisionListMain(rest);
23
+ }
24
+ // When dispatch loads decision-write / decision-list stems, argv has no head.
25
+ // Callers use decisionWriteMain / decisionListMain directly from loadCoreModuleHandler.
26
+ process.stderr.write("decision: unknown subcommand. Use decision:write or decision:list.\n");
27
+ return 2;
28
+ }
29
+ export function writeMainEntry(argv = process.argv.slice(2)) {
30
+ return decisionWriteMain(argv);
31
+ }
32
+ export function listMainEntry(argv = process.argv.slice(2)) {
33
+ return decisionListMain(argv);
34
+ }
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * decision:list — find structured decision records (#1396).
3
+ */
4
+ export interface DecisionListOptions {
5
+ readonly projectRoot?: string | null;
6
+ /** Filter by substring match on id, decision text, or tags. */
7
+ readonly query?: string | null;
8
+ /** Filter by scope path substring. */
9
+ readonly scope?: string | null;
10
+ /** Filter by related issue number. */
11
+ readonly issue?: number | null;
12
+ /** Max rows (default unlimited). */
13
+ readonly limit?: number | null;
14
+ readonly json?: boolean;
15
+ }
16
+ export interface DecisionListEntry {
17
+ readonly path: string;
18
+ readonly id: string;
19
+ readonly decision: string;
20
+ readonly confidence: string;
21
+ readonly timestamp: string;
22
+ readonly revisitTrigger: string;
23
+ readonly activeScopeRefs: readonly string[];
24
+ readonly tags: readonly string[];
25
+ readonly relatedIssues: readonly number[];
26
+ }
27
+ export interface DecisionListResult {
28
+ readonly exitCode: 0 | 1 | 2;
29
+ readonly entries: readonly DecisionListEntry[];
30
+ readonly message: string;
31
+ }
32
+ /** List decision records under xbrief/decisions/. */
33
+ export declare function runDecisionList(options?: DecisionListOptions): DecisionListResult;
34
+ export interface DecisionListCliArgs {
35
+ query?: string;
36
+ scope?: string;
37
+ issue?: number;
38
+ limit?: number;
39
+ json?: boolean;
40
+ projectRoot?: string;
41
+ error?: string;
42
+ }
43
+ /** Parse argv for decision:list. */
44
+ export declare function parseDecisionListArgs(argv: readonly string[]): DecisionListCliArgs;
45
+ /** CLI entry for decision:list. */
46
+ export declare function decisionListMain(argv: readonly string[]): number;
47
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1,250 @@
1
+ /**
2
+ * decision:list — find structured decision records (#1396).
3
+ */
4
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
6
+ import { resolveProjectRoot } from "../scope/project-context.js";
7
+ import { DECISION_FILE_SUFFIX, DECISIONS_DIR_REL, sanitizeForTerminal, validateDecisionRecord, } from "./schema.js";
8
+ function isDecisionFile(name) {
9
+ return name.endsWith(DECISION_FILE_SUFFIX);
10
+ }
11
+ function loadRecord(absPath, relPath) {
12
+ try {
13
+ const raw = readFileSync(absPath, "utf8");
14
+ const parsed = JSON.parse(raw);
15
+ const validated = validateDecisionRecord(parsed);
16
+ if (!validated.ok || validated.record === undefined) {
17
+ return {
18
+ path: relPath,
19
+ id: "(invalid)",
20
+ decision: `unparseable: ${validated.errors.map((e) => e.message).join("; ")}`,
21
+ confidence: "?",
22
+ timestamp: "",
23
+ revisitTrigger: "",
24
+ activeScopeRefs: [],
25
+ tags: [],
26
+ relatedIssues: [],
27
+ };
28
+ }
29
+ const r = validated.record;
30
+ return {
31
+ path: relPath,
32
+ id: r.id,
33
+ decision: r.decision,
34
+ confidence: r.confidence,
35
+ timestamp: r.timestamp,
36
+ revisitTrigger: r.revisitTrigger,
37
+ activeScopeRefs: r.activeScopeRefs,
38
+ tags: r.tags ?? [],
39
+ relatedIssues: r.relatedIssues ?? [],
40
+ };
41
+ }
42
+ catch (err) {
43
+ return {
44
+ path: relPath,
45
+ id: "(error)",
46
+ decision: err instanceof Error ? err.message : String(err),
47
+ confidence: "?",
48
+ timestamp: "",
49
+ revisitTrigger: "",
50
+ activeScopeRefs: [],
51
+ tags: [],
52
+ relatedIssues: [],
53
+ };
54
+ }
55
+ }
56
+ function scanDecisionsDir(projectRoot) {
57
+ const dir = resolve(projectRoot, DECISIONS_DIR_REL);
58
+ if (!existsSync(dir)) {
59
+ return [];
60
+ }
61
+ let names;
62
+ try {
63
+ names = readdirSync(dir).filter(isDecisionFile);
64
+ }
65
+ catch {
66
+ return [];
67
+ }
68
+ const entries = [];
69
+ for (const name of names) {
70
+ const abs = join(dir, name);
71
+ try {
72
+ if (!statSync(abs).isFile())
73
+ continue;
74
+ }
75
+ catch {
76
+ continue;
77
+ }
78
+ const rel = `${DECISIONS_DIR_REL}/${name}`.replace(/\\/g, "/");
79
+ const entry = loadRecord(abs, rel);
80
+ if (entry !== null)
81
+ entries.push(entry);
82
+ }
83
+ entries.sort((a, b) => {
84
+ if (a.timestamp === b.timestamp)
85
+ return a.path.localeCompare(b.path);
86
+ return a.timestamp < b.timestamp ? 1 : -1;
87
+ });
88
+ return entries;
89
+ }
90
+ function matchesFilters(entry, options) {
91
+ if (options.query !== undefined && options.query !== null && options.query.trim().length > 0) {
92
+ const q = options.query.trim().toLowerCase();
93
+ const hay = [
94
+ entry.id,
95
+ entry.decision,
96
+ entry.revisitTrigger,
97
+ ...entry.tags,
98
+ ...entry.activeScopeRefs,
99
+ ]
100
+ .join(" ")
101
+ .toLowerCase();
102
+ if (!hay.includes(q))
103
+ return false;
104
+ }
105
+ if (options.scope !== undefined && options.scope !== null && options.scope.trim().length > 0) {
106
+ const s = options.scope.trim().replace(/\\/g, "/").toLowerCase();
107
+ if (!entry.activeScopeRefs.some((r) => r.toLowerCase().includes(s)))
108
+ return false;
109
+ }
110
+ if (options.issue !== undefined && options.issue !== null) {
111
+ if (!entry.relatedIssues.includes(options.issue))
112
+ return false;
113
+ }
114
+ return true;
115
+ }
116
+ /** List decision records under xbrief/decisions/. */
117
+ export function runDecisionList(options = {}) {
118
+ const projectRootRaw = resolveProjectRoot(options.projectRoot ?? undefined);
119
+ if (projectRootRaw === null) {
120
+ return {
121
+ exitCode: 2,
122
+ entries: [],
123
+ message: "Error: could not resolve project root. Pass --project-root or run from a directive repo.\n",
124
+ };
125
+ }
126
+ const projectRoot = resolve(projectRootRaw);
127
+ let entries = scanDecisionsDir(projectRoot).filter((e) => matchesFilters(e, options));
128
+ if (options.limit !== undefined && options.limit !== null && options.limit > 0) {
129
+ entries = entries.slice(0, options.limit);
130
+ }
131
+ if (entries.length === 0) {
132
+ return {
133
+ exitCode: 0,
134
+ entries: [],
135
+ message: `No decision records under ${DECISIONS_DIR_REL}/.\n`,
136
+ };
137
+ }
138
+ const lines = entries.map((e) => {
139
+ const scopes = e.activeScopeRefs.length > 0
140
+ ? ` scope=${sanitizeForTerminal(e.activeScopeRefs.join(","))}`
141
+ : "";
142
+ const tags = e.tags.length > 0 ? ` tags=${sanitizeForTerminal(e.tags.join(","))}` : "";
143
+ return (`${sanitizeForTerminal(e.path)}\n` +
144
+ ` ${sanitizeForTerminal(e.decision)}\n` +
145
+ ` confidence=${sanitizeForTerminal(e.confidence)} ts=${sanitizeForTerminal(e.timestamp)}` +
146
+ `${scopes}${tags}\n` +
147
+ ` revisit: ${sanitizeForTerminal(e.revisitTrigger)}`);
148
+ });
149
+ return {
150
+ exitCode: 0,
151
+ entries,
152
+ message: `${lines.join("\n\n")}\n`,
153
+ };
154
+ }
155
+ /** Parse argv for decision:list. */
156
+ export function parseDecisionListArgs(argv) {
157
+ const out = {};
158
+ for (let i = 0; i < argv.length; i += 1) {
159
+ const arg = argv[i];
160
+ if (arg === "--json")
161
+ out.json = true;
162
+ else if (arg === "--query" || arg.startsWith("--query=")) {
163
+ if (arg === "--query")
164
+ out.query = argv[++i];
165
+ else
166
+ out.query = arg.slice("--query=".length);
167
+ }
168
+ else if (arg === "--scope" || arg.startsWith("--scope=")) {
169
+ if (arg === "--scope")
170
+ out.scope = argv[++i];
171
+ else
172
+ out.scope = arg.slice("--scope=".length);
173
+ }
174
+ else if (arg === "--issue" || arg.startsWith("--issue=")) {
175
+ const raw = arg === "--issue" ? argv[++i] : arg.slice("--issue=".length);
176
+ if (raw === undefined || raw.trim().length === 0 || raw.startsWith("-")) {
177
+ return { ...out, error: "--issue requires a positive integer" };
178
+ }
179
+ if (!/^\d+$/.test(raw.trim())) {
180
+ return { ...out, error: `--issue must be a positive integer, got: ${raw}` };
181
+ }
182
+ const n = Number(raw.trim());
183
+ if (!Number.isSafeInteger(n) || n <= 0) {
184
+ return {
185
+ ...out,
186
+ error: `--issue must be a safe positive integer (<= ${Number.MAX_SAFE_INTEGER}), got: ${raw}`,
187
+ };
188
+ }
189
+ out.issue = n;
190
+ }
191
+ else if (arg === "--limit" || arg.startsWith("--limit=")) {
192
+ const raw = arg === "--limit" ? argv[++i] : arg.slice("--limit=".length);
193
+ if (raw === undefined || raw.trim().length === 0 || raw.startsWith("-")) {
194
+ return { ...out, error: "--limit requires a positive integer" };
195
+ }
196
+ if (!/^\d+$/.test(raw.trim())) {
197
+ return { ...out, error: `--limit must be a positive integer, got: ${raw}` };
198
+ }
199
+ const n = Number(raw.trim());
200
+ if (!Number.isSafeInteger(n) || n <= 0) {
201
+ return {
202
+ ...out,
203
+ error: `--limit must be a safe positive integer (<= ${Number.MAX_SAFE_INTEGER}), got: ${raw}`,
204
+ };
205
+ }
206
+ out.limit = n;
207
+ }
208
+ else if (arg === "--project-root" || arg.startsWith("--project-root=")) {
209
+ if (arg === "--project-root")
210
+ out.projectRoot = argv[++i];
211
+ else
212
+ out.projectRoot = arg.slice("--project-root=".length);
213
+ }
214
+ else if (arg.startsWith("-")) {
215
+ return { ...out, error: `unrecognized argument: ${arg}` };
216
+ }
217
+ else if (out.query === undefined) {
218
+ out.query = arg;
219
+ }
220
+ }
221
+ return out;
222
+ }
223
+ /** CLI entry for decision:list. */
224
+ export function decisionListMain(argv) {
225
+ const args = parseDecisionListArgs(argv);
226
+ if (args.error !== undefined) {
227
+ process.stderr.write(`decision:list: ${args.error}\n`);
228
+ return 2;
229
+ }
230
+ const result = runDecisionList({
231
+ projectRoot: args.projectRoot,
232
+ query: args.query,
233
+ scope: args.scope,
234
+ issue: args.issue,
235
+ limit: args.limit,
236
+ json: args.json,
237
+ });
238
+ if (args.json) {
239
+ process.stdout.write(`${JSON.stringify({
240
+ exit_code: result.exitCode,
241
+ count: result.entries.length,
242
+ entries: result.entries,
243
+ }, null, 2)}\n`);
244
+ }
245
+ else {
246
+ process.stdout.write(result.message);
247
+ }
248
+ return result.exitCode;
249
+ }
250
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Lightweight structured agent decision log schema (#1396).
3
+ *
4
+ * Intent-debt records for significant choices only. Not a full lifecycle xBRIEF
5
+ * and not a replacement for docs/decisions/ADR-*.md.
6
+ */
7
+ export declare const DECISION_SCHEMA_VERSION: "deft.decision.v1";
8
+ /** Directory under project root for standalone (cross-cutting) decision files. */
9
+ export declare const DECISIONS_DIR_REL = "xbrief/decisions";
10
+ /** Filename suffix for decision records. */
11
+ export declare const DECISION_FILE_SUFFIX = ".decision.json";
12
+ export type DecisionConfidence = "low" | "medium" | "high";
13
+ /** Governing rule or constraint that informed the decision. */
14
+ export interface DecisionGoverningRule {
15
+ /** Human-readable description of the rule/constraint. */
16
+ readonly description: string;
17
+ /** Path to the rule source (skill, docs, AGENTS.md section, issue). */
18
+ readonly path?: string | null;
19
+ /** RFC2119 tier when the rule is MUST/SHOULD/MAY style. */
20
+ readonly rfc2119?: "MUST" | "SHOULD" | "MAY" | "MUST NOT" | "SHOULD NOT" | null;
21
+ }
22
+ /** One alternative considered and not chosen. */
23
+ export interface DecisionAlternative {
24
+ readonly option: string;
25
+ readonly whyNot?: string | null;
26
+ }
27
+ /**
28
+ * Validated decision record (v1).
29
+ *
30
+ * Required fields per #1396 design lock: decision, governing rule/constraint,
31
+ * alternatives considered, why winner, confidence, active scope ref(s) if any,
32
+ * timestamp, revisit trigger.
33
+ */
34
+ export interface DecisionRecord {
35
+ readonly schemaVersion: typeof DECISION_SCHEMA_VERSION;
36
+ /** Stable slug used in the filename (kebab-case). */
37
+ readonly id: string;
38
+ /** One-line or short paragraph of what was decided. */
39
+ readonly decision: string;
40
+ readonly governingRule: DecisionGoverningRule;
41
+ readonly alternativesConsidered: readonly DecisionAlternative[];
42
+ /** Why the chosen option won over the alternatives. */
43
+ readonly whyWinner: string;
44
+ readonly confidence: DecisionConfidence;
45
+ /** Relative path(s) to related scope xBRIEF(s), if any. */
46
+ readonly activeScopeRefs: readonly string[];
47
+ /** ISO-8601 UTC timestamp (second precision preferred). */
48
+ readonly timestamp: string;
49
+ /** When/why a later agent should re-open this decision. */
50
+ readonly revisitTrigger: string;
51
+ /** Optional tags for list filtering. */
52
+ readonly tags?: readonly string[];
53
+ /** Optional related issue numbers (without #). */
54
+ readonly relatedIssues?: readonly number[];
55
+ /** Relative path of the written file (filled after write). */
56
+ readonly path?: string;
57
+ }
58
+ export interface DecisionValidationError {
59
+ readonly field: string;
60
+ readonly message: string;
61
+ }
62
+ export interface DecisionValidationResult {
63
+ readonly ok: boolean;
64
+ readonly errors: readonly DecisionValidationError[];
65
+ readonly record?: DecisionRecord;
66
+ }
67
+ /** Normalize timestamps to second-precision UTC with trailing Z. */
68
+ export declare function normalizeTimestamp(raw?: string | null): string;
69
+ /**
70
+ * Derive a kebab-case slug from free text (max 64 chars).
71
+ * Linear-time (no polynomial regex) for CodeQL safety on library input.
72
+ */
73
+ export declare function slugifyDecision(text: string): string;
74
+ /**
75
+ * Strip terminal / bidi control chars for safe plaintext list rendering.
76
+ * Drops C0 (except tab/LF/CR → space), DEL, C1 (U+0080–U+009F), and
77
+ * Unicode bidi overrides / isolates / embeddings used for deceptive reordering.
78
+ */
79
+ export declare function sanitizeForTerminal(text: string): string;
80
+ /** Date prefix YYYY-MM-DD from an ISO timestamp. */
81
+ export declare function datePrefixFromTimestamp(timestamp: string): string;
82
+ /** Build standalone filename: YYYY-MM-DD-<slug>.decision.json */
83
+ export declare function decisionFilename(id: string, timestamp: string): string;
84
+ /** Validate and normalize an unknown JSON value into a DecisionRecord. */
85
+ export declare function validateDecisionRecord(input: unknown): DecisionValidationResult;
86
+ /** Format validation errors for CLI stderr. */
87
+ export declare function formatDecisionValidationErrors(errors: readonly DecisionValidationError[]): string;
88
+ //# sourceMappingURL=schema.d.ts.map