@ai-sdlc/orchestrator 0.6.0 → 0.10.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 (209) hide show
  1. package/dist/__test-helpers/git-env.d.ts +40 -0
  2. package/dist/__test-helpers/git-env.js +73 -0
  3. package/dist/admission-composite.d.ts +89 -0
  4. package/dist/admission-composite.js +241 -0
  5. package/dist/admission-enrichment.d.ts +142 -0
  6. package/dist/admission-enrichment.js +341 -0
  7. package/dist/admission-hc.d.ts +81 -0
  8. package/dist/admission-hc.js +94 -0
  9. package/dist/admission-score.d.ts +109 -5
  10. package/dist/admission-score.js +90 -11
  11. package/dist/analysis/analyzer.js +3 -2
  12. package/dist/analysis/convention-detector.d.ts +85 -2
  13. package/dist/analysis/convention-detector.js +375 -70
  14. package/dist/analysis/diff-analyzer.d.ts +33 -0
  15. package/dist/analysis/diff-analyzer.js +122 -0
  16. package/dist/analysis/hotspot-analyzer.js +3 -1
  17. package/dist/analysis/index.d.ts +2 -1
  18. package/dist/analysis/index.js +2 -1
  19. package/dist/artifacts/index.d.ts +65 -0
  20. package/dist/artifacts/index.js +142 -0
  21. package/dist/backlog-adapter.d.ts +121 -0
  22. package/dist/backlog-adapter.js +438 -0
  23. package/dist/calibration.d.ts +43 -0
  24. package/dist/calibration.js +76 -0
  25. package/dist/cli/commands/agents.d.ts +24 -0
  26. package/dist/cli/commands/agents.js +66 -1
  27. package/dist/cli/commands/git-remote.d.ts +74 -0
  28. package/dist/cli/commands/git-remote.js +170 -0
  29. package/dist/cli/commands/health.d.ts +4 -0
  30. package/dist/cli/commands/health.js +13 -1
  31. package/dist/cli/commands/init-features.d.ts +208 -0
  32. package/dist/cli/commands/init-features.js +473 -0
  33. package/dist/cli/commands/init-templates.d.ts +104 -0
  34. package/dist/cli/commands/init-templates.js +399 -0
  35. package/dist/cli/commands/init.d.ts +48 -0
  36. package/dist/cli/commands/init.js +322 -23
  37. package/dist/cli/commands/mcp-setup.d.ts +31 -0
  38. package/dist/cli/commands/mcp-setup.js +78 -8
  39. package/dist/cli/formatters/table.js +15 -2
  40. package/dist/cli/index.d.ts +14 -1
  41. package/dist/cli/index.js +81 -20
  42. package/dist/cli/versions.d.ts +57 -0
  43. package/dist/cli/versions.js +128 -0
  44. package/dist/code-area-classifier.d.ts +21 -0
  45. package/dist/code-area-classifier.js +48 -0
  46. package/dist/config.d.ts +33 -1
  47. package/dist/config.js +78 -8
  48. package/dist/database/adapters/external.d.ts +24 -0
  49. package/dist/database/adapters/external.js +80 -0
  50. package/dist/database/adapters/neon.d.ts +41 -0
  51. package/dist/database/adapters/neon.js +98 -0
  52. package/dist/database/adapters/pg-snapshot-restore.d.ts +28 -0
  53. package/dist/database/adapters/pg-snapshot-restore.js +68 -0
  54. package/dist/database/adapters/sqlite-copy.d.ts +32 -0
  55. package/dist/database/adapters/sqlite-copy.js +145 -0
  56. package/dist/database/connection-injection.d.ts +35 -0
  57. package/dist/database/connection-injection.js +93 -0
  58. package/dist/database/index.d.ts +12 -0
  59. package/dist/database/index.js +23 -0
  60. package/dist/database/registry.d.ts +13 -0
  61. package/dist/database/registry.js +27 -0
  62. package/dist/database/topology.d.ts +52 -0
  63. package/dist/database/topology.js +44 -0
  64. package/dist/database/types.d.ts +89 -0
  65. package/dist/database/types.js +26 -0
  66. package/dist/design-authority.d.ts +53 -0
  67. package/dist/design-authority.js +84 -0
  68. package/dist/design-lookahead.d.ts +64 -0
  69. package/dist/design-lookahead.js +86 -0
  70. package/dist/design-quality-trend.d.ts +87 -0
  71. package/dist/design-quality-trend.js +190 -0
  72. package/dist/design-system-context.d.ts +46 -0
  73. package/dist/design-system-context.js +80 -0
  74. package/dist/design-system-correction-loop.d.ts +64 -0
  75. package/dist/design-system-correction-loop.js +128 -0
  76. package/dist/design-system-metrics.d.ts +61 -0
  77. package/dist/design-system-metrics.js +104 -0
  78. package/dist/design-system-stewardship.d.ts +22 -0
  79. package/dist/design-system-stewardship.js +85 -0
  80. package/dist/design-system-validation.d.ts +37 -0
  81. package/dist/design-system-validation.js +88 -0
  82. package/dist/dispatch/index.d.ts +4 -0
  83. package/dist/dispatch/index.js +4 -0
  84. package/dist/dispatch/merge-gate.d.ts +46 -0
  85. package/dist/dispatch/merge-gate.js +90 -0
  86. package/dist/dispatch/requeue.d.ts +57 -0
  87. package/dist/dispatch/requeue.js +131 -0
  88. package/dist/dispatch/worker-pool.d.ts +62 -0
  89. package/dist/dispatch/worker-pool.js +60 -0
  90. package/dist/execute.d.ts +40 -0
  91. package/dist/execute.js +188 -15
  92. package/dist/fix-ci.js +8 -1
  93. package/dist/fix-review.js +8 -1
  94. package/dist/harness/adapters/claude-code.d.ts +29 -0
  95. package/dist/harness/adapters/claude-code.js +191 -0
  96. package/dist/harness/adapters/codex.d.ts +25 -0
  97. package/dist/harness/adapters/codex.js +61 -0
  98. package/dist/harness/independence.d.ts +51 -0
  99. package/dist/harness/independence.js +67 -0
  100. package/dist/harness/index.d.ts +14 -0
  101. package/dist/harness/index.js +20 -0
  102. package/dist/harness/registry.d.ts +17 -0
  103. package/dist/harness/registry.js +31 -0
  104. package/dist/harness/types.d.ts +123 -0
  105. package/dist/harness/types.js +8 -0
  106. package/dist/harness/version-probe.d.ts +14 -0
  107. package/dist/harness/version-probe.js +123 -0
  108. package/dist/index.d.ts +30 -4
  109. package/dist/index.js +35 -2
  110. package/dist/models/classifier.d.ts +78 -0
  111. package/dist/models/classifier.js +277 -0
  112. package/dist/models/index.d.ts +3 -0
  113. package/dist/models/index.js +3 -0
  114. package/dist/models/registry.d.ts +97 -0
  115. package/dist/models/registry.js +173 -0
  116. package/dist/pillar-breakdown.d.ts +85 -0
  117. package/dist/pillar-breakdown.js +162 -0
  118. package/dist/priority.d.ts +5 -0
  119. package/dist/priority.js +18 -5
  120. package/dist/review-meta.d.ts +65 -0
  121. package/dist/review-meta.js +149 -0
  122. package/dist/review.d.ts +4 -0
  123. package/dist/review.js +12 -2
  124. package/dist/runners/claude-code-sdk.d.ts +31 -0
  125. package/dist/runners/claude-code-sdk.js +238 -0
  126. package/dist/runners/claude-code.d.ts +3 -0
  127. package/dist/runners/claude-code.js +30 -66
  128. package/dist/runners/codex.js +4 -1
  129. package/dist/runners/copilot.js +4 -1
  130. package/dist/runners/cursor.js +4 -1
  131. package/dist/runners/git-utils.d.ts +81 -0
  132. package/dist/runners/git-utils.js +201 -0
  133. package/dist/runners/index.d.ts +3 -0
  134. package/dist/runners/index.js +3 -0
  135. package/dist/runners/review-agent.d.ts +20 -0
  136. package/dist/runners/review-agent.js +171 -41
  137. package/dist/runners/runner-registry.js +10 -0
  138. package/dist/runners/sdk-review-runner.d.ts +65 -0
  139. package/dist/runners/sdk-review-runner.js +185 -0
  140. package/dist/runners/security-triage.d.ts +20 -4
  141. package/dist/runners/security-triage.js +39 -15
  142. package/dist/runners/types.d.ts +6 -0
  143. package/dist/runtime/attestations.d.ts +766 -0
  144. package/dist/runtime/attestations.js +1195 -0
  145. package/dist/runtime/git-env.d.ts +53 -0
  146. package/dist/runtime/git-env.js +60 -0
  147. package/dist/runtime/index.d.ts +7 -0
  148. package/dist/runtime/index.js +7 -0
  149. package/dist/runtime/parallelism-flag.d.ts +28 -0
  150. package/dist/runtime/parallelism-flag.js +39 -0
  151. package/dist/runtime/port-allocator.d.ts +32 -0
  152. package/dist/runtime/port-allocator.js +96 -0
  153. package/dist/runtime/worktree-pool.d.ts +86 -0
  154. package/dist/runtime/worktree-pool.js +204 -0
  155. package/dist/runtime/worktree.d.ts +25 -0
  156. package/dist/runtime/worktree.js +111 -0
  157. package/dist/sa-scoring/auto-calibrate.d.ts +69 -0
  158. package/dist/sa-scoring/auto-calibrate.js +107 -0
  159. package/dist/sa-scoring/c1-sa2-computable.d.ts +42 -0
  160. package/dist/sa-scoring/c1-sa2-computable.js +59 -0
  161. package/dist/sa-scoring/composite.d.ts +107 -0
  162. package/dist/sa-scoring/composite.js +139 -0
  163. package/dist/sa-scoring/depparse-client.d.ts +79 -0
  164. package/dist/sa-scoring/depparse-client.js +187 -0
  165. package/dist/sa-scoring/did-compiler.d.ts +122 -0
  166. package/dist/sa-scoring/did-compiler.js +286 -0
  167. package/dist/sa-scoring/drift-monitor.d.ts +84 -0
  168. package/dist/sa-scoring/drift-monitor.js +186 -0
  169. package/dist/sa-scoring/exemplar-bank.d.ts +78 -0
  170. package/dist/sa-scoring/exemplar-bank.js +154 -0
  171. package/dist/sa-scoring/feedback-store.d.ts +100 -0
  172. package/dist/sa-scoring/feedback-store.js +156 -0
  173. package/dist/sa-scoring/index.d.ts +71 -0
  174. package/dist/sa-scoring/index.js +158 -0
  175. package/dist/sa-scoring/layer1-deterministic.d.ts +115 -0
  176. package/dist/sa-scoring/layer1-deterministic.js +298 -0
  177. package/dist/sa-scoring/layer2-structural.d.ts +71 -0
  178. package/dist/sa-scoring/layer2-structural.js +151 -0
  179. package/dist/sa-scoring/layer3-llm.d.ts +86 -0
  180. package/dist/sa-scoring/layer3-llm.js +282 -0
  181. package/dist/sa-scoring/rescore-orchestrator.d.ts +52 -0
  182. package/dist/sa-scoring/rescore-orchestrator.js +47 -0
  183. package/dist/scheduling/burn-down.d.ts +27 -0
  184. package/dist/scheduling/burn-down.js +43 -0
  185. package/dist/scheduling/calibration.d.ts +42 -0
  186. package/dist/scheduling/calibration.js +150 -0
  187. package/dist/scheduling/index.d.ts +8 -0
  188. package/dist/scheduling/index.js +8 -0
  189. package/dist/scheduling/ledger.d.ts +59 -0
  190. package/dist/scheduling/ledger.js +216 -0
  191. package/dist/scheduling/off-peak.d.ts +27 -0
  192. package/dist/scheduling/off-peak.js +112 -0
  193. package/dist/scheduling/schedule-decision.d.ts +39 -0
  194. package/dist/scheduling/schedule-decision.js +89 -0
  195. package/dist/scheduling/tier-analysis.d.ts +47 -0
  196. package/dist/scheduling/tier-analysis.js +81 -0
  197. package/dist/scheduling/types.d.ts +140 -0
  198. package/dist/scheduling/types.js +11 -0
  199. package/dist/shared.d.ts +13 -0
  200. package/dist/shared.js +32 -0
  201. package/dist/state/schema.d.ts +5 -1
  202. package/dist/state/schema.js +223 -1
  203. package/dist/state/store.d.ts +55 -1
  204. package/dist/state/store.js +342 -7
  205. package/dist/state/types.d.ts +139 -0
  206. package/dist/types.d.ts +1 -1
  207. package/dist/validate-agent-output.js +4 -1
  208. package/dist/watch.js +6 -0
  209. package/package.json +3 -2
@@ -0,0 +1,438 @@
1
+ /**
2
+ * Backlog.md tracker adapter for the admission composite.
3
+ *
4
+ * Backlog.md tasks are markdown files in `backlog/tasks/` (open) or
5
+ * `backlog/completed/` (archived). They carry YAML frontmatter
6
+ * (`id`, `title`, `status`, `priority`, `labels`, `references`,
7
+ * `created_date`, `updated_date`, `assignee`) plus a body that
8
+ * includes a `## Acceptance Criteria` checklist.
9
+ *
10
+ * This module:
11
+ * 1. Parses a task file into `BacklogTaskSnapshot`
12
+ * 2. Maps the snapshot onto `AdmissionInput` using the conventions
13
+ * in the bug-fix doc (priority:p* / size:[SML] / track:* / etc.)
14
+ * 3. Emits `qualityFlags` for "Done with unchecked ACs" zombie closes
15
+ *
16
+ * The GitHub-shaped `mapIssueToPriorityInput` in `admission-score.ts`
17
+ * is left intact; the dispatch sits in `cli-admit`.
18
+ */
19
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
20
+ import { join } from 'node:path';
21
+ import { parse as parseYaml } from 'yaml';
22
+ import { normalizeBacklogPriority } from './admission-score.js';
23
+ // ── Parser ──────────────────────────────────────────────────────────
24
+ /**
25
+ * Parse a Backlog.md task markdown file into `BacklogTaskSnapshot`.
26
+ * Throws when the frontmatter is missing or malformed.
27
+ */
28
+ export function parseBacklogTask(content, sourcePath) {
29
+ const fm = extractFrontmatter(content);
30
+ if (!fm)
31
+ throw new Error('Backlog task is missing YAML frontmatter');
32
+ const id = String(fm.id ?? '').trim();
33
+ if (!id)
34
+ throw new Error('Backlog task frontmatter is missing `id`');
35
+ const numericId = parseNumericId(id);
36
+ const labels = normaliseLabels(fm.labels);
37
+ const references = normaliseStringArray(fm.references);
38
+ const dependencies = normaliseStringArray(fm.dependencies);
39
+ const description = extractSection(content, 'Description');
40
+ const acceptanceCriteria = extractAcceptanceCriteria(content);
41
+ return {
42
+ id,
43
+ numericId,
44
+ title: String(fm.title ?? '').trim(),
45
+ description,
46
+ status: String(fm.status ?? '').trim(),
47
+ priority: fm.priority == null ? null : String(fm.priority).trim(),
48
+ labels,
49
+ createdDate: String(fm.created_date ?? '').trim(),
50
+ updatedDate: String(fm.updated_date ?? '').trim(),
51
+ createdBy: fm.created_by ? String(fm.created_by).trim() : undefined,
52
+ acceptanceCriteria,
53
+ references,
54
+ dependencies,
55
+ sourcePath,
56
+ };
57
+ }
58
+ /**
59
+ * Resolve a Backlog task id ("AISDLC-42" or "task-42") to a snapshot
60
+ * by searching the `backlog/tasks/` and `backlog/completed/` directories
61
+ * under `backlogRoot` (typically the project root).
62
+ */
63
+ export function loadBacklogTaskFromRoot(backlogRoot, id) {
64
+ const idLower = id.toLowerCase();
65
+ const candidates = [
66
+ join(backlogRoot, 'backlog', 'tasks'),
67
+ join(backlogRoot, 'backlog', 'completed'),
68
+ ];
69
+ for (const dir of candidates) {
70
+ if (!existsSync(dir))
71
+ continue;
72
+ for (const entry of readdirSync(dir)) {
73
+ if (!entry.toLowerCase().startsWith(`${idLower} `) &&
74
+ !entry.toLowerCase().startsWith(`${idLower}.`)) {
75
+ continue;
76
+ }
77
+ const path = join(dir, entry);
78
+ const content = readFileSync(path, 'utf-8');
79
+ return parseBacklogTask(content, path);
80
+ }
81
+ }
82
+ return undefined;
83
+ }
84
+ // ── Frontmatter helpers (no external YAML dep — handle the subset
85
+ // Backlog.md emits: scalar, sequence, null, "Done"-quoted strings) ──
86
+ function extractFrontmatter(content) {
87
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
88
+ if (!match)
89
+ return undefined;
90
+ return parseSimpleYaml(match[1]);
91
+ }
92
+ function parseSimpleYaml(src) {
93
+ const out = {};
94
+ const lines = src.split(/\r?\n/);
95
+ let i = 0;
96
+ while (i < lines.length) {
97
+ const line = lines[i];
98
+ if (!line.trim() || line.trim().startsWith('#')) {
99
+ i++;
100
+ continue;
101
+ }
102
+ const m = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$/);
103
+ if (!m) {
104
+ i++;
105
+ continue;
106
+ }
107
+ const key = m[1];
108
+ let value = m[2].trim();
109
+ if (value === '' || value === '[]') {
110
+ // Inline empty or block sequence follows.
111
+ const seq = [];
112
+ let j = i + 1;
113
+ while (j < lines.length && /^\s+-\s+/.test(lines[j])) {
114
+ seq.push(lines[j]
115
+ .replace(/^\s+-\s+/, '')
116
+ .trim()
117
+ .replace(/^['"]|['"]$/g, ''));
118
+ j++;
119
+ }
120
+ out[key] = value === '[]' ? [] : seq.length > 0 ? seq : '';
121
+ i = j;
122
+ continue;
123
+ }
124
+ // Strip wrapping quotes if present.
125
+ if ((value.startsWith("'") && value.endsWith("'")) ||
126
+ (value.startsWith('"') && value.endsWith('"'))) {
127
+ value = value.slice(1, -1);
128
+ }
129
+ out[key] = value;
130
+ i++;
131
+ }
132
+ return out;
133
+ }
134
+ function normaliseLabels(raw) {
135
+ if (Array.isArray(raw))
136
+ return raw.map((s) => String(s).trim()).filter(Boolean);
137
+ if (typeof raw === 'string' && raw.trim())
138
+ return [raw.trim()];
139
+ return [];
140
+ }
141
+ function normaliseStringArray(raw) {
142
+ if (Array.isArray(raw))
143
+ return raw.map((s) => String(s).trim()).filter(Boolean);
144
+ return [];
145
+ }
146
+ function parseNumericId(id) {
147
+ const m = id.match(/(\d+)$/);
148
+ return m ? Number(m[1]) : 0;
149
+ }
150
+ function extractSection(content, name) {
151
+ // Match `## <name>` until the next `## ` header or end of file.
152
+ const re = new RegExp(`##\\s+${name}[\\s\\S]*?(?=\\n##\\s|$)`, 'i');
153
+ const match = content.match(re);
154
+ if (!match)
155
+ return '';
156
+ return match[0].replace(/^##\s+\S+\s*/i, '').trim();
157
+ }
158
+ function extractAcceptanceCriteria(content) {
159
+ const section = extractSection(content, 'Acceptance Criteria');
160
+ if (!section)
161
+ return [];
162
+ const out = [];
163
+ for (const line of section.split(/\r?\n/)) {
164
+ const m = line.match(/^\s*-\s+\[([ x])\]\s+(?:#(\d+)\s+)?(.*)$/i);
165
+ if (!m)
166
+ continue;
167
+ out.push({
168
+ index: m[2] ? Number(m[2]) : out.length + 1,
169
+ checked: m[1].toLowerCase() === 'x',
170
+ text: m[3].trim(),
171
+ });
172
+ }
173
+ return out;
174
+ }
175
+ const DEFAULT_SOUL_TRACKS = {
176
+ // Generic defaults — projects override via `.ai-sdlc/soul-tracks.json`.
177
+ 'track:ops': 0.55,
178
+ 'track:infra': 0.55,
179
+ 'track:hygiene': 0.55,
180
+ };
181
+ /**
182
+ * Map a Backlog snapshot onto an `AdmissionInput` plus the
183
+ * `PriorityInput` overrides cli-admit feeds into the composite.
184
+ * The label-mapping table is the load-bearing piece; tests in
185
+ * `backlog-adapter.test.ts` pin each row.
186
+ */
187
+ export function mapBacklogTaskToAdmissionInput(snap, options = {}) {
188
+ const labels = snap.labels;
189
+ const labelSet = new Set(labels.map((l) => l.toLowerCase()));
190
+ const tracks = { ...DEFAULT_SOUL_TRACKS, ...(options.soulTracks ?? {}) };
191
+ const authorAssociation = snap.createdBy && options.maintainers?.includes(snap.createdBy) ? 'OWNER' : 'MEMBER';
192
+ // Defaults — overridden below as labels apply.
193
+ let soulAlignment = 0.5;
194
+ let bugSeverity;
195
+ let complexity;
196
+ // ── Priority labels → explicit priority signal ────────────────
197
+ // priority:p0..p3 + frontmatter `priority:` field.
198
+ let explicitPriority;
199
+ if (labelSet.has('priority:p0'))
200
+ explicitPriority = 1.0;
201
+ else if (labelSet.has('priority:p1'))
202
+ explicitPriority = 0.75;
203
+ else if (labelSet.has('priority:p2'))
204
+ explicitPriority = 0.5;
205
+ else if (labelSet.has('priority:p3'))
206
+ explicitPriority = 0.25;
207
+ else if (snap.priority?.toLowerCase() === 'high')
208
+ explicitPriority = 0.75;
209
+ else if (snap.priority?.toLowerCase() === 'medium')
210
+ explicitPriority = 0.5;
211
+ else if (snap.priority?.toLowerCase() === 'low')
212
+ explicitPriority = 0.25;
213
+ // ── Size labels → complexity ─────────────────────────────────
214
+ if (labelSet.has('size:s'))
215
+ complexity = 2;
216
+ else if (labelSet.has('size:m'))
217
+ complexity = 5;
218
+ else if (labelSet.has('size:l'))
219
+ complexity = 7;
220
+ else if (labelSet.has('size:xl'))
221
+ complexity = 9;
222
+ // Otherwise derive from AC count below.
223
+ // ── Soul-alignment signals ────────────────────────────────────
224
+ if (labelSet.has('source:rfc') || labelSet.has('source:spec')) {
225
+ soulAlignment = Math.max(soulAlignment, 0.9);
226
+ }
227
+ if (labelSet.has('governance') || labelSet.has('compliance')) {
228
+ soulAlignment = Math.max(soulAlignment, 0.85);
229
+ }
230
+ for (const label of labels) {
231
+ const key = label.toLowerCase();
232
+ if (key.startsWith('track:') && tracks[key] !== undefined) {
233
+ soulAlignment = Math.max(soulAlignment, tracks[key]);
234
+ }
235
+ }
236
+ // ── Bug-class labels ──────────────────────────────────────────
237
+ if (labelSet.has('bug') || labelSet.has('regression') || labelSet.has('defect')) {
238
+ bugSeverity = 3;
239
+ }
240
+ if (labelSet.has('security') || labelSet.has('vulnerability')) {
241
+ bugSeverity = 5;
242
+ soulAlignment = Math.max(soulAlignment, 0.7);
243
+ }
244
+ if (labelSet.has('critical') || labelSet.has('p0')) {
245
+ bugSeverity = Math.max(bugSeverity ?? 0, 5);
246
+ }
247
+ // ── AC progress → complexity proxy + quality flags ───────────
248
+ const acTotal = snap.acceptanceCriteria.length;
249
+ const acChecked = snap.acceptanceCriteria.filter((a) => a.checked).length;
250
+ const acProgress = acTotal === 0 ? null : acChecked / acTotal;
251
+ if (complexity === undefined && acTotal > 0) {
252
+ // 1 AC → 1.6, 5 ACs → 4, 9 ACs → 6.4, 13 ACs → 8.8 (capped at 9).
253
+ complexity = Math.min(1 + acTotal * 0.6, 9);
254
+ }
255
+ const qualityFlags = [];
256
+ let defectRiskFactor;
257
+ const isZombieClose = snap.status.toLowerCase() === 'done' && acTotal > 0 && acProgress !== null && acProgress < 1.0;
258
+ if (isZombieClose) {
259
+ const severity = acProgress < 0.5 ? 'high' : 'medium';
260
+ qualityFlags.push({
261
+ kind: 'unchecked-acs-on-done',
262
+ detail: `${acTotal - acChecked}/${acTotal} ACs unchecked`,
263
+ severity,
264
+ });
265
+ defectRiskFactor = severity === 'high' ? 0.3 : 0.15;
266
+ }
267
+ // ── Demand / drift / consensus heuristics ─────────────────────
268
+ let demandSignal;
269
+ let competitiveDrift = 0;
270
+ for (const label of labels) {
271
+ const key = label.toLowerCase();
272
+ if (key.startsWith('source:') && key.includes('-tonight')) {
273
+ demandSignal = Math.max(demandSignal ?? 0, 0.7);
274
+ competitiveDrift += 0.2;
275
+ }
276
+ if (key === 'scope:v1-ship' || key === 'scope:v1') {
277
+ competitiveDrift += 0.6;
278
+ }
279
+ }
280
+ competitiveDrift = Math.min(competitiveDrift, 1);
281
+ // teamConsensus: someone is on the hook.
282
+ const teamConsensus = snap.createdBy ? 0.4 : undefined;
283
+ // builderConviction: anchored in references? Multiple references to
284
+ // existing files = author did the homework.
285
+ const builderConviction = snap.references.length >= 2 ? 0.7 : snap.references.length === 1 ? 0.6 : undefined;
286
+ // commentCount / reactionCount have no Backlog analog yet; default 0.
287
+ const reactionCount = 0;
288
+ const commentCount = 0;
289
+ // Construct the body the existing scorer expects (Description +
290
+ // Acceptance Criteria) — preserves the GitHub mapper's complexity
291
+ // regex for callers that bypass the Backlog mapping path.
292
+ const renderedBody = buildAdmissionBody(snap, complexity);
293
+ const input = {
294
+ issueNumber: snap.numericId,
295
+ title: snap.title,
296
+ body: renderedBody,
297
+ labels,
298
+ reactionCount,
299
+ commentCount,
300
+ createdAt: normaliseIsoDate(snap.createdDate),
301
+ authorAssociation,
302
+ authorLogin: snap.createdBy,
303
+ backlogContext: {
304
+ priority: normalizeBacklogPriority(snap.priority),
305
+ dependencyCount: snap.dependencies.length,
306
+ referenceCount: snap.references.length,
307
+ acceptanceCriteriaCount: snap.acceptanceCriteria.length,
308
+ status: snap.status,
309
+ },
310
+ };
311
+ const priorityInputOverrides = {};
312
+ if (soulAlignment !== 0.5)
313
+ priorityInputOverrides.soulAlignment = soulAlignment;
314
+ if (bugSeverity !== undefined)
315
+ priorityInputOverrides.bugSeverity = bugSeverity;
316
+ if (complexity !== undefined)
317
+ priorityInputOverrides.complexity = complexity;
318
+ if (explicitPriority !== undefined)
319
+ priorityInputOverrides.explicitPriority = explicitPriority;
320
+ if (demandSignal !== undefined)
321
+ priorityInputOverrides.demandSignal = demandSignal;
322
+ if (competitiveDrift > 0)
323
+ priorityInputOverrides.competitiveDrift = competitiveDrift;
324
+ if (teamConsensus !== undefined)
325
+ priorityInputOverrides.teamConsensus = teamConsensus;
326
+ if (builderConviction !== undefined)
327
+ priorityInputOverrides.builderConviction = builderConviction;
328
+ if (defectRiskFactor !== undefined)
329
+ priorityInputOverrides.defectRiskFactor = defectRiskFactor;
330
+ if (qualityFlags.length > 0)
331
+ priorityInputOverrides.qualityFlags = qualityFlags;
332
+ return { input, priorityInputOverrides, qualityFlags };
333
+ }
334
+ function buildAdmissionBody(snap, complexity) {
335
+ const parts = [];
336
+ if (snap.description)
337
+ parts.push(snap.description);
338
+ if (complexity !== undefined) {
339
+ parts.push('');
340
+ parts.push('### Complexity');
341
+ parts.push('');
342
+ parts.push(String(Math.round(complexity)));
343
+ }
344
+ if (snap.acceptanceCriteria.length > 0) {
345
+ parts.push('');
346
+ parts.push('### Acceptance Criteria');
347
+ parts.push('');
348
+ for (const ac of snap.acceptanceCriteria) {
349
+ parts.push(`- [${ac.checked ? 'x' : ' '}] ${ac.text}`);
350
+ }
351
+ }
352
+ return parts.join('\n');
353
+ }
354
+ function normaliseIsoDate(raw) {
355
+ // Backlog stores '2026-03-08 22:27' — coerce to ISO for `cli-admit`.
356
+ if (!raw)
357
+ return new Date().toISOString();
358
+ if (/T\d{2}:\d{2}/.test(raw))
359
+ return raw;
360
+ const m = raw.match(/^(\d{4}-\d{2}-\d{2})[\s T](\d{2}:\d{2})(?::(\d{2}))?$/);
361
+ if (!m)
362
+ return new Date(raw).toISOString();
363
+ return `${m[1]}T${m[2]}:${m[3] ?? '00'}Z`;
364
+ }
365
+ // ── Soul-tracks loader ─────────────────────────────────────────────
366
+ /**
367
+ * Load `.ai-sdlc/soul-tracks.json` from the given config root.
368
+ * Format: `{ "track:enchantment": 0.85, "track:reflect": 0.85, ... }`.
369
+ * Missing file → empty object (caller falls back to DEFAULT_SOUL_TRACKS).
370
+ */
371
+ export function loadSoulTracks(configRoot) {
372
+ const path = join(configRoot, '.ai-sdlc', 'soul-tracks.json');
373
+ if (!existsSync(path))
374
+ return {};
375
+ try {
376
+ const parsed = JSON.parse(readFileSync(path, 'utf-8'));
377
+ const out = {};
378
+ for (const [key, value] of Object.entries(parsed)) {
379
+ if (typeof value === 'number' && value >= 0 && value <= 1)
380
+ out[key] = value;
381
+ }
382
+ return out;
383
+ }
384
+ catch {
385
+ return {};
386
+ }
387
+ }
388
+ /**
389
+ * Load `.ai-sdlc/maintainers.yaml` from the given config root.
390
+ *
391
+ * Accepts either of two shapes:
392
+ *
393
+ * maintainers:
394
+ * - alice
395
+ * - bob
396
+ *
397
+ * or with metadata (only `login` is read, the rest is forward-looking):
398
+ *
399
+ * maintainers:
400
+ * - login: alice
401
+ * role: owner
402
+ * - login: bob
403
+ *
404
+ * A bare top-level list (`- alice\n- bob`) is also accepted. Returns
405
+ * an empty array on missing file or any parse error — callers can
406
+ * still pass `--maintainers` explicitly to override.
407
+ */
408
+ export function loadMaintainers(configRoot) {
409
+ const path = join(configRoot, '.ai-sdlc', 'maintainers.yaml');
410
+ if (!existsSync(path))
411
+ return [];
412
+ try {
413
+ const doc = parseYaml(readFileSync(path, 'utf-8'));
414
+ const list = Array.isArray(doc)
415
+ ? doc
416
+ : doc &&
417
+ typeof doc === 'object' &&
418
+ Array.isArray(doc.maintainers)
419
+ ? doc.maintainers
420
+ : [];
421
+ const out = [];
422
+ for (const entry of list) {
423
+ if (typeof entry === 'string' && entry.trim()) {
424
+ out.push(entry.trim());
425
+ }
426
+ else if (entry && typeof entry === 'object' && 'login' in entry) {
427
+ const login = String(entry.login).trim();
428
+ if (login)
429
+ out.push(login);
430
+ }
431
+ }
432
+ return out;
433
+ }
434
+ catch {
435
+ return [];
436
+ }
437
+ }
438
+ //# sourceMappingURL=backlog-adapter.js.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * C6 — Category-scoped calibration (RFC-0008 §10 Amendment 6).
3
+ *
4
+ * Replaces PPA v1.0's scalar calibrationCoefficient with per-category
5
+ * coefficients derived from feedback signals. Categories are
6
+ * PillarContribution labels (product / design / engineering) or
7
+ * arbitrary labels the caller chooses.
8
+ *
9
+ * Cκ_category = clamp([0.7, 1.3], 1.0 + (accepts - escalates) / max(1, total) × 0.3)
10
+ *
11
+ * Per v1.1 note: this adjusts the multiplicative Cκ term, NOT SA-2
12
+ * directly. Per-dimension calibration lands in PPA v1.1 §17.
13
+ */
14
+ import type { SaDimension } from './state/types.js';
15
+ import type { SAFeedbackStore } from './sa-scoring/feedback-store.js';
16
+ export declare const CALIBRATION_MIN = 0.7;
17
+ export declare const CALIBRATION_MAX = 1.3;
18
+ /** Slope of the feedback-driven adjustment. Spec §10 uses 0.3. */
19
+ export declare const CALIBRATION_SLOPE = 0.3;
20
+ export interface CategoryFeedback {
21
+ accepts: number;
22
+ dismisses: number;
23
+ escalates: number;
24
+ overrides?: number;
25
+ }
26
+ export declare function computeCalibrationCoefficient(feedback: CategoryFeedback): number;
27
+ export interface BuildCategoryCoefficientsInput {
28
+ /** Which SA dimension to pull feedback from. Defaults to SA-1. */
29
+ dimension?: SaDimension;
30
+ /** Trailing-window filter. */
31
+ since?: string;
32
+ /** Categories to evaluate; when absent, uses all seen in the window. */
33
+ categories?: readonly string[];
34
+ /** Minimum feedback count per category before returning a coefficient. */
35
+ minSampleSize?: number;
36
+ }
37
+ /**
38
+ * Aggregate feedback rows by category and return `{category: coefficient}`.
39
+ * Categories with fewer than `minSampleSize` samples are omitted — the
40
+ * scalar fallback applies to them.
41
+ */
42
+ export declare function buildCategoryCoefficients(feedback: SAFeedbackStore, input?: BuildCategoryCoefficientsInput): Record<string, number>;
43
+ //# sourceMappingURL=calibration.d.ts.map
@@ -0,0 +1,76 @@
1
+ /**
2
+ * C6 — Category-scoped calibration (RFC-0008 §10 Amendment 6).
3
+ *
4
+ * Replaces PPA v1.0's scalar calibrationCoefficient with per-category
5
+ * coefficients derived from feedback signals. Categories are
6
+ * PillarContribution labels (product / design / engineering) or
7
+ * arbitrary labels the caller chooses.
8
+ *
9
+ * Cκ_category = clamp([0.7, 1.3], 1.0 + (accepts - escalates) / max(1, total) × 0.3)
10
+ *
11
+ * Per v1.1 note: this adjusts the multiplicative Cκ term, NOT SA-2
12
+ * directly. Per-dimension calibration lands in PPA v1.1 §17.
13
+ */
14
+ export const CALIBRATION_MIN = 0.7;
15
+ export const CALIBRATION_MAX = 1.3;
16
+ /** Slope of the feedback-driven adjustment. Spec §10 uses 0.3. */
17
+ export const CALIBRATION_SLOPE = 0.3;
18
+ export function computeCalibrationCoefficient(feedback) {
19
+ const total = feedback.accepts + feedback.dismisses + feedback.escalates;
20
+ if (total === 0)
21
+ return 1.0;
22
+ const delta = (feedback.accepts - feedback.escalates) / Math.max(1, total);
23
+ return clamp(1.0 + delta * CALIBRATION_SLOPE, CALIBRATION_MIN, CALIBRATION_MAX);
24
+ }
25
+ function clamp(value, min, max) {
26
+ return Math.min(max, Math.max(min, value));
27
+ }
28
+ /**
29
+ * Aggregate feedback rows by category and return `{category: coefficient}`.
30
+ * Categories with fewer than `minSampleSize` samples are omitted — the
31
+ * scalar fallback applies to them.
32
+ */
33
+ export function buildCategoryCoefficients(feedback, input = {}) {
34
+ const window = {
35
+ dimension: input.dimension,
36
+ since: input.since,
37
+ };
38
+ const events = feedback.list(window).filter((e) => e.category);
39
+ const buckets = new Map();
40
+ for (const e of events) {
41
+ const key = e.category;
42
+ if (input.categories && !input.categories.includes(key))
43
+ continue;
44
+ const current = buckets.get(key) ?? {
45
+ accepts: 0,
46
+ dismisses: 0,
47
+ escalates: 0,
48
+ overrides: 0,
49
+ };
50
+ switch (e.signal) {
51
+ case 'accept':
52
+ current.accepts++;
53
+ break;
54
+ case 'dismiss':
55
+ current.dismisses++;
56
+ break;
57
+ case 'escalate':
58
+ current.escalates++;
59
+ break;
60
+ case 'override':
61
+ current.overrides = (current.overrides ?? 0) + 1;
62
+ break;
63
+ }
64
+ buckets.set(key, current);
65
+ }
66
+ const minSize = input.minSampleSize ?? 1;
67
+ const result = {};
68
+ for (const [category, f] of buckets) {
69
+ const samples = f.accepts + f.dismisses + f.escalates;
70
+ if (samples < minSize)
71
+ continue;
72
+ result[category] = computeCalibrationCoefficient(f);
73
+ }
74
+ return result;
75
+ }
76
+ //# sourceMappingURL=calibration.js.map
@@ -1,6 +1,30 @@
1
1
  /**
2
2
  * ai-sdlc agents [name] — show agent roster, autonomy levels, performance.
3
+ *
4
+ * AISDLC-78 (AC #10): in addition to the autonomy ledger (which only
5
+ * shows agents that have actually executed at least once), we now also
6
+ * scan the project's .ai-sdlc directory for AgentRole resources and
7
+ * surface declared-but-not-executed agents. Fresh installs that ran
8
+ * `ai-sdlc init` previously saw an empty roster even though
9
+ * `agent-role.yaml` declared `default-agent`; now the user sees the
10
+ * declared roster + a "(declared, not yet executed)" hint.
3
11
  */
4
12
  import { Command } from 'commander';
13
+ interface DeclaredAgent {
14
+ agentName: string;
15
+ currentLevel: number;
16
+ totalTasks: number;
17
+ successCount: number;
18
+ failureCount: number;
19
+ lastTaskAt?: string;
20
+ declaredOnly: true;
21
+ }
22
+ /**
23
+ * Scan the config directory for AgentRole YAML resources and return the
24
+ * declared agent metadata. Errors are swallowed (the autonomy ledger
25
+ * is the primary source of truth — declared agents are a hint).
26
+ */
27
+ export declare function loadDeclaredAgents(configDir: string): DeclaredAgent[];
5
28
  export declare const agentsCommand: Command;
29
+ export {};
6
30
  //# sourceMappingURL=agents.d.ts.map
@@ -1,9 +1,67 @@
1
1
  /**
2
2
  * ai-sdlc agents [name] — show agent roster, autonomy levels, performance.
3
+ *
4
+ * AISDLC-78 (AC #10): in addition to the autonomy ledger (which only
5
+ * shows agents that have actually executed at least once), we now also
6
+ * scan the project's .ai-sdlc directory for AgentRole resources and
7
+ * surface declared-but-not-executed agents. Fresh installs that ran
8
+ * `ai-sdlc init` previously saw an empty roster even though
9
+ * `agent-role.yaml` declared `default-agent`; now the user sees the
10
+ * declared roster + a "(declared, not yet executed)" hint.
3
11
  */
4
12
  import { Command } from 'commander';
13
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
14
+ import { join, resolve } from 'node:path';
15
+ import { parse as parseYaml } from 'yaml';
5
16
  import { Orchestrator } from '../../orchestrator.js';
6
17
  import { formatOutput } from '../formatters/index.js';
18
+ /**
19
+ * Scan the config directory for AgentRole YAML resources and return the
20
+ * declared agent metadata. Errors are swallowed (the autonomy ledger
21
+ * is the primary source of truth — declared agents are a hint).
22
+ */
23
+ export function loadDeclaredAgents(configDir) {
24
+ const dir = resolve(configDir);
25
+ if (!existsSync(dir))
26
+ return [];
27
+ let files;
28
+ try {
29
+ files = readdirSync(dir).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'));
30
+ }
31
+ catch {
32
+ return [];
33
+ }
34
+ const declared = [];
35
+ for (const file of files) {
36
+ try {
37
+ const raw = readFileSync(join(dir, file), 'utf-8');
38
+ const docs = raw
39
+ .split(/^---\s*$/m)
40
+ .map((s) => s.trim())
41
+ .filter(Boolean);
42
+ for (const docText of docs) {
43
+ const doc = parseYaml(docText);
44
+ if (!doc || doc.kind !== 'AgentRole')
45
+ continue;
46
+ const name = doc.metadata?.name;
47
+ if (!name)
48
+ continue;
49
+ declared.push({
50
+ agentName: name,
51
+ currentLevel: 0,
52
+ totalTasks: 0,
53
+ successCount: 0,
54
+ failureCount: 0,
55
+ declaredOnly: true,
56
+ });
57
+ }
58
+ }
59
+ catch {
60
+ // ignore malformed YAML — surfaced by `ai-sdlc validate`
61
+ }
62
+ }
63
+ return declared;
64
+ }
7
65
  export const agentsCommand = new Command('agents')
8
66
  .description('Show agent roster with autonomy levels and performance')
9
67
  .argument('[name]', 'Filter by agent name')
@@ -11,12 +69,19 @@ export const agentsCommand = new Command('agents')
11
69
  .action(async (name, opts, cmd) => {
12
70
  const globalOpts = cmd.parent?.opts() ?? {};
13
71
  const format = globalOpts.format ?? 'table';
72
+ const configDir = globalOpts.config ?? '.ai-sdlc';
14
73
  const orchestrator = new Orchestrator({
15
74
  configDir: globalOpts.config,
16
75
  statePath: opts.state,
17
76
  });
18
77
  try {
19
- let agents = await orchestrator.agents();
78
+ const ledger = await orchestrator.agents();
79
+ const executedNames = new Set(ledger.map((a) => a.agentName));
80
+ const declared = loadDeclaredAgents(configDir).filter((d) => !executedNames.has(d.agentName));
81
+ let agents = [
82
+ ...ledger.map((a) => ({ ...a, declaredOnly: false })),
83
+ ...declared.map((d) => ({ ...d })),
84
+ ];
20
85
  if (name) {
21
86
  agents = agents.filter((a) => a.agentName === name);
22
87
  }