specky-sdd 3.10.2 → 3.11.1

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 (322) hide show
  1. package/.apm/agents/specky-design-architect.agent.md +8 -28
  2. package/.apm/agents/specky-implementer.agent.md +15 -27
  3. package/.apm/agents/specky-onboarding.agent.md +8 -49
  4. package/.apm/agents/specky-orchestrator.agent.md +12 -37
  5. package/.apm/agents/specky-quality-reviewer.agent.md +9 -30
  6. package/.apm/agents/specky-release-engineer.agent.md +8 -30
  7. package/.apm/agents/specky-requirements-engineer.agent.md +1 -1
  8. package/.apm/agents/specky-research-analyst.agent.md +1 -1
  9. package/.apm/agents/specky-sdd-clarify.agent.md +1 -1
  10. package/.apm/agents/specky-sdd-init.agent.md +8 -19
  11. package/.apm/agents/specky-spec-engineer.agent.md +8 -30
  12. package/.apm/agents/specky-task-planner.agent.md +7 -26
  13. package/.apm/agents/specky-test-verifier.agent.md +7 -17
  14. package/.apm/hooks/scripts/specky-artifact-validator.sh +14 -10
  15. package/.apm/hooks/scripts/specky-auto-checkpoint.sh +9 -4
  16. package/.apm/hooks/scripts/specky-branch-validator.sh +29 -116
  17. package/.apm/hooks/scripts/specky-cognitive-debt-alert.sh +6 -3
  18. package/.apm/hooks/scripts/specky-contract-context.bash +24 -0
  19. package/.apm/hooks/scripts/specky-contract-context.mjs +219 -0
  20. package/.apm/hooks/scripts/specky-drift-monitor.sh +6 -3
  21. package/.apm/hooks/scripts/specky-ears-validator.sh +5 -2
  22. package/.apm/hooks/scripts/specky-lgtm-gate.sh +12 -10
  23. package/.apm/hooks/scripts/specky-metrics-dashboard.sh +5 -2
  24. package/.apm/hooks/scripts/specky-phase-gate.sh +18 -9
  25. package/.apm/hooks/scripts/specky-pipeline-guard.sh +19 -26
  26. package/.apm/hooks/scripts/specky-release-gate.sh +38 -46
  27. package/.apm/hooks/scripts/specky-security-scan.sh +53 -36
  28. package/.apm/hooks/scripts/specky-session-banner.sh +22 -74
  29. package/.apm/hooks/scripts/specky-spec-quality.sh +7 -4
  30. package/.apm/hooks/scripts/specky-spec-sync.sh +12 -11
  31. package/.apm/hooks/scripts/specky-task-tracer.sh +5 -2
  32. package/.apm/hooks/specky-run.sh +3 -0
  33. package/.apm/instructions/claude-instructions.instructions.md +3 -3
  34. package/.apm/instructions/copilot-instructions.instructions.md +3 -3
  35. package/.apm/instructions/cursor-instructions.instructions.md +3 -3
  36. package/.apm/instructions/opencode-instructions.instructions.md +2 -2
  37. package/.apm/prompts/specky-brownfield.prompt.md +8 -5
  38. package/.apm/prompts/specky-greenfield.prompt.md +7 -5
  39. package/.apm/prompts/specky-implement.prompt.md +1 -1
  40. package/.apm/prompts/specky-orchestrate.prompt.md +1 -1
  41. package/.apm/skills/specky-design-architect/SKILL.md +39 -16
  42. package/.apm/skills/specky-implementer/SKILL.md +46 -158
  43. package/.apm/skills/specky-onboarding/SKILL.md +50 -156
  44. package/.apm/skills/specky-orchestrator/SKILL.md +28 -26
  45. package/.apm/skills/specky-quality-reviewer/SKILL.md +38 -23
  46. package/.apm/skills/specky-release-engineer/SKILL.md +38 -270
  47. package/.apm/skills/specky-research-analyst/SKILL.md +1 -1
  48. package/.apm/skills/specky-sdd-init/SKILL.md +61 -23
  49. package/.apm/skills/specky-sdd-pipeline/SKILL.md +36 -216
  50. package/.apm/skills/specky-spec-engineer/SKILL.md +40 -17
  51. package/.apm/skills/specky-task-planner/SKILL.md +31 -12
  52. package/.apm/skills/specky-test-verifier/SKILL.md +23 -202
  53. package/CHANGELOG.md +39 -0
  54. package/README.md +49 -47
  55. package/SECURITY.md +5 -1
  56. package/apm-policy.yml +2 -0
  57. package/apm.lock.yaml +55 -53
  58. package/apm.yml +3 -3
  59. package/config.yml +32 -53
  60. package/dist/cli/commands/doctor.d.ts.map +1 -1
  61. package/dist/cli/commands/doctor.js +106 -20
  62. package/dist/cli/commands/doctor.js.map +1 -1
  63. package/dist/cli/commands/init.d.ts +2 -0
  64. package/dist/cli/commands/init.d.ts.map +1 -1
  65. package/dist/cli/commands/init.js +105 -7
  66. package/dist/cli/commands/init.js.map +1 -1
  67. package/dist/cli/commands/migrate-contracts.d.ts +15 -0
  68. package/dist/cli/commands/migrate-contracts.d.ts.map +1 -0
  69. package/dist/cli/commands/migrate-contracts.js +311 -0
  70. package/dist/cli/commands/migrate-contracts.js.map +1 -0
  71. package/dist/cli/commands/status.d.ts.map +1 -1
  72. package/dist/cli/commands/status.js +63 -48
  73. package/dist/cli/commands/status.js.map +1 -1
  74. package/dist/cli/index.js +82 -27
  75. package/dist/cli/index.js.map +1 -1
  76. package/dist/cli/lib/apm/policy.d.ts +1 -0
  77. package/dist/cli/lib/apm/policy.d.ts.map +1 -1
  78. package/dist/cli/lib/apm/policy.js +15 -0
  79. package/dist/cli/lib/apm/policy.js.map +1 -1
  80. package/dist/cli/lib/asset-copier.d.ts +1 -0
  81. package/dist/cli/lib/asset-copier.d.ts.map +1 -1
  82. package/dist/cli/lib/asset-copier.js +4 -4
  83. package/dist/cli/lib/asset-copier.js.map +1 -1
  84. package/dist/cli/lib/cursor-plugin-writer.d.ts.map +1 -1
  85. package/dist/cli/lib/cursor-plugin-writer.js +34 -9
  86. package/dist/cli/lib/cursor-plugin-writer.js.map +1 -1
  87. package/dist/cli/lib/harness/compilers/claude.js +3 -3
  88. package/dist/cli/lib/harness/compilers/claude.js.map +1 -1
  89. package/dist/cli/lib/harness/compilers/common.d.ts +10 -1
  90. package/dist/cli/lib/harness/compilers/common.d.ts.map +1 -1
  91. package/dist/cli/lib/harness/compilers/common.js +71 -8
  92. package/dist/cli/lib/harness/compilers/common.js.map +1 -1
  93. package/dist/cli/lib/harness/compilers/copilot.js +3 -3
  94. package/dist/cli/lib/harness/compilers/copilot.js.map +1 -1
  95. package/dist/cli/lib/harness/compilers/cursor.js +3 -3
  96. package/dist/cli/lib/harness/compilers/cursor.js.map +1 -1
  97. package/dist/cli/lib/harness/compilers/opencode.js +3 -3
  98. package/dist/cli/lib/harness/compilers/opencode.js.map +1 -1
  99. package/dist/cli/lib/harness/tool-map.d.ts +8 -1
  100. package/dist/cli/lib/harness/tool-map.d.ts.map +1 -1
  101. package/dist/cli/lib/harness/tool-map.js +74 -0
  102. package/dist/cli/lib/harness/tool-map.js.map +1 -1
  103. package/dist/cli/lib/harness/types.d.ts +11 -1
  104. package/dist/cli/lib/harness/types.d.ts.map +1 -1
  105. package/dist/cli/lib/mcp-writer.d.ts +2 -1
  106. package/dist/cli/lib/mcp-writer.d.ts.map +1 -1
  107. package/dist/cli/lib/mcp-writer.js +13 -5
  108. package/dist/cli/lib/mcp-writer.js.map +1 -1
  109. package/dist/cli/lib/settings-merger.d.ts +15 -0
  110. package/dist/cli/lib/settings-merger.d.ts.map +1 -1
  111. package/dist/cli/lib/settings-merger.js +66 -1
  112. package/dist/cli/lib/settings-merger.js.map +1 -1
  113. package/dist/config.d.ts +110 -42
  114. package/dist/config.d.ts.map +1 -1
  115. package/dist/config.js +129 -140
  116. package/dist/config.js.map +1 -1
  117. package/dist/constants.d.ts +1 -1
  118. package/dist/constants.d.ts.map +1 -1
  119. package/dist/contracts/pipeline-profiles.d.ts +61 -0
  120. package/dist/contracts/pipeline-profiles.d.ts.map +1 -0
  121. package/dist/contracts/pipeline-profiles.js +303 -0
  122. package/dist/contracts/pipeline-profiles.js.map +1 -0
  123. package/dist/contracts/tool-contracts.d.ts +449 -0
  124. package/dist/contracts/tool-contracts.d.ts.map +1 -0
  125. package/dist/contracts/tool-contracts.js +83 -0
  126. package/dist/contracts/tool-contracts.js.map +1 -0
  127. package/dist/contracts/use-case.d.ts +736 -0
  128. package/dist/contracts/use-case.d.ts.map +1 -0
  129. package/dist/contracts/use-case.js +352 -0
  130. package/dist/contracts/use-case.js.map +1 -0
  131. package/dist/index.js +7 -4
  132. package/dist/index.js.map +1 -1
  133. package/dist/schemas/common.d.ts +3 -3
  134. package/dist/schemas/common.d.ts.map +1 -1
  135. package/dist/schemas/common.js +1 -4
  136. package/dist/schemas/common.js.map +1 -1
  137. package/dist/schemas/context.d.ts +2 -2
  138. package/dist/schemas/environment.d.ts +6 -17
  139. package/dist/schemas/environment.d.ts.map +1 -1
  140. package/dist/schemas/environment.js +1 -28
  141. package/dist/schemas/environment.js.map +1 -1
  142. package/dist/schemas/feature-content.d.ts +116 -0
  143. package/dist/schemas/feature-content.d.ts.map +1 -0
  144. package/dist/schemas/feature-content.js +65 -0
  145. package/dist/schemas/feature-content.js.map +1 -0
  146. package/dist/schemas/infrastructure.d.ts +6 -28
  147. package/dist/schemas/infrastructure.d.ts.map +1 -1
  148. package/dist/schemas/infrastructure.js +1 -30
  149. package/dist/schemas/infrastructure.js.map +1 -1
  150. package/dist/schemas/input.d.ts +412 -8
  151. package/dist/schemas/input.d.ts.map +1 -1
  152. package/dist/schemas/input.js +6 -7
  153. package/dist/schemas/input.js.map +1 -1
  154. package/dist/schemas/integration.d.ts +22 -28
  155. package/dist/schemas/integration.d.ts.map +1 -1
  156. package/dist/schemas/integration.js +11 -47
  157. package/dist/schemas/integration.js.map +1 -1
  158. package/dist/schemas/metrics.d.ts +3 -2
  159. package/dist/schemas/metrics.d.ts.map +1 -1
  160. package/dist/schemas/metrics.js +2 -1
  161. package/dist/schemas/metrics.js.map +1 -1
  162. package/dist/schemas/pbt.d.ts +2 -7
  163. package/dist/schemas/pbt.d.ts.map +1 -1
  164. package/dist/schemas/pbt.js +1 -9
  165. package/dist/schemas/pbt.js.map +1 -1
  166. package/dist/schemas/pipeline.d.ts +282 -35
  167. package/dist/schemas/pipeline.d.ts.map +1 -1
  168. package/dist/schemas/pipeline.js +29 -15
  169. package/dist/schemas/pipeline.js.map +1 -1
  170. package/dist/schemas/quality.d.ts +15 -19
  171. package/dist/schemas/quality.d.ts.map +1 -1
  172. package/dist/schemas/quality.js +6 -3
  173. package/dist/schemas/quality.js.map +1 -1
  174. package/dist/schemas/routing.d.ts +2 -2
  175. package/dist/schemas/routing.d.ts.map +1 -1
  176. package/dist/schemas/routing.js +2 -4
  177. package/dist/schemas/routing.js.map +1 -1
  178. package/dist/schemas/testing.d.ts +4 -13
  179. package/dist/schemas/testing.d.ts.map +1 -1
  180. package/dist/schemas/testing.js +2 -10
  181. package/dist/schemas/testing.js.map +1 -1
  182. package/dist/schemas/transcript.d.ts +648 -10
  183. package/dist/schemas/transcript.d.ts.map +1 -1
  184. package/dist/schemas/transcript.js +25 -12
  185. package/dist/schemas/transcript.js.map +1 -1
  186. package/dist/schemas/utility.d.ts +28 -15
  187. package/dist/schemas/utility.d.ts.map +1 -1
  188. package/dist/schemas/utility.js +23 -14
  189. package/dist/schemas/utility.js.map +1 -1
  190. package/dist/schemas/visualization.d.ts +66 -15
  191. package/dist/schemas/visualization.d.ts.map +1 -1
  192. package/dist/schemas/visualization.js +36 -12
  193. package/dist/schemas/visualization.js.map +1 -1
  194. package/dist/services/codebase-scanner.d.ts +1 -1
  195. package/dist/services/codebase-scanner.d.ts.map +1 -1
  196. package/dist/services/codebase-scanner.js +2 -2
  197. package/dist/services/codebase-scanner.js.map +1 -1
  198. package/dist/services/compliance-engine.d.ts +3 -2
  199. package/dist/services/compliance-engine.d.ts.map +1 -1
  200. package/dist/services/compliance-engine.js +34 -21
  201. package/dist/services/compliance-engine.js.map +1 -1
  202. package/dist/services/diagram-generator.d.ts +4 -96
  203. package/dist/services/diagram-generator.d.ts.map +1 -1
  204. package/dist/services/diagram-generator.js +18 -1051
  205. package/dist/services/diagram-generator.js.map +1 -1
  206. package/dist/services/doc-generator.d.ts +19 -52
  207. package/dist/services/doc-generator.d.ts.map +1 -1
  208. package/dist/services/doc-generator.js +389 -524
  209. package/dist/services/doc-generator.js.map +1 -1
  210. package/dist/services/execution-context.d.ts +32 -0
  211. package/dist/services/execution-context.d.ts.map +1 -0
  212. package/dist/services/execution-context.js +120 -0
  213. package/dist/services/execution-context.js.map +1 -0
  214. package/dist/services/file-manager.d.ts +16 -4
  215. package/dist/services/file-manager.d.ts.map +1 -1
  216. package/dist/services/file-manager.js +81 -5
  217. package/dist/services/file-manager.js.map +1 -1
  218. package/dist/services/iac-generator.d.ts +9 -10
  219. package/dist/services/iac-generator.d.ts.map +1 -1
  220. package/dist/services/iac-generator.js +36 -99
  221. package/dist/services/iac-generator.js.map +1 -1
  222. package/dist/services/methodology.js +1 -1
  223. package/dist/services/methodology.js.map +1 -1
  224. package/dist/services/metrics-generator.d.ts +1 -1
  225. package/dist/services/metrics-generator.d.ts.map +1 -1
  226. package/dist/services/metrics-generator.js +2 -2
  227. package/dist/services/metrics-generator.js.map +1 -1
  228. package/dist/services/pbt-generator.d.ts +10 -31
  229. package/dist/services/pbt-generator.d.ts.map +1 -1
  230. package/dist/services/pbt-generator.js +43 -515
  231. package/dist/services/pbt-generator.js.map +1 -1
  232. package/dist/services/state-machine.d.ts +22 -16
  233. package/dist/services/state-machine.d.ts.map +1 -1
  234. package/dist/services/state-machine.js +176 -179
  235. package/dist/services/state-machine.js.map +1 -1
  236. package/dist/services/template-engine.d.ts +11 -5
  237. package/dist/services/template-engine.d.ts.map +1 -1
  238. package/dist/services/template-engine.js +48 -21
  239. package/dist/services/template-engine.js.map +1 -1
  240. package/dist/services/test-generator.d.ts +11 -7
  241. package/dist/services/test-generator.d.ts.map +1 -1
  242. package/dist/services/test-generator.js +45 -54
  243. package/dist/services/test-generator.js.map +1 -1
  244. package/dist/templates/design.md +6 -0
  245. package/dist/tools/analysis.d.ts.map +1 -1
  246. package/dist/tools/analysis.js +8 -9
  247. package/dist/tools/analysis.js.map +1 -1
  248. package/dist/tools/checkpoint.d.ts.map +1 -1
  249. package/dist/tools/checkpoint.js +43 -46
  250. package/dist/tools/checkpoint.js.map +1 -1
  251. package/dist/tools/context.d.ts.map +1 -1
  252. package/dist/tools/context.js +5 -7
  253. package/dist/tools/context.js.map +1 -1
  254. package/dist/tools/documentation.d.ts +1 -4
  255. package/dist/tools/documentation.d.ts.map +1 -1
  256. package/dist/tools/documentation.js +112 -193
  257. package/dist/tools/documentation.js.map +1 -1
  258. package/dist/tools/environment.d.ts +1 -1
  259. package/dist/tools/environment.d.ts.map +1 -1
  260. package/dist/tools/environment.js +58 -99
  261. package/dist/tools/environment.js.map +1 -1
  262. package/dist/tools/infrastructure.d.ts.map +1 -1
  263. package/dist/tools/infrastructure.js +36 -38
  264. package/dist/tools/infrastructure.js.map +1 -1
  265. package/dist/tools/input.d.ts.map +1 -1
  266. package/dist/tools/input.js +18 -11
  267. package/dist/tools/input.js.map +1 -1
  268. package/dist/tools/integration.d.ts.map +1 -1
  269. package/dist/tools/integration.js +64 -65
  270. package/dist/tools/integration.js.map +1 -1
  271. package/dist/tools/metrics.d.ts.map +1 -1
  272. package/dist/tools/metrics.js +18 -30
  273. package/dist/tools/metrics.js.map +1 -1
  274. package/dist/tools/pbt.d.ts.map +1 -1
  275. package/dist/tools/pbt.js +10 -12
  276. package/dist/tools/pbt.js.map +1 -1
  277. package/dist/tools/pipeline.d.ts.map +1 -1
  278. package/dist/tools/pipeline.js +156 -274
  279. package/dist/tools/pipeline.js.map +1 -1
  280. package/dist/tools/quality.d.ts.map +1 -1
  281. package/dist/tools/quality.js +58 -45
  282. package/dist/tools/quality.js.map +1 -1
  283. package/dist/tools/response-builder.d.ts.map +1 -1
  284. package/dist/tools/response-builder.js +12 -10
  285. package/dist/tools/response-builder.js.map +1 -1
  286. package/dist/tools/testing.d.ts.map +1 -1
  287. package/dist/tools/testing.js +28 -29
  288. package/dist/tools/testing.js.map +1 -1
  289. package/dist/tools/tool-enforcement.d.ts +2 -0
  290. package/dist/tools/tool-enforcement.d.ts.map +1 -1
  291. package/dist/tools/tool-enforcement.js +84 -59
  292. package/dist/tools/tool-enforcement.js.map +1 -1
  293. package/dist/tools/transcript.d.ts.map +1 -1
  294. package/dist/tools/transcript.js +304 -374
  295. package/dist/tools/transcript.js.map +1 -1
  296. package/dist/tools/turnkey.d.ts +1 -31
  297. package/dist/tools/turnkey.d.ts.map +1 -1
  298. package/dist/tools/turnkey.js +104 -488
  299. package/dist/tools/turnkey.js.map +1 -1
  300. package/dist/tools/utility.d.ts.map +1 -1
  301. package/dist/tools/utility.js +115 -134
  302. package/dist/tools/utility.js.map +1 -1
  303. package/dist/tools/visualization.d.ts +1 -25
  304. package/dist/tools/visualization.d.ts.map +1 -1
  305. package/dist/tools/visualization.js +187 -562
  306. package/dist/tools/visualization.js.map +1 -1
  307. package/dist/types.d.ts +21 -11
  308. package/dist/types.d.ts.map +1 -1
  309. package/dist/utils/artifact-metadata.d.ts +8 -0
  310. package/dist/utils/artifact-metadata.d.ts.map +1 -0
  311. package/dist/utils/artifact-metadata.js +10 -0
  312. package/dist/utils/artifact-metadata.js.map +1 -0
  313. package/dist/utils/requirement-partition.d.ts +8 -0
  314. package/dist/utils/requirement-partition.d.ts.map +1 -0
  315. package/dist/utils/requirement-partition.js +15 -0
  316. package/dist/utils/requirement-partition.js.map +1 -0
  317. package/package.json +11 -2
  318. package/templates/design.md +6 -0
  319. package/dist/utils/design-stubs.d.ts +0 -32
  320. package/dist/utils/design-stubs.d.ts.map +0 -1
  321. package/dist/utils/design-stubs.js +0 -103
  322. package/dist/utils/design-stubs.js.map +0 -1
@@ -12,11 +12,70 @@ import { importTranscriptInputSchema, autoPipelineInputSchema, batchTranscriptsI
12
12
  import { enrichResponse } from "./response-builder.js";
13
13
  import { FeaturePackageGenerator } from "../services/feature-package-generator.js";
14
14
  import { AnalysisEngine } from "../services/analysis-engine.js";
15
- import { deriveDesignStubs } from "../utils/design-stubs.js";
16
- function syntheticSpecFromRequirements(requirements) {
17
- return requirements
18
- .map((r) => `### ${r.id}: (ubiquitous)\n\n${r.text}\n`)
19
- .join("\n");
15
+ import { requireExecutionContext } from "../services/execution-context.js";
16
+ import { renderWorkloadDesign } from "../contracts/pipeline-profiles.js";
17
+ import { artifactMetadata } from "../utils/artifact-metadata.js";
18
+ function transcriptDiscoveryContext(analysis) {
19
+ const lines = [
20
+ "## Discovery Context",
21
+ "",
22
+ `- **Participants:** ${analysis.participants.join(", ") || "Not identified in source"}`,
23
+ `- **Topics:** ${analysis.topics.map((topic) => topic.name).join(", ") || "No named topics extracted"}`,
24
+ `- **Decisions captured:** ${analysis.decisions.length}`,
25
+ `- **Constraints captured:** ${analysis.constraints_mentioned.length}`,
26
+ `- **Open questions:** ${analysis.open_questions.length}`,
27
+ "",
28
+ "This context was extracted from the referenced transcript and must be reviewed against the source before approval.",
29
+ "",
30
+ "---",
31
+ "",
32
+ ];
33
+ return lines.join("\n");
34
+ }
35
+ function validateExplicitFeatureContent(analysis, requirements, tasks, earsValidator) {
36
+ const source = analysis.full_text.toLowerCase();
37
+ const requirementIds = new Set();
38
+ for (const requirement of requirements) {
39
+ if (requirementIds.has(requirement.id))
40
+ throw new Error(`Duplicate requirement ID ${requirement.id}.`);
41
+ requirementIds.add(requirement.id);
42
+ if (!source.includes(requirement.source_quote.toLowerCase())) {
43
+ throw new Error(`Requirement ${requirement.id} source_quote is not present in the transcript.`);
44
+ }
45
+ const validation = earsValidator.validate(requirement.text);
46
+ if (!validation.valid) {
47
+ throw new Error(`Requirement ${requirement.id} is not valid EARS: ${(validation.issues ?? []).join("; ")}.`);
48
+ }
49
+ if (requirement.acceptance_criteria.length === 0) {
50
+ throw new Error(`Requirement ${requirement.id} requires acceptance criteria.`);
51
+ }
52
+ }
53
+ const taskIds = new Set(tasks.map((task) => task.id));
54
+ for (const task of tasks) {
55
+ const unknownRequirements = task.traces_to.filter((id) => !requirementIds.has(id));
56
+ const unknownDependencies = task.dependencies.filter((id) => !taskIds.has(id));
57
+ if (unknownRequirements.length > 0) {
58
+ throw new Error(`Task ${task.id} traces to unknown requirements: ${unknownRequirements.join(", ")}.`);
59
+ }
60
+ if (unknownDependencies.length > 0) {
61
+ throw new Error(`Task ${task.id} depends on unknown tasks: ${unknownDependencies.join(", ")}.`);
62
+ }
63
+ }
64
+ }
65
+ function replaceDesignEvidence(content, diagrams, adrs) {
66
+ const diagramContent = diagrams.map((diagram) => `### ${diagram.title}\n\n\`\`\`mermaid\n${diagram.code}\n\`\`\``).join("\n\n---\n\n");
67
+ const adrContent = adrs.map((adr, index) => [
68
+ `### ADR-${String(index + 1).padStart(3, "0")}: ${adr.title}`,
69
+ "",
70
+ `**Decision:** ${adr.decision}`,
71
+ "",
72
+ `**Rationale:** ${adr.rationale}`,
73
+ "",
74
+ `**Consequences:** ${adr.consequences}`,
75
+ ].join("\n")).join("\n\n---\n\n");
76
+ return content
77
+ .replace(/## 5\. System Diagrams\n\n[\s\S]*?(?=\n---\n\n## 6)/, `## 5. System Diagrams\n\n${diagramContent}`)
78
+ .replace(/## 10\. Architecture Decision Records\n\n[\s\S]*?(?=\n---\n\n## 11)/, `## 10. Architecture Decision Records\n\n${adrContent}`);
20
79
  }
21
80
  export function registerTranscriptTools(server, fileManager, stateMachine, templateEngine, earsValidator, transcriptParser) {
22
81
  const featurePackageGenerator = new FeaturePackageGenerator(fileManager);
@@ -97,7 +156,7 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
97
156
  // ─── sdd_auto_pipeline ───
98
157
  server.registerTool("sdd_auto_pipeline", {
99
158
  title: "Auto Pipeline from Transcript",
100
- description: "FULLY AUTOMATED: Reads a meeting transcript, extracts requirements, and runs the complete SDD pipeline in one call. Creates CONSTITUTION.md, SPECIFICATION.md, DESIGN.md, TASKS.md, and ANALYSIS.md from a single transcript file. Supports VTT (Teams), SRT (Zoom), TXT, and MD formats.",
159
+ description: "Validates explicit Constitution, source-quoted EARS requirements, complete architecture, tasks, and gates against one transcript before atomically orchestrating the full contracted artifact set.",
101
160
  inputSchema: autoPipelineInputSchema,
102
161
  annotations: {
103
162
  readOnlyHint: false,
@@ -105,7 +164,7 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
105
164
  idempotentHint: false,
106
165
  openWorldHint: false,
107
166
  },
108
- }, async ({ file_path, raw_text, project_name, format, spec_dir, principles, force }) => {
167
+ }, async ({ file_path, raw_text, project_name, feature_number, constitution, requirements, architecture, tasks, pre_impl_gates, format, spec_dir, force }) => {
109
168
  try {
110
169
  // ── Step 1: Parse transcript ──
111
170
  console.error(`[specky] Auto-pipeline: parsing transcript...`);
@@ -120,47 +179,60 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
120
179
  else {
121
180
  throw new Error("Provide either file_path or raw_text.\n→ Fix: Set file_path to transcript location, or paste text in raw_text.");
122
181
  }
123
- const featureDir = join(spec_dir, `001-${project_name}`);
182
+ const featureDir = join(spec_dir, `${feature_number}-${project_name}`);
183
+ const contract = requireExecutionContext("sdd_auto_pipeline").requestedContract;
184
+ if (contract.execution_mode !== "full") {
185
+ throw new Error(`sdd_auto_pipeline requires a full execution-mode contract; received ${contract.id}.`);
186
+ }
187
+ validateExplicitFeatureContent(analysis, requirements, tasks, earsValidator);
188
+ const workloadDesign = renderWorkloadDesign(contract.workload, architecture.workload_design);
189
+ if (contract.workload === "api" && architecture.api_contracts.length === 0) {
190
+ throw new Error("API transcript orchestration requires at least one explicit API contract.");
191
+ }
192
+ const existing = (await fileManager.listFeatures(spec_dir)).find((feature) => feature.number === feature_number);
193
+ if (existing)
194
+ throw new Error(`Feature number ${feature_number} is already assigned to ${existing.directory}.`);
124
195
  const filesCreated = [];
125
196
  // ── Step 2: Create CONSTITUTION.md ──
126
197
  console.error(`[specky] Auto-pipeline: writing CONSTITUTION.md...`);
127
198
  await fileManager.ensureSpecDir(spec_dir);
128
- const autoConstraints = analysis.constraints_mentioned.length > 0
129
- ? analysis.constraints_mentioned
130
- : ["Derived from meeting transcript — review for accuracy"];
131
- const autoPrinciples = principles || [
132
- "Requirements traced to meeting decisions",
133
- "EARS notation for all requirements",
134
- "Traceability from transcript to implementation",
135
- ];
136
199
  const constitutionContent = await templateEngine.renderWithFrontmatter("constitution", {
200
+ ...artifactMetadata({ version: "1.0.0", author: "sdd_auto_pipeline", status: "Active" }),
137
201
  title: `${project_name} — Constitution`,
138
- feature_id: `001-${project_name}`,
202
+ feature_id: `${feature_number}-${project_name}`,
139
203
  project_name,
140
- author: `SDD Pipeline (from transcript: ${analysis.participants.join(", ")})`,
141
- principles: autoPrinciples,
142
- constraints: autoConstraints,
143
- description: `Project charter auto-generated from meeting transcript. ${analysis.topics.length} topics discussed by ${analysis.participants.length} participants.`,
144
- license: "MIT",
145
- scope_in: analysis.topics.map((t) => t.name).join(", "),
146
- scope_out: "Items not discussed in the meeting",
204
+ author: constitution.author,
205
+ principles: constitution.principles,
206
+ constraints: constitution.constraints,
207
+ description: constitution.description,
208
+ license: constitution.license,
209
+ scope_in: constitution.scope_in,
210
+ scope_out: constitution.scope_out,
147
211
  });
148
212
  await fileManager.writeSpecFile(featureDir, "CONSTITUTION.md", constitutionContent, force);
149
213
  filesCreated.push("CONSTITUTION.md");
150
214
  // Initialize state and advance through phases properly
151
- const state = stateMachine.createDefaultState(project_name);
152
- state.features = [featureDir];
215
+ const state = stateMachine.createFeatureState({
216
+ projectName: project_name,
217
+ feature: { number: feature_number, name: project_name, directory: featureDir },
218
+ contract,
219
+ });
153
220
  state.phases[Phase.Init] = {
154
221
  status: "completed",
155
222
  started_at: new Date().toISOString(),
156
223
  completed_at: new Date().toISOString(),
157
224
  };
158
- await stateMachine.saveState(spec_dir, state);
225
+ await stateMachine.saveState(featureDir, state);
159
226
  // Advance Init → Discover
160
- await stateMachine.advancePhase(spec_dir, "001");
161
- // ── Step 3: Generate EARS requirements from transcript ──
162
- console.error(`[specky] Auto-pipeline: generating EARS requirements...`);
163
- const earsRequirements = generateEarsRequirements(analysis, earsValidator);
227
+ await stateMachine.advancePhase(featureDir);
228
+ const earsRequirements = requirements.map((requirement) => ({
229
+ id: requirement.id,
230
+ title: requirement.title,
231
+ pattern: requirement.ears_pattern,
232
+ text: requirement.text,
233
+ acceptance_criteria: requirement.acceptance_criteria,
234
+ source: requirement.source_quote,
235
+ }));
164
236
  // ── Step 4: Write SPECIFICATION.md ──
165
237
  console.error(`[specky] Auto-pipeline: writing SPECIFICATION.md...`);
166
238
  const reqSections = earsRequirements
@@ -184,9 +256,11 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
184
256
  .map((req) => `| ${req.id} | ${req.text.slice(0, 60)}... | Acceptance test |`)
185
257
  .join("\n");
186
258
  const specContent = await templateEngine.renderWithFrontmatter("specification", {
259
+ ...artifactMetadata({ version: "1.0.0", author: "sdd_auto_pipeline", status: "Draft" }),
187
260
  title: `${project_name} — Specification`,
188
- feature_id: `001-${project_name}`,
261
+ feature_id: `${feature_number}-${project_name}`,
189
262
  project_name,
263
+ discovery_context: transcriptDiscoveryContext(analysis),
190
264
  requirements_core: reqSections,
191
265
  requirements_functional: "",
192
266
  requirements_nonfunctional: "",
@@ -199,57 +273,66 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
199
273
  await fileManager.writeSpecFile(featureDir, "SPECIFICATION.md", specContent, force);
200
274
  filesCreated.push("SPECIFICATION.md");
201
275
  // Advance Discover → Specify → Clarify
202
- await stateMachine.recordPhaseComplete(spec_dir, Phase.Discover);
203
- await stateMachine.advancePhase(spec_dir, "001"); // Discover → Specify
204
- await stateMachine.recordPhaseComplete(spec_dir, Phase.Specify);
205
- await stateMachine.advancePhase(spec_dir, "001"); // Specify → Clarify
206
- await stateMachine.recordPhaseComplete(spec_dir, Phase.Clarify);
276
+ await stateMachine.recordPhaseComplete(featureDir, Phase.Discover);
277
+ await stateMachine.advancePhase(featureDir); // Discover → Specify
278
+ await stateMachine.recordPhaseComplete(featureDir, Phase.Specify);
279
+ await stateMachine.advancePhase(featureDir); // Specify → Clarify
280
+ await stateMachine.recordPhaseComplete(featureDir, Phase.Clarify);
207
281
  // ── Step 5: Write DESIGN.md ──
208
282
  console.error(`[specky] Auto-pipeline: writing DESIGN.md...`);
209
- const topicList = analysis.topics.map((t) => t.name);
210
- const designStubs = deriveDesignStubs(syntheticSpecFromRequirements(earsRequirements));
211
- const designContent = await templateEngine.renderWithFrontmatter("design", {
283
+ const apiContent = architecture.api_contracts.length > 0
284
+ ? architecture.api_contracts.map((api) => [
285
+ `### ${api.method} ${api.endpoint}`,
286
+ "",
287
+ api.description,
288
+ "",
289
+ `**Request:** ${api.request}`,
290
+ "",
291
+ `**Response:** ${api.response}`,
292
+ ].join("\n")).join("\n\n---\n\n")
293
+ : "No network API is exposed by this workload contract.";
294
+ let designContent = await templateEngine.renderWithFrontmatter("design", {
295
+ ...artifactMetadata({ version: "1.0.0", author: "sdd_auto_pipeline", status: "Draft" }),
212
296
  title: `${project_name} — Design`,
213
- feature_id: `001-${project_name}`,
297
+ feature_id: `${feature_number}-${project_name}`,
214
298
  project_name,
215
- architecture_overview: generateArchitectureOverview(analysis, earsRequirements),
216
- diagrams: topicList,
217
- adrs: analysis.decisions.length > 0
218
- ? analysis.decisions.map((d, i) => `ADR-${String(i + 1).padStart(3, "0")}: ${d.slice(0, 80)}`)
219
- : ["No decisions recorded in transcript"],
220
- api_contracts: designStubs.api_contracts_stub,
221
- data_models: designStubs.data_models,
222
- error_handling: designStubs.error_handling,
223
- component_design: designStubs.component_design,
224
- security_architecture: designStubs.security_architecture,
225
- infrastructure: designStubs.infrastructure,
226
- cross_cutting: designStubs.cross_cutting,
227
- requirement_references: designStubs.requirement_references,
299
+ architecture_overview: architecture.architecture_overview,
300
+ system_context: architecture.system_context,
301
+ container_architecture: architecture.container_architecture,
302
+ diagrams: architecture.mermaid_diagrams.map((diagram) => diagram.title),
303
+ adrs: architecture.adrs.map((adr) => adr.title),
304
+ api_contracts: apiContent,
305
+ data_models: architecture.data_models,
306
+ error_handling: architecture.error_handling,
307
+ component_design: architecture.component_design,
308
+ code_level_design: architecture.code_level_design,
309
+ security_architecture: architecture.security_architecture,
310
+ infrastructure: architecture.infrastructure,
311
+ cross_cutting: architecture.cross_cutting,
312
+ workload_design: workloadDesign,
313
+ requirement_references: requirements.map((requirement) => `- ${requirement.id}`).join("\n"),
228
314
  });
315
+ designContent = replaceDesignEvidence(designContent, architecture.mermaid_diagrams, architecture.adrs);
229
316
  await fileManager.writeSpecFile(featureDir, "DESIGN.md", designContent, force);
230
317
  filesCreated.push("DESIGN.md");
231
318
  // Advance Clarify → Design
232
- await stateMachine.advancePhase(spec_dir, "001");
233
- await stateMachine.recordPhaseComplete(spec_dir, Phase.Design);
319
+ await stateMachine.advancePhase(featureDir);
320
+ await stateMachine.recordPhaseComplete(featureDir, Phase.Design);
234
321
  // ── Step 6: Write TASKS.md ──
235
322
  console.error(`[specky] Auto-pipeline: writing TASKS.md...`);
236
- const tasks = generateTasks(earsRequirements, analysis);
237
323
  const taskTable = tasks
238
- .map((t) => `| ${t.id} | ${t.title} | ${t.parallel ? "[P]" : ""} | ${t.effort} | ${t.depends} | ${t.traces} |`)
324
+ .map((t) => `| ${t.id} | ${t.title} | ${t.parallel ? "[P]" : ""} | ${t.effort} | ${t.dependencies.join(", ") || "—"} | ${t.traces_to.join(", ")} |`)
239
325
  .join("\n");
240
- const gates = [
241
- "**Gate 1:** CONSTITUTION.md approved — project charter and scope confirmed",
242
- "**Gate 2:** SPECIFICATION.md reviewed — all EARS requirements have acceptance criteria",
243
- "**Gate 3:** DESIGN.md reviewed — architecture and decisions documented",
244
- ];
326
+ const gates = pre_impl_gates.map((gate) => `**Gate ${gate.id}:** ${gate.check} (${gate.constitution_article})`);
245
327
  const tasksContent = await templateEngine.renderWithFrontmatter("tasks", {
328
+ ...artifactMetadata({ version: "1.0.0", author: "sdd_auto_pipeline", status: "Draft" }),
246
329
  title: `${project_name} — Tasks`,
247
- feature_id: `001-${project_name}`,
330
+ feature_id: `${feature_number}-${project_name}`,
248
331
  project_name,
249
332
  gates,
250
333
  task_table: taskTable,
251
- dependency_graph: tasks.map((t) => `${t.id}: ${t.title}`).join("\n"),
252
- effort_summary: "",
334
+ dependency_graph: tasks.map((t) => `${t.id}: ${t.title} -> [${t.dependencies.join(", ")}]`).join("\n"),
335
+ effort_summary: tasks.map((t) => `${t.id}: ${t.effort}`).join("; "),
253
336
  total_tasks: String(tasks.length),
254
337
  parallel_tasks: String(tasks.filter((t) => t.parallel).length),
255
338
  total_effort: `${tasks.length} tasks`,
@@ -257,8 +340,8 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
257
340
  await fileManager.writeSpecFile(featureDir, "TASKS.md", tasksContent, force);
258
341
  filesCreated.push("TASKS.md");
259
342
  // Advance Design → Tasks
260
- await stateMachine.advancePhase(spec_dir, "001");
261
- await stateMachine.recordPhaseComplete(spec_dir, Phase.Tasks);
343
+ await stateMachine.advancePhase(featureDir);
344
+ await stateMachine.recordPhaseComplete(featureDir, Phase.Tasks);
262
345
  // ── Step 7: Write ANALYSIS.md ──
263
346
  // Real quality gate over the artifacts we just wrote — the SAME
264
347
  // AnalysisEngine sdd_run_analysis and sdd_batch_transcripts use, with
@@ -274,9 +357,16 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
274
357
  designContent,
275
358
  tasksContent,
276
359
  });
360
+ let reportedGaps = gate.gaps;
361
+ if (reportedGaps.length === 0) {
362
+ reportedGaps = analysis.open_questions.length > 0
363
+ ? analysis.open_questions.map((question) => `Open question from meeting: ${question}`)
364
+ : ["No gaps — all requirements mapped through design and tasks"];
365
+ }
277
366
  const analysisContent = await templateEngine.renderWithFrontmatter("analysis", {
367
+ ...artifactMetadata({ version: "1.0.0", author: "sdd_auto_pipeline", status: gate.decision }),
278
368
  title: `${project_name} — Analysis`,
279
- feature_id: `001-${project_name}`,
369
+ feature_id: `${feature_number}-${project_name}`,
280
370
  project_name,
281
371
  gate_decision: gate.decision,
282
372
  coverage_percent: String(gate.coveragePercent),
@@ -284,19 +374,12 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
284
374
  design_coverage: `${gate.designCoverage}%`,
285
375
  task_coverage: `${gate.taskCoverage}%`,
286
376
  test_coverage: "Pending implementation",
287
- gaps: gate.gaps.length > 0
288
- ? gate.gaps
289
- : analysis.open_questions.length > 0
290
- ? analysis.open_questions.map((q) => `Open question from meeting: ${q}`)
291
- : ["No gaps all requirements mapped through design and tasks"],
292
- recommendations: [
293
- "Review auto-generated EARS requirements for accuracy",
294
- ...gate.gaps.map((g) => `Remediate: ${g}`),
295
- "Validate architecture decisions with team",
296
- "Add detailed API contracts to DESIGN.md",
297
- "Prioritize tasks based on team capacity",
298
- ...analysis.action_items.map((a) => `Action item: ${a}`),
299
- ],
377
+ gaps: reportedGaps,
378
+ recommendations: gate.gaps.length > 0
379
+ ? gate.gaps.map((gap) => `Remediate: ${gap}`)
380
+ : analysis.action_items.length > 0
381
+ ? analysis.action_items.map((action) => `Source action: ${action}`)
382
+ : ["No remediation is required by the current traceability analysis."],
300
383
  ears_compliance: `${gate.earsCoverage}%`,
301
384
  ears_status: gate.earsCoverage === 100 ? "✅" : "❌",
302
385
  coverage_status: gate.coveragePercent >= 90 ? "✅" : "❌",
@@ -311,24 +394,24 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
311
394
  filesCreated.push("TRANSCRIPT.md");
312
395
  const featurePackage = await featurePackageGenerator.ensureFeaturePackage({
313
396
  featureDir,
314
- featureNumber: "001",
397
+ featureNumber: feature_number,
315
398
  featureName: project_name,
316
399
  specContent,
317
400
  sourceTool: "sdd_auto_pipeline",
318
401
  });
319
402
  filesCreated.push(...featurePackage.created);
320
403
  // ── Step 9: Finalize state — advance Tasks → Analyze and set gate decision ──
321
- await stateMachine.advancePhase(spec_dir, "001"); // Tasks → Analyze
322
- await stateMachine.recordPhaseComplete(spec_dir, Phase.Analyze);
404
+ await stateMachine.advancePhase(featureDir); // Tasks → Analyze
405
+ await stateMachine.recordPhaseComplete(featureDir, Phase.Analyze);
323
406
  // Persist the COMPUTED gate decision — the state must carry the same
324
407
  // engine verdict written into ANALYSIS.md, not an asserted APPROVE.
325
408
  const gateDecidedAt = new Date().toISOString();
326
- await stateMachine.mutateState(spec_dir, (state) => {
409
+ await stateMachine.mutateState(featureDir, (state) => {
327
410
  state.gate_decision = {
328
411
  decision: gate.decision,
329
412
  reasons: [
330
413
  ...gate.reasons,
331
- `${earsRequirements.length} EARS requirements extracted from transcript.`,
414
+ `${earsRequirements.length} source-backed EARS requirements validated against the transcript.`,
332
415
  `${analysis.decisions.length} decisions captured.`,
333
416
  `${analysis.topics.length} topics covered.`,
334
417
  ],
@@ -350,7 +433,7 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
350
433
  topics_extracted: analysis.topics.length,
351
434
  decisions_captured: analysis.decisions.length,
352
435
  action_items: analysis.action_items.length,
353
- requirements_generated: earsRequirements.length,
436
+ requirements_validated: earsRequirements.length,
354
437
  open_questions: analysis.open_questions.length,
355
438
  },
356
439
  gate_decision: {
@@ -364,14 +447,14 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
364
447
  ...(gate.decision === "APPROVE"
365
448
  ? []
366
449
  : [`0. Gate is ${gate.decision} (${gate.coveragePercent}% coverage) — remediate the gaps in ANALYSIS.md, then re-run sdd_run_analysis`]),
367
- "1. Review SPECIFICATION.md validate auto-generated EARS requirements",
368
- "2. Review DESIGN.md add Mermaid diagrams and API contracts",
369
- "3. Review TASKS.md adjust priorities and effort estimates",
450
+ "1. Review SPECIFICATION.md source quotes and acceptance criteria",
451
+ "2. Review DESIGN.md diagrams, ADRs, and workload-specific contract",
452
+ "3. Review TASKS.md dependency and requirement traceability",
370
453
  "4. Review TRANSCRIPT.md — original meeting content preserved",
371
454
  "5. Start implementation following TASKS.md breakdown",
372
455
  ].join("\n"),
373
456
  };
374
- const enriched = await enrichResponse("sdd_auto_pipeline", result, stateMachine, spec_dir);
457
+ const enriched = await enrichResponse("sdd_auto_pipeline", result, stateMachine, featureDir);
375
458
  return {
376
459
  content: [
377
460
  { type: "text", text: truncate(JSON.stringify(enriched, null, 2)) },
@@ -393,7 +476,7 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
393
476
  // ─── sdd_batch_transcripts ───
394
477
  server.registerTool("sdd_batch_transcripts", {
395
478
  title: "Batch Process Transcript Folder",
396
- description: "Scans a folder for transcript files (.vtt, .srt, .txt, .md) and runs the full SDD auto-pipeline for each one. Designed for Power Automate + OneDrive workflows where meeting transcripts are saved automatically to a shared folder. Each transcript becomes its own feature spec package.",
479
+ description: "Validates a one-to-one explicit feature manifest for every transcript file, including source quotes, architecture, tasks, gates, and feature numbers, before writing batch feature packages.",
397
480
  inputSchema: batchTranscriptsInputSchema,
398
481
  annotations: {
399
482
  readOnlyHint: false,
@@ -401,8 +484,12 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
401
484
  idempotentHint: false,
402
485
  openWorldHint: false,
403
486
  },
404
- }, async ({ transcripts_dir, spec_dir, force }) => {
487
+ }, async ({ transcripts_dir, spec_dir, features: featureInputs, force }) => {
405
488
  try {
489
+ const contract = requireExecutionContext("sdd_batch_transcripts").requestedContract;
490
+ if (contract.execution_mode !== "full") {
491
+ throw new Error(`sdd_batch_transcripts requires a full execution-mode contract; received ${contract.id}.`);
492
+ }
406
493
  console.error(`[specky] Batch: scanning ${transcripts_dir} for transcripts...`);
407
494
  // Find all transcript files
408
495
  const extensions = [".vtt", ".srt", ".txt", ".md"];
@@ -422,52 +509,67 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
422
509
  ],
423
510
  };
424
511
  }
425
- console.error(`[specky] Batch: found ${files.length} transcript(s). Processing...`);
426
- const results = [];
427
- let featureSeq = 1;
428
- // Check existing features to avoid number collisions
512
+ const filesByName = new Map(files.map((filePath) => [basename(filePath), filePath]));
513
+ const configuredNames = new Set(featureInputs.map((feature) => feature.file_name));
514
+ const missingConfigurations = [...filesByName.keys()].filter((name) => !configuredNames.has(name));
515
+ const missingFiles = featureInputs
516
+ .map((feature) => feature.file_name)
517
+ .filter((name) => !filesByName.has(name));
518
+ if (missingConfigurations.length > 0 || missingFiles.length > 0) {
519
+ throw new Error(`Batch manifest mismatch. Unconfigured files: ${missingConfigurations.join(", ") || "none"}. ` +
520
+ `Missing files: ${missingFiles.join(", ") || "none"}.`);
521
+ }
429
522
  const existingFeatures = await fileManager.listFeatures(spec_dir);
430
- if (existingFeatures.length > 0) {
431
- const maxNum = Math.max(...existingFeatures.map((f) => parseInt(f.number, 10)));
432
- featureSeq = maxNum + 1;
523
+ const existingNumbers = new Set(existingFeatures.map((feature) => feature.number));
524
+ const configuredNumbers = featureInputs.map((feature) => feature.feature_number);
525
+ const duplicateNumbers = configuredNumbers.filter((number, index) => configuredNumbers.indexOf(number) !== index);
526
+ const collisions = configuredNumbers.filter((number) => existingNumbers.has(number));
527
+ if (duplicateNumbers.length > 0 || collisions.length > 0) {
528
+ throw new Error(`Batch feature number conflict. Duplicates: ${[...new Set(duplicateNumbers)].join(", ") || "none"}. ` +
529
+ `Existing: ${collisions.join(", ") || "none"}.`);
433
530
  }
434
- for (const filePath of files) {
435
- const fileName = basename(filePath);
436
- // Derive project name from filename: "Sprint Planning 2026-03-20.vtt" → "sprint-planning-2026-03-20"
437
- const projectName = fileName
438
- .replace(/\.(vtt|srt|txt|md)$/i, "")
439
- .toLowerCase()
440
- .replace(/[^a-z0-9]+/g, "-")
441
- .replace(/^-+|-+$/g, "")
442
- .slice(0, 80) || "transcript";
443
- const featureNum = String(featureSeq).padStart(3, "0");
531
+ const prepared = await Promise.all(featureInputs.map(async (featureInput) => {
532
+ const filePath = filesByName.get(featureInput.file_name);
533
+ const analysis = await transcriptParser.parseFile(filePath);
534
+ validateExplicitFeatureContent(analysis, featureInput.requirements, featureInput.tasks, earsValidator);
535
+ const workloadDesign = renderWorkloadDesign(contract.workload, featureInput.architecture.workload_design);
536
+ if (contract.workload === "api" && featureInput.architecture.api_contracts.length === 0) {
537
+ throw new Error(`Batch feature ${featureInput.feature_number} requires an explicit API contract.`);
538
+ }
539
+ return { featureInput, filePath, analysis, workloadDesign };
540
+ }));
541
+ console.error(`[specky] Batch: validated ${prepared.length} transcript feature manifest(s). Processing...`);
542
+ const results = [];
543
+ for (const { featureInput, analysis, workloadDesign } of prepared) {
544
+ const fileName = featureInput.file_name;
545
+ const projectName = featureInput.project_name;
546
+ const featureNum = featureInput.feature_number;
444
547
  const featureDir = join(spec_dir, `${featureNum}-${projectName}`);
445
548
  try {
446
- console.error(`[specky] Batch [${featureSeq}/${files.length}]: processing "${fileName}"...`);
447
- // Parse transcript
448
- const analysis = await transcriptParser.parseFile(filePath);
449
- // Generate requirements
450
- const earsRequirements = generateEarsRequirements(analysis, earsValidator);
549
+ console.error(`[specky] Batch feature ${featureNum}: processing "${fileName}"...`);
550
+ const earsRequirements = featureInput.requirements.map((requirement) => ({
551
+ id: requirement.id,
552
+ title: requirement.title,
553
+ pattern: requirement.ears_pattern,
554
+ text: requirement.text,
555
+ acceptance_criteria: requirement.acceptance_criteria,
556
+ source: requirement.source_quote,
557
+ }));
451
558
  // ── Write all spec files ──
452
559
  await fileManager.ensureSpecDir(spec_dir);
453
560
  // CONSTITUTION.md
454
561
  const constitutionContent = await templateEngine.renderWithFrontmatter("constitution", {
562
+ ...artifactMetadata({ version: "1.0.0", author: "sdd_batch_transcripts", status: "Active" }),
455
563
  title: `${projectName} — Constitution`,
456
564
  feature_id: `${featureNum}-${projectName}`,
457
565
  project_name: projectName,
458
- author: `SDD Pipeline (from: ${analysis.participants.join(", ")})`,
459
- principles: [
460
- "Requirements traced to meeting decisions",
461
- "EARS notation for all requirements",
462
- "Traceability from transcript to implementation",
463
- ],
464
- constraints: analysis.constraints_mentioned.length > 0
465
- ? analysis.constraints_mentioned
466
- : ["Derived from meeting transcript"],
467
- description: `Auto-generated from ${fileName}. ${analysis.topics.length} topics, ${analysis.participants.length} participants.`,
468
- license: "MIT",
469
- scope_in: analysis.topics.map((t) => t.name).join(", ") || "Meeting topics",
470
- scope_out: "Items not discussed",
566
+ author: featureInput.constitution.author,
567
+ principles: featureInput.constitution.principles,
568
+ constraints: featureInput.constitution.constraints,
569
+ description: featureInput.constitution.description,
570
+ license: featureInput.constitution.license,
571
+ scope_in: featureInput.constitution.scope_in,
572
+ scope_out: featureInput.constitution.scope_out,
471
573
  });
472
574
  await fileManager.writeSpecFile(featureDir, "CONSTITUTION.md", constitutionContent, force);
473
575
  // SPECIFICATION.md
@@ -479,9 +581,11 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
479
581
  "", `**Source:** ${req.source}`, "", "---", "",
480
582
  ].join("\n")).join("\n");
481
583
  const specContent = await templateEngine.renderWithFrontmatter("specification", {
584
+ ...artifactMetadata({ version: "1.0.0", author: "sdd_batch_transcripts", status: "Draft" }),
482
585
  title: `${projectName} — Specification`,
483
586
  feature_id: `${featureNum}-${projectName}`,
484
587
  project_name: projectName,
588
+ discovery_context: transcriptDiscoveryContext(analysis),
485
589
  requirements_core: reqSections,
486
590
  requirements_functional: "",
487
591
  requirements_nonfunctional: "",
@@ -492,41 +596,53 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
492
596
  uniqueness_score: `${earsRequirements.length}/${earsRequirements.length}`,
493
597
  });
494
598
  await fileManager.writeSpecFile(featureDir, "SPECIFICATION.md", specContent, force);
495
- // DESIGN.md
496
- const batchStubs = deriveDesignStubs(syntheticSpecFromRequirements(earsRequirements));
497
- const designContent = await templateEngine.renderWithFrontmatter("design", {
599
+ const architecture = featureInput.architecture;
600
+ const apiContent = architecture.api_contracts.length > 0
601
+ ? architecture.api_contracts.map((api) => [
602
+ `### ${api.method} ${api.endpoint}`,
603
+ "",
604
+ api.description,
605
+ "",
606
+ `**Request:** ${api.request}`,
607
+ "",
608
+ `**Response:** ${api.response}`,
609
+ ].join("\n")).join("\n\n---\n\n")
610
+ : "No network API is exposed by this workload contract.";
611
+ let designContent = await templateEngine.renderWithFrontmatter("design", {
612
+ ...artifactMetadata({ version: "1.0.0", author: "sdd_batch_transcripts", status: "Draft" }),
498
613
  title: `${projectName} — Design`,
499
614
  feature_id: `${featureNum}-${projectName}`,
500
615
  project_name: projectName,
501
- architecture_overview: generateArchitectureOverview(analysis, earsRequirements),
502
- diagrams: analysis.topics.map((t) => t.name),
503
- adrs: analysis.decisions.length > 0
504
- ? analysis.decisions.map((d, i) => `ADR-${String(i + 1).padStart(3, "0")}: ${d.slice(0, 80)}`)
505
- : ["No decisions in transcript"],
506
- api_contracts: batchStubs.api_contracts_stub,
507
- data_models: batchStubs.data_models,
508
- error_handling: batchStubs.error_handling,
509
- component_design: batchStubs.component_design,
510
- security_architecture: batchStubs.security_architecture,
511
- infrastructure: batchStubs.infrastructure,
512
- cross_cutting: batchStubs.cross_cutting,
513
- requirement_references: batchStubs.requirement_references,
616
+ architecture_overview: architecture.architecture_overview,
617
+ system_context: architecture.system_context,
618
+ container_architecture: architecture.container_architecture,
619
+ diagrams: architecture.mermaid_diagrams.map((diagram) => diagram.title),
620
+ adrs: architecture.adrs.map((adr) => adr.title),
621
+ api_contracts: apiContent,
622
+ data_models: architecture.data_models,
623
+ error_handling: architecture.error_handling,
624
+ component_design: architecture.component_design,
625
+ code_level_design: architecture.code_level_design,
626
+ security_architecture: architecture.security_architecture,
627
+ infrastructure: architecture.infrastructure,
628
+ cross_cutting: architecture.cross_cutting,
629
+ workload_design: workloadDesign,
630
+ requirement_references: featureInput.requirements
631
+ .map((requirement) => `- ${requirement.id}`)
632
+ .join("\n"),
514
633
  });
634
+ designContent = replaceDesignEvidence(designContent, architecture.mermaid_diagrams, architecture.adrs);
515
635
  await fileManager.writeSpecFile(featureDir, "DESIGN.md", designContent, force);
516
- // TASKS.md
517
- const tasks = generateTasks(earsRequirements, analysis);
636
+ const tasks = featureInput.tasks;
518
637
  const tasksContent = await templateEngine.renderWithFrontmatter("tasks", {
638
+ ...artifactMetadata({ version: "1.0.0", author: "sdd_batch_transcripts", status: "Draft" }),
519
639
  title: `${projectName} — Tasks`,
520
640
  feature_id: `${featureNum}-${projectName}`,
521
641
  project_name: projectName,
522
- gates: [
523
- "**Gate 1:** CONSTITUTION.md approved",
524
- "**Gate 2:** SPECIFICATION.md reviewed",
525
- "**Gate 3:** DESIGN.md reviewed",
526
- ],
527
- task_table: tasks.map((t) => `| ${t.id} | ${t.title} | ${t.parallel ? "[P]" : ""} | ${t.effort} | ${t.depends} | ${t.traces} |`).join("\n"),
528
- dependency_graph: tasks.map((t) => `${t.id}: ${t.title}`).join("\n"),
529
- effort_summary: "",
642
+ gates: featureInput.pre_impl_gates.map((gate) => `**Gate ${gate.id}:** ${gate.check} (${gate.constitution_article})`),
643
+ task_table: tasks.map((t) => `| ${t.id} | ${t.title} | ${t.parallel ? "[P]" : ""} | ${t.effort} | ${t.dependencies.join(", ") || "—"} | ${t.traces_to.join(", ")} |`).join("\n"),
644
+ dependency_graph: tasks.map((t) => `${t.id}: ${t.title} -> [${t.dependencies.join(", ")}]`).join("\n"),
645
+ effort_summary: tasks.map((t) => `${t.id}: ${t.effort}`).join("; "),
530
646
  total_tasks: String(tasks.length),
531
647
  parallel_tasks: String(tasks.filter((t) => t.parallel).length),
532
648
  total_effort: `${tasks.length} tasks`,
@@ -546,6 +662,7 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
546
662
  tasksContent,
547
663
  });
548
664
  const analysisContent = await templateEngine.renderWithFrontmatter("analysis", {
665
+ ...artifactMetadata({ version: "1.0.0", author: "sdd_batch_transcripts", status: gate.decision }),
549
666
  title: `${projectName} — Analysis`,
550
667
  feature_id: `${featureNum}-${projectName}`,
551
668
  project_name: projectName,
@@ -555,19 +672,17 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
555
672
  design_coverage: `${gate.designCoverage}%`,
556
673
  task_coverage: `${gate.taskCoverage}%`,
557
674
  test_coverage: "Pending",
558
- gaps: gate.gaps.length > 0
559
- ? gate.gaps
560
- : analysis.open_questions.map((q) => `Open: ${q}`),
561
- recommendations: [
562
- "Review auto-generated EARS requirements",
563
- ...gate.gaps.map((g) => `Remediate: ${g}`),
564
- ...analysis.action_items.slice(0, 3).map((a) => `Action: ${a}`),
565
- ],
675
+ gaps: batchAnalysisGaps(gate.gaps, analysis.open_questions),
676
+ recommendations: gate.gaps.length > 0
677
+ ? gate.gaps.map((gap) => `Remediate: ${gap}`)
678
+ : analysis.action_items.length > 0
679
+ ? analysis.action_items.map((action) => `Source action: ${action}`)
680
+ : ["No remediation is required by the current traceability analysis."],
566
681
  ears_compliance: `${gate.earsCoverage}%`,
567
- ears_status: gate.earsCoverage === 100 ? "✅" : "❌",
568
- coverage_status: gate.coveragePercent >= 90 ? "✅" : "❌",
682
+ ears_status: evidenceStatus(gate.earsCoverage === 100),
683
+ coverage_status: evidenceStatus(gate.coveragePercent >= 90),
569
684
  orphan_count: String(gate.orphanCount),
570
- orphan_status: gate.orphanCount === 0 ? "✅" : "❌",
685
+ orphan_status: evidenceStatus(gate.orphanCount === 0),
571
686
  });
572
687
  await fileManager.writeSpecFile(featureDir, "ANALYSIS.md", analysisContent, force);
573
688
  // TRANSCRIPT.md
@@ -580,9 +695,11 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
580
695
  specContent,
581
696
  sourceTool: "sdd_batch_transcripts",
582
697
  });
583
- // State for this feature
584
- const state = stateMachine.createDefaultState(projectName);
585
- state.features = [featureDir];
698
+ const state = stateMachine.createFeatureState({
699
+ projectName,
700
+ feature: { number: featureNum, name: projectName, directory: featureDir },
701
+ contract,
702
+ });
586
703
  state.current_phase = Phase.Analyze;
587
704
  for (const phase of [Phase.Init, Phase.Discover, Phase.Specify, Phase.Clarify, Phase.Design, Phase.Tasks, Phase.Analyze]) {
588
705
  state.phases[phase] = {
@@ -604,8 +721,7 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
604
721
  gaps: gate.gaps,
605
722
  decided_at: new Date().toISOString(),
606
723
  };
607
- // Save state per feature (use feature-specific state dir)
608
- await fileManager.writeSpecFile(featureDir, ".sdd-state.json", JSON.stringify(state, null, 2), true);
724
+ await stateMachine.saveState(featureDir, state);
609
725
  results.push({
610
726
  file: fileName,
611
727
  project_name: projectName,
@@ -614,7 +730,6 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
614
730
  requirements: earsRequirements.length,
615
731
  package_artifacts: featurePackage.created.length,
616
732
  });
617
- featureSeq++;
618
733
  }
619
734
  catch (err) {
620
735
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -627,7 +742,6 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
627
742
  requirements: 0,
628
743
  error: errorMsg,
629
744
  });
630
- featureSeq++;
631
745
  }
632
746
  }
633
747
  const succeeded = results.filter((r) => r.status === "completed");
@@ -644,17 +758,20 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
644
758
  spec_directory: spec_dir,
645
759
  next_action: [
646
760
  `${succeeded.length} feature spec packages created in ${spec_dir}/`,
647
- "Review each SPECIFICATION.md for EARS requirement accuracy.",
761
+ "Review each source quote and artifact approval state.",
648
762
  "Use sdd_get_status to check any individual feature.",
649
- failed.length > 0
650
- ? `${failed.length} transcript(s) failed — check error messages above.`
651
- : "All transcripts processed successfully.",
763
+ batchCompletionMessage(failed.length),
652
764
  ].join("\n"),
653
765
  };
654
- const enriched = await enrichResponse("sdd_batch_transcripts", batchResult, stateMachine, spec_dir);
655
766
  return {
656
767
  content: [
657
- { type: "text", text: truncate(JSON.stringify(enriched, null, 2)) },
768
+ {
769
+ type: "text", text: truncate(JSON.stringify({
770
+ ...batchResult,
771
+ contract_id: contract.id,
772
+ contract_fingerprint: contract.fingerprint,
773
+ }, null, 2))
774
+ },
658
775
  ],
659
776
  };
660
777
  }
@@ -671,202 +788,15 @@ export function registerTranscriptTools(server, fileManager, stateMachine, templ
671
788
  }
672
789
  });
673
790
  }
674
- function generateEarsRequirements(analysis, earsValidator) {
675
- const reqs = [];
676
- let seqCore = 1;
677
- let seqFunc = 1;
678
- let seqNfr = 1;
679
- // Generate from extracted requirements
680
- for (const rawReq of analysis.requirements_raw) {
681
- const improvement = earsValidator.suggestImprovement(rawReq);
682
- const id = `REQ-FUNC-${String(seqFunc++).padStart(3, "0")}`;
683
- reqs.push({
684
- id,
685
- title: rawReq.slice(0, 60),
686
- pattern: improvement.pattern,
687
- text: improvement.suggestion.includes("[TODO")
688
- ? `The system shall ${rawReq.toLowerCase().replace(/\.$/, "")}.`
689
- : improvement.suggestion,
690
- acceptance_criteria: [
691
- `Verify that the system ${rawReq.toLowerCase().replace(/\.$/, "")}`,
692
- "Automated test validates this requirement",
693
- ],
694
- source: `Meeting transcript — raw requirement`,
695
- });
696
- }
697
- // Generate from decisions
698
- for (const decision of analysis.decisions) {
699
- const cleanDecision = decision.replace(/^\[[^\]]+\]\s*/, "");
700
- const id = `REQ-CORE-${String(seqCore++).padStart(3, "0")}`;
701
- reqs.push({
702
- id,
703
- title: cleanDecision.slice(0, 60),
704
- pattern: "event_driven",
705
- text: `When the project is implemented, the system shall ${cleanDecision.toLowerCase().replace(/\.$/, "")}.`,
706
- acceptance_criteria: [
707
- `Verify that ${cleanDecision.toLowerCase().replace(/\.$/, "")}`,
708
- "Team review confirms alignment with meeting decision",
709
- ],
710
- source: `Meeting decision`,
711
- });
712
- }
713
- // Generate from constraints
714
- for (const constraint of analysis.constraints_mentioned) {
715
- const id = `REQ-NFR-${String(seqNfr++).padStart(3, "0")}`;
716
- reqs.push({
717
- id,
718
- title: constraint.slice(0, 60),
719
- pattern: "ubiquitous",
720
- text: `The system shall comply with the constraint: ${constraint.toLowerCase().replace(/\.$/, "")}.`,
721
- acceptance_criteria: [
722
- `Verify compliance with: ${constraint}`,
723
- "Architecture review confirms constraint is met",
724
- ],
725
- source: `Meeting constraint`,
726
- });
727
- }
728
- // Generate from topics (at least one per topic)
729
- for (const topic of analysis.topics) {
730
- const existing = reqs.some((r) => r.source.includes(topic.name) || r.title.toLowerCase().includes(topic.name.toLowerCase()));
731
- if (!existing && topic.key_points.length > 0) {
732
- const id = `REQ-FUNC-${String(seqFunc++).padStart(3, "0")}`;
733
- reqs.push({
734
- id,
735
- title: topic.name,
736
- pattern: "ubiquitous",
737
- text: `The system shall support ${topic.name.toLowerCase()} as discussed: ${topic.key_points[0].slice(0, 200)}.`,
738
- acceptance_criteria: [
739
- `Verify ${topic.name.toLowerCase()} functionality`,
740
- ...topic.key_points.slice(0, 2).map((kp) => `Verify: ${kp.slice(0, 100)}`),
741
- ],
742
- source: `Meeting topic: ${topic.name}`,
743
- });
744
- }
745
- }
746
- // Ensure minimum 5 requirements
747
- if (reqs.length < 5) {
748
- const defaults = [
749
- {
750
- title: "System Availability",
751
- text: "The system shall be available during business hours with 99% uptime.",
752
- criteria: ["Monitor uptime", "Alert on downtime"],
753
- },
754
- {
755
- title: "Error Handling",
756
- text: "When an error occurs, the system shall log the error and return a user-friendly message.",
757
- criteria: ["Verify error logging", "Verify user-friendly error messages"],
758
- },
759
- {
760
- title: "Security",
761
- text: "The system shall authenticate all users before granting access to protected resources.",
762
- criteria: ["Verify authentication is required", "Verify unauthorized access is rejected"],
763
- },
764
- ];
765
- for (const def of defaults) {
766
- if (reqs.length >= 5)
767
- break;
768
- const id = `REQ-NFR-${String(seqNfr++).padStart(3, "0")}`;
769
- reqs.push({
770
- id,
771
- title: def.title,
772
- pattern: reqs.length % 2 === 0 ? "ubiquitous" : "event_driven",
773
- text: def.text,
774
- acceptance_criteria: def.criteria,
775
- source: "Auto-generated baseline requirement",
776
- });
777
- }
778
- }
779
- return reqs;
791
+ function batchAnalysisGaps(gaps, openQuestions) {
792
+ return gaps.length > 0 ? gaps : openQuestions.map((question) => `Open: ${question}`);
780
793
  }
781
- function generateArchitectureOverview(analysis, requirements) {
782
- const lines = [
783
- `Architecture auto-generated from meeting transcript with ${analysis.participants.length} participants.`,
784
- "",
785
- "## Key Components (from meeting topics)",
786
- "",
787
- ];
788
- for (const topic of analysis.topics) {
789
- lines.push(`### ${topic.name}`);
790
- lines.push("");
791
- lines.push(topic.summary || "Component discussed in meeting.");
792
- lines.push("");
793
- if (topic.key_points.length > 0) {
794
- lines.push("**Key points:**");
795
- for (const kp of topic.key_points.slice(0, 3)) {
796
- lines.push(`- ${kp}`);
797
- }
798
- lines.push("");
799
- }
800
- }
801
- if (analysis.decisions.length > 0) {
802
- lines.push("## Architecture Decisions");
803
- lines.push("");
804
- for (const d of analysis.decisions) {
805
- lines.push(`- ${d}`);
806
- }
807
- lines.push("");
808
- }
809
- lines.push(`## Requirements Coverage`);
810
- lines.push("");
811
- lines.push(`Total requirements: ${requirements.length}`);
812
- lines.push(`Topics covered: ${analysis.topics.length}`);
813
- return lines.join("\n");
794
+ function evidenceStatus(passed) {
795
+ return passed ? "✅" : "❌";
814
796
  }
815
- function generateTasks(requirements, analysis) {
816
- const tasks = [];
817
- let seq = 1;
818
- // Task 1: Project setup (always)
819
- tasks.push({
820
- id: `T-${String(seq++).padStart(3, "0")}`,
821
- title: "Project setup and scaffolding",
822
- parallel: false,
823
- effort: "M",
824
- depends: "—",
825
- traces: "REQ-CORE-001",
826
- });
827
- // Tasks from requirements
828
- for (const req of requirements) {
829
- tasks.push({
830
- id: `T-${String(seq++).padStart(3, "0")}`,
831
- title: `Implement: ${req.title.slice(0, 60)}`,
832
- parallel: seq > 3, // First 2 tasks sequential, rest parallel
833
- effort: req.acceptance_criteria.length > 2 ? "L" : "M",
834
- depends: seq <= 3 ? `T-${String(seq - 2).padStart(3, "0")}` : "T-001",
835
- traces: req.id,
836
- });
837
- }
838
- // Tasks from action items
839
- for (const action of analysis.action_items.slice(0, 5)) {
840
- const cleanAction = action.replace(/^\[[^\]]+\]\s*/, "");
841
- tasks.push({
842
- id: `T-${String(seq++).padStart(3, "0")}`,
843
- title: `Action: ${cleanAction.slice(0, 60)}`,
844
- parallel: true,
845
- effort: "S",
846
- depends: "T-001",
847
- traces: "—",
848
- });
849
- }
850
- // Final tasks
851
- tasks.push({
852
- id: `T-${String(seq++).padStart(3, "0")}`,
853
- title: "Integration testing",
854
- parallel: false,
855
- effort: "L",
856
- depends: tasks
857
- .slice(-3)
858
- .map((t) => t.id)
859
- .join(", "),
860
- traces: "All REQ",
861
- });
862
- tasks.push({
863
- id: `T-${String(seq++).padStart(3, "0")}`,
864
- title: "Documentation and deployment",
865
- parallel: false,
866
- effort: "M",
867
- depends: `T-${String(seq - 2).padStart(3, "0")}`,
868
- traces: "All REQ",
869
- });
870
- return tasks;
797
+ function batchCompletionMessage(failedCount) {
798
+ return failedCount > 0
799
+ ? `${failedCount} transcript feature(s) failed; no unvalidated source content was synthesized.`
800
+ : "All explicitly configured transcript features were processed successfully.";
871
801
  }
872
802
  //# sourceMappingURL=transcript.js.map