specrails-desktop 2.29.4 → 2.30.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 (169) hide show
  1. package/client/dist/assets/{ActivityFeedPage-CDMzpGXd.js → ActivityFeedPage-AQgpr9qG.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-BVTacapZ.js → AgentBrowserCapture-I7a0usG7.js} +1 -1
  3. package/client/dist/assets/AgentModeCodePane-BqOVg15n.js +2 -0
  4. package/client/dist/assets/{AgentModeJobsPane-DAjN8Au5.js → AgentModeJobsPane-4SG6FF27.js} +1 -1
  5. package/client/dist/assets/{AgentsPage-CebykajE.js → AgentsPage-C3FgYlEi.js} +1 -1
  6. package/client/dist/assets/{AnalyticsPage-BiPrVvgv.js → AnalyticsPage-BcMNByW5.js} +1 -1
  7. package/client/dist/assets/{CodePage-Dw9eQFRS.js → CodePage-CgdDkXp-.js} +1 -1
  8. package/client/dist/assets/{DesktopAnalyticsPage-cCG_JAEi.js → DesktopAnalyticsPage-DPMcS88n.js} +1 -1
  9. package/client/dist/assets/{DocsDialog-C1kZ_mRP.js → DocsDialog-BC7574Zi.js} +1 -1
  10. package/client/dist/assets/{DocsPage-3GivxWJm.js → DocsPage-Dd9yilUg.js} +1 -1
  11. package/client/dist/assets/{InteractiveJobComposer-KuZXGdr8.js → InteractiveJobComposer-CqC_0VQL.js} +1 -1
  12. package/client/dist/assets/{JobDetailModal-DWpRALkB.js → JobDetailModal-BFNXOhk1.js} +1 -1
  13. package/client/dist/assets/{JobDetailPage-mrXDKafj.js → JobDetailPage-DM-6zYpA.js} +1 -1
  14. package/client/dist/assets/{JobsPage-CUYXjcoc.js → JobsPage-1dkjPk1k.js} +1 -1
  15. package/client/dist/assets/{LoopBuilderPage-CwjBD5Gm.js → LoopBuilderPage-BPeqz6cG.js} +1 -1
  16. package/client/dist/assets/{LoopPreviewModal-CO9ton0-.js → LoopPreviewModal-EdkyYaGN.js} +1 -1
  17. package/client/dist/assets/{LoopsPage-CLL5UuvN.js → LoopsPage-nZfgiaK8.js} +1 -1
  18. package/client/dist/assets/{MinimizedChatsContext-DJAkRYps.js → MinimizedChatsContext-Cs34FPTz.js} +1 -1
  19. package/client/dist/assets/{PluginsPage-DZvCC5Ri.js → PluginsPage-CwFfFBzW.js} +1 -1
  20. package/client/dist/assets/{ProjectSettingsDialog-C7Hesadg.js → ProjectSettingsDialog-DHsxLFMT.js} +1 -1
  21. package/client/dist/assets/{TemplatePreviewModal-DczCzUzP.js → TemplatePreviewModal-wcI3nGNr.js} +1 -1
  22. package/client/dist/assets/TicketDetailModal-7EloKP1C.js +14 -0
  23. package/client/dist/assets/{Trans-Cm9rhdnG.js → Trans-CPISVji8.js} +1 -1
  24. package/client/dist/assets/agent-BABjRt0P.js +1 -0
  25. package/client/dist/assets/agent-BuUpgqVx.js +1 -0
  26. package/client/dist/assets/agent-CCDZesgU.js +1 -0
  27. package/client/dist/assets/agent-CVn64zWH.js +1 -0
  28. package/client/dist/assets/agent-CWeOny0E.js +1 -0
  29. package/client/dist/assets/agent-DOysmad_.js +1 -0
  30. package/client/dist/assets/agent-DSsjTBhn.js +1 -0
  31. package/client/dist/assets/agent-DlZMxfM9.js +1 -0
  32. package/client/dist/assets/builder-BH8Fua-g.js +1 -0
  33. package/client/dist/assets/builder-BoV7DgKd.js +1 -0
  34. package/client/dist/assets/builder-Cc_Lf79C.js +1 -0
  35. package/client/dist/assets/builder-CwA4T7gP.js +1 -0
  36. package/client/dist/assets/builder-CwZ7Bme2.js +1 -0
  37. package/client/dist/assets/builder-CxW-JFsR.js +1 -0
  38. package/client/dist/assets/builder-D94lrV7x.js +1 -0
  39. package/client/dist/assets/builder-cqHpCyGa.js +1 -0
  40. package/client/dist/assets/{dist-js-BERxOT0X.js → dist-js-Bt2zW2YG.js} +1 -1
  41. package/client/dist/assets/{dist-js-Di8lflPo.js → dist-js-zvt5ap3o.js} +1 -1
  42. package/client/dist/assets/{formatDistanceToNow-BAXZRbEO.js → formatDistanceToNow-ByKli7Q_.js} +1 -1
  43. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DwzZXHtg.js → getTimezoneOffsetInMilliseconds-qrcBaLW1.js} +1 -1
  44. package/client/dist/assets/i18n-Bf9Ckd10.js +2 -0
  45. package/client/dist/assets/index-Dh9i4CnN.css +2 -0
  46. package/client/dist/assets/index-xkzK3E5i.js +76 -0
  47. package/client/dist/assets/setup-3832BJCo.js +1 -0
  48. package/client/dist/assets/setup-B8J-gECe.js +1 -0
  49. package/client/dist/assets/setup-BCIOxVsc.js +1 -0
  50. package/client/dist/assets/setup-BciLj_tU.js +1 -0
  51. package/client/dist/assets/setup-CITtPjed.js +1 -0
  52. package/client/dist/assets/setup-CTkE3cfh.js +1 -0
  53. package/client/dist/assets/setup-ajB4rYm-.js +1 -0
  54. package/client/dist/assets/setup-xFO1wNl0.js +1 -0
  55. package/client/dist/assets/{spending-BLb5pj6G.js → spending-DMnCXy84.js} +1 -1
  56. package/client/dist/assets/{useBrowserCaptureSession-DR1_jL56.js → useBrowserCaptureSession-CkBJShUw.js} +1 -1
  57. package/client/dist/assets/{useDesktop-D4DxBGPU.js → useDesktop-B4pSBIDd.js} +1 -1
  58. package/client/dist/index.html +14 -13
  59. package/docs/codex.md +2 -1
  60. package/docs/customizing.md +1 -1
  61. package/docs/internals/gemini-mcp-registration.md +1 -1
  62. package/docs/internals/global-artifacts-alignment-contract.md +4 -4
  63. package/docs/internals/project-builder.md +306 -0
  64. package/docs/internals/safe-pr-review-flow.md +29 -19
  65. package/package.json +1 -1
  66. package/server/dist/agent-operator-prompt.js +7 -1
  67. package/server/dist/blueprint-chat-manager.js +335 -0
  68. package/server/dist/blueprint-commit.js +335 -0
  69. package/server/dist/blueprint-draft-parser.js +168 -0
  70. package/server/dist/blueprint-operator-prompt.js +149 -0
  71. package/server/dist/blueprint-render.js +105 -0
  72. package/server/dist/blueprint-router.js +173 -0
  73. package/server/dist/blueprint-spec-quality.js +167 -0
  74. package/server/dist/blueprint-store.js +49 -0
  75. package/server/dist/blueprint-types.js +29 -0
  76. package/server/dist/builder-cwd-manager.js +57 -0
  77. package/server/dist/chat-manager.js +435 -23
  78. package/server/dist/context-scope.js +6 -3
  79. package/server/dist/contract-refine-runner.js +143 -72
  80. package/server/dist/desktop-db.js +27 -0
  81. package/server/dist/index.js +15 -0
  82. package/server/dist/offline-assemble.js +203 -0
  83. package/server/dist/pricing.js +5 -0
  84. package/server/dist/project-router-chat.js +14 -2
  85. package/server/dist/project-router.js +130 -7
  86. package/server/dist/providers/claude-adapter.js +8 -2
  87. package/server/dist/providers/codex-adapter.js +23 -10
  88. package/server/dist/providers/gemini-adapter.js +16 -7
  89. package/server/dist/rail-isolated-launch.js +11 -1
  90. package/server/dist/setup-manager.js +6 -54
  91. package/server/dist/spec-models.js +3 -0
  92. package/server/dist/workspace-manager.js +3 -1
  93. package/server/dist/worktree-overlay.js +233 -51
  94. package/client/dist/assets/AgentModeCodePane-D9bhFyuy.js +0 -2
  95. package/client/dist/assets/TicketDetailModal-CFh7GOv5.js +0 -14
  96. package/client/dist/assets/agent-BXc59ARK2.js +0 -1
  97. package/client/dist/assets/agent-BbIFXdDq.js +0 -1
  98. package/client/dist/assets/agent-BqEfA5u5.js +0 -1
  99. package/client/dist/assets/agent-BtGzgTC4.js +0 -1
  100. package/client/dist/assets/agent-CIhys_5d.js +0 -1
  101. package/client/dist/assets/agent-CJPaqsVR.js +0 -1
  102. package/client/dist/assets/agent-n-hY4GuL.js +0 -1
  103. package/client/dist/assets/agent-tJ-efa3_2.js +0 -1
  104. package/client/dist/assets/i18n-cK5pPbj8.js +0 -2
  105. package/client/dist/assets/index-BT6xDQjB.css +0 -2
  106. package/client/dist/assets/index-CCq_kIrF.js +0 -76
  107. package/client/dist/assets/setup-CGZjy2IF2.js +0 -1
  108. package/client/dist/assets/setup-CXa0Sx16.js +0 -1
  109. package/client/dist/assets/setup-CmK87LuS.js +0 -1
  110. package/client/dist/assets/setup-D2ktKKMF2.js +0 -1
  111. package/client/dist/assets/setup-DG2H7ha5.js +0 -1
  112. package/client/dist/assets/setup-DK5bHDUq2.js +0 -1
  113. package/client/dist/assets/setup-DrWlIQTZ.js +0 -1
  114. package/client/dist/assets/setup-rzHUVgAu.js +0 -1
  115. /package/client/dist/assets/{activity-C6kJpm9q2.js → activity-C6kJpm9q.js} +0 -0
  116. /package/client/dist/assets/{activity-DAyekdlt2.js → activity-DAyekdlt.js} +0 -0
  117. /package/client/dist/assets/{addspec-DbNBbrha2.js → addspec-DbNBbrha.js} +0 -0
  118. /package/client/dist/assets/{addspec-htHGZHOe2.js → addspec-htHGZHOe.js} +0 -0
  119. /package/client/dist/assets/{agents-6ZOJXWVk2.js → agents-6ZOJXWVk.js} +0 -0
  120. /package/client/dist/assets/{agents-Bz-thHkB2.js → agents-Bz-thHkB.js} +0 -0
  121. /package/client/dist/assets/{agentstudio-BLGvxWBx2.js → agentstudio-BLGvxWBx.js} +0 -0
  122. /package/client/dist/assets/{agentstudio-D9UKiWTj2.js → agentstudio-D9UKiWTj.js} +0 -0
  123. /package/client/dist/assets/{aiedit-BoG24nlg2.js → aiedit-BoG24nlg.js} +0 -0
  124. /package/client/dist/assets/{aiedit-olnv3QSt2.js → aiedit-olnv3QSt.js} +0 -0
  125. /package/client/dist/assets/{analytics-CdsAPAwZ2.js → analytics-CdsAPAwZ.js} +0 -0
  126. /package/client/dist/assets/{analytics-IEsKZvpW2.js → analytics-IEsKZvpW.js} +0 -0
  127. /package/client/dist/assets/{attachments-D0wXXt9T2.js → attachments-D0wXXt9T.js} +0 -0
  128. /package/client/dist/assets/{attachments-V_WZAYXx2.js → attachments-V_WZAYXx.js} +0 -0
  129. /package/client/dist/assets/{browser-CyR1Kp3m2.js → browser-CyR1Kp3m.js} +0 -0
  130. /package/client/dist/assets/{browser-DwZljcRh2.js → browser-DwZljcRh.js} +0 -0
  131. /package/client/dist/assets/{chat-BR8oe53x2.js → chat-BR8oe53x.js} +0 -0
  132. /package/client/dist/assets/{chat-DZyu3ImY2.js → chat-DZyu3ImY.js} +0 -0
  133. /package/client/dist/assets/{code-D20ef9Cf2.js → code-D20ef9Cf.js} +0 -0
  134. /package/client/dist/assets/{code-geHmknB92.js → code-geHmknB9.js} +0 -0
  135. /package/client/dist/assets/{commands-BEwbAjtD2.js → commands-BEwbAjtD.js} +0 -0
  136. /package/client/dist/assets/{commands-DTQBF_q52.js → commands-DTQBF_q5.js} +0 -0
  137. /package/client/dist/assets/{common-BNbnv8cE2.js → common-BNbnv8cE.js} +0 -0
  138. /package/client/dist/assets/{common-DBtTzeYm2.js → common-DBtTzeYm.js} +0 -0
  139. /package/client/dist/assets/{dashboard-Cqmmbh3l2.js → dashboard-Cqmmbh3l.js} +0 -0
  140. /package/client/dist/assets/{dashboard-Dy2Um-US2.js → dashboard-Dy2Um-US.js} +0 -0
  141. /package/client/dist/assets/{explore-C0TjprJW2.js → explore-C0TjprJW.js} +0 -0
  142. /package/client/dist/assets/{explore-DsXAgnmJ2.js → explore-DsXAgnmJ.js} +0 -0
  143. /package/client/dist/assets/{integrations-CH0zy1Mw2.js → integrations-CH0zy1Mw.js} +0 -0
  144. /package/client/dist/assets/{integrations-b8pH-vfl2.js → integrations-b8pH-vfl.js} +0 -0
  145. /package/client/dist/assets/{jira-CNeYi06w2.js → jira-CNeYi06w.js} +0 -0
  146. /package/client/dist/assets/{jira-pRaL7JQo2.js → jira-pRaL7JQo.js} +0 -0
  147. /package/client/dist/assets/{jobs-B-sDHEay2.js → jobs-B-sDHEay.js} +0 -0
  148. /package/client/dist/assets/{jobs-DzSIR8V62.js → jobs-DzSIR8V6.js} +0 -0
  149. /package/client/dist/assets/{loops-Crc9It8o2.js → loops-Crc9It8o.js} +0 -0
  150. /package/client/dist/assets/{loops-DH2hZcbK2.js → loops-DH2hZcbK.js} +0 -0
  151. /package/client/dist/assets/{loops-DS5TmvIU2.js → loops-DS5TmvIU.js} +0 -0
  152. /package/client/dist/assets/{mcp-Czd_iyp02.js → mcp-Czd_iyp0.js} +0 -0
  153. /package/client/dist/assets/{mcp-D4ZjtIx32.js → mcp-D4ZjtIx3.js} +0 -0
  154. /package/client/dist/assets/{mcp-DIfBUv2D2.js → mcp-DIfBUv2D.js} +0 -0
  155. /package/client/dist/assets/{nav-BRJBxaGt2.js → nav-BRJBxaGt.js} +0 -0
  156. /package/client/dist/assets/{nav-CNskRJKQ2.js → nav-CNskRJKQ.js} +0 -0
  157. /package/client/dist/assets/{nav-TvumAFD22.js → nav-TvumAFD2.js} +0 -0
  158. /package/client/dist/assets/{settings-DAZqjX2a2.js → settings-DAZqjX2a.js} +0 -0
  159. /package/client/dist/assets/{settings-DRh39v1S2.js → settings-DRh39v1S.js} +0 -0
  160. /package/client/dist/assets/{settings-Mj1OJbfd2.js → settings-Mj1OJbfd.js} +0 -0
  161. /package/client/dist/assets/{specs-BcyxaCwf2.js → specs-BcyxaCwf.js} +0 -0
  162. /package/client/dist/assets/{specs-CLZKysq-2.js → specs-CLZKysq-.js} +0 -0
  163. /package/client/dist/assets/{specs-dV3Mq5Mu2.js → specs-dV3Mq5Mu.js} +0 -0
  164. /package/client/dist/assets/{terminal-C2NAd7BW2.js → terminal-C2NAd7BW.js} +0 -0
  165. /package/client/dist/assets/{terminal-IahE4LW92.js → terminal-IahE4LW9.js} +0 -0
  166. /package/client/dist/assets/{terminal-QpXZU2Bn2.js → terminal-QpXZU2Bn.js} +0 -0
  167. /package/client/dist/assets/{tickets-Bs37scwY2.js → tickets-Bs37scwY.js} +0 -0
  168. /package/client/dist/assets/{tickets-DQp7JS2R2.js → tickets-DQp7JS2R.js} +0 -0
  169. /package/client/dist/assets/{tickets-DpmdE8tv2.js → tickets-DpmdE8tv.js} +0 -0
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BUILDER_SPEC_HEADINGS = void 0;
4
+ exports.analyzeBuilderSpecBatch = analyzeBuilderSpecBatch;
5
+ exports.firstBuilderSpecQualityDetail = firstBuilderSpecQualityDetail;
6
+ exports.analyzeTypedBuilderSpecs = analyzeTypedBuilderSpecs;
7
+ exports.BUILDER_SPEC_HEADINGS = [
8
+ 'Problem Statement',
9
+ 'Proposed Solution',
10
+ 'Out of Scope',
11
+ 'Technical Considerations',
12
+ 'Estimated Complexity',
13
+ ];
14
+ const VALID_KINDS = new Set(['scaffold', 'feature', 'verification']);
15
+ const VALID_PRIORITIES = new Set(['critical', 'high', 'medium', 'low']);
16
+ const PLACEHOLDER_CRITERION = /^(?:todo|tbd|n\/?a|works?|test(?: it)?|criterion|acceptance criterion)$/i;
17
+ function issue(issues, specIndex, field, code, message) {
18
+ issues.push({ specIndex, field, code, message });
19
+ }
20
+ function record(value) {
21
+ return value && typeof value === 'object' && !Array.isArray(value)
22
+ ? value
23
+ : null;
24
+ }
25
+ function normalized(value) {
26
+ return value.trim().toLocaleLowerCase('en-US');
27
+ }
28
+ function h2Sections(description) {
29
+ const matches = [...description.matchAll(/^##[ \t]+(.+?)[ \t]*$/gm)];
30
+ const headings = matches.map((match) => match[1].trim());
31
+ const bodies = new Map();
32
+ matches.forEach((match, index) => {
33
+ const start = (match.index ?? 0) + match[0].length;
34
+ const end = index + 1 < matches.length ? (matches[index + 1].index ?? description.length) : description.length;
35
+ bodies.set(headings[index], description.slice(start, end).trim());
36
+ });
37
+ return { headings, bodies };
38
+ }
39
+ function bulletCount(body) {
40
+ return body.split(/\r?\n/).filter((line) => /^\s*[-*+]\s+\S/.test(line)).length;
41
+ }
42
+ /**
43
+ * Strict readiness gate for detailed Project Builder specs. Parsing stays
44
+ * permissive so interview/stream snapshots and old persisted blueprints remain
45
+ * readable; this gate runs only before a batch can mutate project state.
46
+ */
47
+ function analyzeBuilderSpecBatch(input, options) {
48
+ const issues = [];
49
+ const label = options.milestoneLabel.trim().toUpperCase();
50
+ if (input.specsComplete !== true) {
51
+ issue(issues, null, 'specsComplete', 'batch_incomplete', 'generation is not marked complete');
52
+ }
53
+ if (input.specs.length < options.minSpecs || input.specs.length > options.maxSpecs) {
54
+ issue(issues, null, 'm1Specs', 'spec_count', `requires ${options.minSpecs}-${options.maxSpecs} specs (received ${input.specs.length})`);
55
+ }
56
+ const titles = new Map();
57
+ input.specs.forEach((raw, specIndex) => {
58
+ const spec = record(raw);
59
+ const humanIndex = specIndex + 1;
60
+ if (!spec) {
61
+ issue(issues, specIndex, 'spec', 'invalid_shape', `spec ${humanIndex} must be an object`);
62
+ return;
63
+ }
64
+ const kind = typeof spec.kind === 'string' ? spec.kind : '';
65
+ if (!VALID_KINDS.has(kind)) {
66
+ issue(issues, specIndex, 'kind', 'invalid_kind', `spec ${humanIndex} kind must be scaffold, feature, or verification`);
67
+ }
68
+ if (options.requireScaffold && specIndex === 0 && kind !== 'scaffold') {
69
+ issue(issues, specIndex, 'kind', 'scaffold_first', 'spec 1 must be the project scaffold');
70
+ }
71
+ if (options.requireScaffold && specIndex > 0 && kind === 'scaffold') {
72
+ issue(issues, specIndex, 'kind', 'duplicate_scaffold', `spec ${humanIndex} cannot be another scaffold`);
73
+ }
74
+ const title = typeof spec.title === 'string' ? spec.title.trim() : '';
75
+ if (!title) {
76
+ issue(issues, specIndex, 'title', 'required', `spec ${humanIndex} title is required`);
77
+ }
78
+ else {
79
+ const key = normalized(title);
80
+ const prior = titles.get(key);
81
+ if (prior !== undefined) {
82
+ issue(issues, specIndex, 'title', 'duplicate', `spec ${humanIndex} duplicates spec ${prior + 1} title`);
83
+ }
84
+ else {
85
+ titles.set(key, specIndex);
86
+ }
87
+ }
88
+ const shortSummary = typeof spec.shortSummary === 'string' ? spec.shortSummary.trim() : '';
89
+ if (!shortSummary) {
90
+ issue(issues, specIndex, 'shortSummary', 'required', `spec ${humanIndex} shortSummary is required`);
91
+ }
92
+ else if (shortSummary.length > 240 || /[\r\n]/.test(shortSummary)) {
93
+ issue(issues, specIndex, 'shortSummary', 'invalid_length', `spec ${humanIndex} shortSummary must be one line and at most 240 characters`);
94
+ }
95
+ const priority = typeof spec.priority === 'string' ? spec.priority : '';
96
+ if (!VALID_PRIORITIES.has(priority)) {
97
+ issue(issues, specIndex, 'priority', 'invalid_priority', `spec ${humanIndex} priority must be critical, high, medium, or low`);
98
+ }
99
+ const labels = Array.isArray(spec.labels)
100
+ ? spec.labels.filter((value) => typeof value === 'string').map((value) => value.trim()).filter(Boolean)
101
+ : [];
102
+ const normalizedLabels = new Set(labels.map((value) => value.toUpperCase()));
103
+ if (!normalizedLabels.has(label)) {
104
+ issue(issues, specIndex, 'labels', 'milestone_label', `spec ${humanIndex} labels must include ${label}`);
105
+ }
106
+ if (labels.every((value) => value.toUpperCase() === label)) {
107
+ issue(issues, specIndex, 'labels', 'domain_label', `spec ${humanIndex} requires at least one domain label besides ${label}`);
108
+ }
109
+ const description = typeof spec.description === 'string' ? spec.description.trim() : '';
110
+ const sections = h2Sections(description);
111
+ if (!/^## Problem Statement(?:\r?\n|$)/.test(description)
112
+ || sections.headings.length !== exports.BUILDER_SPEC_HEADINGS.length
113
+ || sections.headings.some((heading, index) => heading !== exports.BUILDER_SPEC_HEADINGS[index])) {
114
+ issue(issues, specIndex, 'description', 'canonical_sections', `spec ${humanIndex} description must contain exactly the five canonical sections in order`);
115
+ }
116
+ for (const heading of exports.BUILDER_SPEC_HEADINGS) {
117
+ if (!(sections.bodies.get(heading) ?? '').trim()) {
118
+ issue(issues, specIndex, 'description', 'empty_section', `spec ${humanIndex} section "${heading}" cannot be empty`);
119
+ }
120
+ }
121
+ for (const heading of ['Out of Scope', 'Technical Considerations']) {
122
+ const body = sections.bodies.get(heading) ?? '';
123
+ if (body && bulletCount(body) < 2) {
124
+ issue(issues, specIndex, 'description', 'section_bullets', `spec ${humanIndex} section "${heading}" requires at least two bullets`);
125
+ }
126
+ }
127
+ const complexity = sections.bodies.get('Estimated Complexity') ?? '';
128
+ if (complexity && !/^(?:Low|Medium|High|Very High)\s*(?:[—\-:]\s*)\S/i.test(complexity)) {
129
+ issue(issues, specIndex, 'description', 'complexity_format', `spec ${humanIndex} Estimated Complexity needs a level and justification`);
130
+ }
131
+ if (options.requireScaffold && specIndex === 0 && description && !/\bREADME\b/i.test(description)) {
132
+ issue(issues, specIndex, 'description', 'scaffold_readme', 'spec 1 must note that the repository already contains a README');
133
+ }
134
+ const criteria = Array.isArray(spec.acceptanceCriteria) ? spec.acceptanceCriteria : [];
135
+ if (criteria.length < 4 || criteria.length > 10) {
136
+ issue(issues, specIndex, 'acceptanceCriteria', 'criteria_count', `spec ${humanIndex} acceptanceCriteria requires 4-10 items`);
137
+ }
138
+ const seenCriteria = new Set();
139
+ criteria.forEach((criterion, criterionIndex) => {
140
+ const text = typeof criterion === 'string' ? criterion.trim() : '';
141
+ if (text.length < 10 || PLACEHOLDER_CRITERION.test(text)) {
142
+ issue(issues, specIndex, 'acceptanceCriteria', 'criterion_quality', `spec ${humanIndex} criterion ${criterionIndex + 1} must be a concrete testable outcome`);
143
+ return;
144
+ }
145
+ const key = normalized(text);
146
+ if (seenCriteria.has(key)) {
147
+ issue(issues, specIndex, 'acceptanceCriteria', 'duplicate_criterion', `spec ${humanIndex} criterion ${criterionIndex + 1} is duplicated`);
148
+ }
149
+ seenCriteria.add(key);
150
+ });
151
+ const dependency = spec.dependsOnIndex;
152
+ if (dependency !== undefined && (!Number.isInteger(dependency) || dependency < 0 || dependency >= specIndex)) {
153
+ issue(issues, specIndex, 'dependsOnIndex', 'invalid_dependency', `spec ${humanIndex} dependency must point to an earlier spec`);
154
+ }
155
+ if (options.requireScaffold && specIndex === 0 && dependency !== undefined) {
156
+ issue(issues, specIndex, 'dependsOnIndex', 'scaffold_dependency', 'spec 1 scaffold must not declare a dependency');
157
+ }
158
+ });
159
+ return { valid: issues.length === 0, issues };
160
+ }
161
+ function firstBuilderSpecQualityDetail(report) {
162
+ return report.issues[0]?.message;
163
+ }
164
+ /** Typed convenience for already-coerced callers and tests. */
165
+ function analyzeTypedBuilderSpecs(specs, specsComplete, options) {
166
+ return analyzeBuilderSpecBatch({ specs, specsComplete }, options);
167
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBlueprintConversation = createBlueprintConversation;
4
+ exports.getBlueprintConversation = getBlueprintConversation;
5
+ exports.listBlueprintConversations = listBlueprintConversations;
6
+ exports.updateBlueprintConversation = updateBlueprintConversation;
7
+ exports.deleteBlueprintConversation = deleteBlueprintConversation;
8
+ exports.addBlueprintMessage = addBlueprintMessage;
9
+ exports.listBlueprintMessages = listBlueprintMessages;
10
+ const crypto_1 = require("crypto");
11
+ function createBlueprintConversation(db, input = {}) {
12
+ const id = (0, crypto_1.randomUUID)();
13
+ db.prepare('INSERT INTO blueprint_conversations (id, provider, model) VALUES (?, ?, ?)').run(id, input.provider ?? 'claude', input.model ?? null);
14
+ return getBlueprintConversation(db, id);
15
+ }
16
+ function getBlueprintConversation(db, id) {
17
+ return db.prepare('SELECT * FROM blueprint_conversations WHERE id = ?').get(id);
18
+ }
19
+ function listBlueprintConversations(db, limit = 50) {
20
+ return db
21
+ .prepare('SELECT * FROM blueprint_conversations ORDER BY updated_at DESC LIMIT ?')
22
+ .all(limit);
23
+ }
24
+ /** Patch a conversation. Only provided fields change; `updated_at` always bumps. */
25
+ function updateBlueprintConversation(db, id, patch) {
26
+ const sets = [];
27
+ const vals = [];
28
+ for (const [k, v] of Object.entries(patch)) {
29
+ sets.push(`${k} = ?`);
30
+ vals.push(v);
31
+ }
32
+ sets.push("updated_at = datetime('now')");
33
+ db.prepare(`UPDATE blueprint_conversations SET ${sets.join(', ')} WHERE id = ?`).run(...vals, id);
34
+ return getBlueprintConversation(db, id);
35
+ }
36
+ function deleteBlueprintConversation(db, id) {
37
+ db.prepare('DELETE FROM blueprint_conversations WHERE id = ?').run(id);
38
+ }
39
+ function addBlueprintMessage(db, input) {
40
+ const id = (0, crypto_1.randomUUID)();
41
+ db.prepare('INSERT INTO blueprint_messages (id, conversation_id, role, content) VALUES (?, ?, ?, ?)').run(id, input.conversationId, input.role, input.content);
42
+ db.prepare("UPDATE blueprint_conversations SET updated_at = datetime('now') WHERE id = ?").run(input.conversationId);
43
+ return db.prepare('SELECT * FROM blueprint_messages WHERE id = ?').get(id);
44
+ }
45
+ function listBlueprintMessages(db, conversationId) {
46
+ return db
47
+ .prepare('SELECT * FROM blueprint_messages WHERE conversation_id = ? ORDER BY created_at, rowid')
48
+ .all(conversationId);
49
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ // Shared Blueprint schema types for the Project Builder (design D2).
3
+ //
4
+ // A Blueprint is the full structured snapshot the day-0 Builder conversation
5
+ // converges on: the five interview dimensions (product, core flow, platform,
6
+ // stack, M1 constraints via milestones) plus the detailed Milestone-1 specs.
7
+ // It travels as fenced ```blueprint-draft JSON blocks during the conversation
8
+ // and persists as `<workspace>/.specrails/blueprint.json` after the commit.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.M1_SPECS_MAX = exports.M1_SPECS_MIN = exports.BLUEPRINT_VERSION = void 0;
11
+ exports.isMilestoneStatus = isMilestoneStatus;
12
+ exports.isBlueprintSpecKind = isBlueprintSpecKind;
13
+ exports.isBlueprintSpecPriority = isBlueprintSpecPriority;
14
+ exports.BLUEPRINT_VERSION = 1;
15
+ /** Milestone-1 walking-skeleton bounds (proposal: 5–10 specs, scaffold first). */
16
+ exports.M1_SPECS_MIN = 5;
17
+ exports.M1_SPECS_MAX = 10;
18
+ const MILESTONE_STATUSES = new Set(['planned', 'committed', 'done']);
19
+ const SPEC_KINDS = new Set(['scaffold', 'feature', 'verification']);
20
+ const SPEC_PRIORITIES = new Set(['critical', 'high', 'medium', 'low']);
21
+ function isMilestoneStatus(value) {
22
+ return typeof value === 'string' && MILESTONE_STATUSES.has(value);
23
+ }
24
+ function isBlueprintSpecKind(value) {
25
+ return typeof value === 'string' && SPEC_KINDS.has(value);
26
+ }
27
+ function isBlueprintSpecPriority(value) {
28
+ return typeof value === 'string' && SPEC_PRIORITIES.has(value);
29
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.builderCwdPath = builderCwdPath;
7
+ exports.ensureBuilderCwd = ensureBuilderCwd;
8
+ exports.removeBuilderCwd = removeBuilderCwd;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const os_1 = __importDefault(require("os"));
12
+ const blueprint_operator_prompt_1 = require("./blueprint-operator-prompt");
13
+ // ─── Project Builder working directory (add-project-builder D1) ──────────────
14
+ //
15
+ // Day-0 Builder turns spawn the AI CLI from an app-owned cwd so no project
16
+ // CLAUDE.md is auto-loaded (there IS no project yet). Unlike agent-cwd there is
17
+ // never a `./project` symlink and never any MCP configuration — the Builder
18
+ // only converses and emits fenced blueprint-draft JSON.
19
+ function homeDir() {
20
+ return process.env.SPECRAILS_REGISTRY_HOME || os_1.default.homedir();
21
+ }
22
+ function builderCwdPath() {
23
+ return path_1.default.join(homeDir(), '.specrails', 'builder-cwd');
24
+ }
25
+ const INSTRUCTION_FILES = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
26
+ /**
27
+ * Ensures the Builder cwd exists with instruction files. App-owned files are
28
+ * always (re)written so prompt updates take effect immediately. Idempotent;
29
+ * safe to call on every turn.
30
+ */
31
+ function ensureBuilderCwd() {
32
+ const dir = builderCwdPath();
33
+ fs_1.default.mkdirSync(dir, { recursive: true, mode: 0o700 });
34
+ try {
35
+ fs_1.default.chmodSync(dir, 0o700);
36
+ }
37
+ catch { /* best-effort on platforms without chmod */ }
38
+ for (const name of INSTRUCTION_FILES) {
39
+ fs_1.default.writeFileSync(path_1.default.join(dir, name), blueprint_operator_prompt_1.BUILDER_INSTRUCTIONS, 'utf-8');
40
+ }
41
+ // Defence-in-depth: the Builder must never inherit MCP config from a stale
42
+ // file someone dropped here — this dir is wholly app-owned.
43
+ try {
44
+ fs_1.default.unlinkSync(path_1.default.join(dir, '.mcp.json'));
45
+ }
46
+ catch { /* absent */ }
47
+ return dir;
48
+ }
49
+ /** Removes the Builder cwd (full teardown / reset). Best-effort. */
50
+ function removeBuilderCwd() {
51
+ try {
52
+ fs_1.default.rmSync(builderCwdPath(), { recursive: true, force: true });
53
+ }
54
+ catch {
55
+ /* best-effort */
56
+ }
57
+ }