speckit-assistant 0.1.2 → 0.1.4

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 (175) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +59 -23
  3. package/.next/app-path-routes-manifest.json +7 -2
  4. package/.next/build-manifest.json +5 -5
  5. package/.next/cache/next-devtools-config.json +1 -0
  6. package/.next/cache/webpack/client-development/0.pack.gz +0 -0
  7. package/.next/cache/webpack/client-development/index.pack.gz +0 -0
  8. package/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/.next/cache/webpack/client-production/1.pack +0 -0
  10. package/.next/cache/webpack/client-production/11.pack +0 -0
  11. package/.next/cache/webpack/client-production/12.pack +0 -0
  12. package/.next/cache/webpack/client-production/13.pack +0 -0
  13. package/.next/cache/webpack/client-production/15.pack +0 -0
  14. package/.next/cache/webpack/client-production/16.pack +0 -0
  15. package/.next/cache/webpack/client-production/17.pack +0 -0
  16. package/.next/cache/webpack/client-production/18.pack +0 -0
  17. package/.next/cache/webpack/client-production/19.pack +0 -0
  18. package/.next/cache/webpack/client-production/2.pack +0 -0
  19. package/.next/cache/webpack/client-production/20.pack +0 -0
  20. package/.next/cache/webpack/client-production/21.pack +0 -0
  21. package/.next/cache/webpack/client-production/22.pack +0 -0
  22. package/.next/cache/webpack/client-production/23.pack +0 -0
  23. package/.next/cache/webpack/client-production/24.pack +0 -0
  24. package/.next/cache/webpack/client-production/25.pack +0 -0
  25. package/.next/cache/webpack/client-production/26.pack +0 -0
  26. package/.next/cache/webpack/client-production/27.pack +0 -0
  27. package/.next/cache/webpack/client-production/28.pack +0 -0
  28. package/.next/cache/webpack/client-production/29.pack +0 -0
  29. package/.next/cache/webpack/client-production/3.pack +0 -0
  30. package/.next/cache/webpack/client-production/30.pack +0 -0
  31. package/.next/cache/webpack/client-production/31.pack +0 -0
  32. package/.next/cache/webpack/client-production/32.pack +0 -0
  33. package/.next/cache/webpack/client-production/33.pack +0 -0
  34. package/.next/cache/webpack/client-production/34.pack +0 -0
  35. package/.next/cache/webpack/client-production/4.pack +0 -0
  36. package/.next/cache/webpack/client-production/6.pack +0 -0
  37. package/.next/cache/webpack/client-production/7.pack +0 -0
  38. package/.next/cache/webpack/client-production/8.pack +0 -0
  39. package/.next/cache/webpack/client-production/9.pack +0 -0
  40. package/.next/cache/webpack/client-production/index.pack +0 -0
  41. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  42. package/.next/cache/webpack/server-production/0.pack +0 -0
  43. package/.next/cache/webpack/server-production/1.pack +0 -0
  44. package/.next/cache/webpack/server-production/10.pack +0 -0
  45. package/.next/cache/webpack/server-production/11.pack +0 -0
  46. package/.next/cache/webpack/server-production/12.pack +0 -0
  47. package/.next/cache/webpack/server-production/13.pack +0 -0
  48. package/.next/cache/webpack/server-production/14.pack +0 -0
  49. package/.next/cache/webpack/server-production/16.pack +0 -0
  50. package/.next/cache/webpack/server-production/18.pack +0 -0
  51. package/.next/cache/webpack/server-production/19.pack +0 -0
  52. package/.next/cache/webpack/server-production/2.pack +0 -0
  53. package/.next/cache/webpack/server-production/20.pack +0 -0
  54. package/.next/cache/webpack/server-production/21.pack +0 -0
  55. package/.next/cache/webpack/server-production/23.pack +0 -0
  56. package/.next/cache/webpack/server-production/24.pack +0 -0
  57. package/.next/cache/webpack/server-production/25.pack +0 -0
  58. package/.next/cache/webpack/server-production/26.pack +0 -0
  59. package/.next/cache/webpack/server-production/27.pack +0 -0
  60. package/.next/cache/webpack/server-production/28.pack +0 -0
  61. package/.next/cache/webpack/server-production/29.pack +0 -0
  62. package/.next/cache/webpack/server-production/3.pack +0 -0
  63. package/.next/cache/webpack/server-production/30.pack +0 -0
  64. package/.next/cache/webpack/server-production/31.pack +0 -0
  65. package/.next/cache/webpack/server-production/4.pack +0 -0
  66. package/.next/cache/webpack/server-production/5.pack +0 -0
  67. package/.next/cache/webpack/server-production/6.pack +0 -0
  68. package/.next/cache/webpack/server-production/7.pack +0 -0
  69. package/.next/cache/webpack/server-production/9.pack +0 -0
  70. package/.next/cache/webpack/server-production/index.pack +0 -0
  71. package/.next/cache/webpack/server-production/index.pack.old +0 -0
  72. package/.next/react-loadable-manifest.json +8 -1
  73. package/.next/required-server-files.json +5 -2
  74. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/_not-found.html +1 -1
  76. package/.next/server/app/_not-found.rsc +2 -2
  77. package/.next/server/app/api/feature/route.js +1 -1
  78. package/.next/server/app/api/feature/route.js.nft.json +1 -1
  79. package/.next/server/app/api/feature/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/file/route.js +1 -1
  81. package/.next/server/app/api/file/route.js.nft.json +1 -1
  82. package/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/git/route.js +1 -0
  84. package/.next/server/app/api/git/route.js.nft.json +1 -0
  85. package/.next/server/app/api/git/route_client-reference-manifest.js +1 -0
  86. package/.next/server/app/api/personas/route.js +1 -0
  87. package/.next/server/app/api/personas/route.js.nft.json +1 -0
  88. package/.next/server/app/api/personas/route_client-reference-manifest.js +1 -0
  89. package/.next/server/app/api/phase/route.js +3 -3
  90. package/.next/server/app/api/phase/route.js.nft.json +1 -1
  91. package/.next/server/app/api/phase/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/api/state/route.js +1 -1
  93. package/.next/server/app/api/state/route.js.nft.json +1 -1
  94. package/.next/server/app/api/state/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/state/watch/route.js +2 -2
  96. package/.next/server/app/api/state/watch/route.js.nft.json +1 -1
  97. package/.next/server/app/api/state/watch/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/task/route.js +1 -1
  99. package/.next/server/app/api/task/route.js.nft.json +1 -1
  100. package/.next/server/app/api/task/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/terminal/input/route.js +1 -0
  102. package/.next/server/app/api/terminal/input/route.js.nft.json +1 -0
  103. package/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -0
  104. package/.next/server/app/api/terminal/resize/route.js +1 -0
  105. package/.next/server/app/api/terminal/resize/route.js.nft.json +1 -0
  106. package/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -0
  107. package/.next/server/app/api/terminal/stream/route.js +3 -0
  108. package/.next/server/app/api/terminal/stream/route.js.nft.json +1 -0
  109. package/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -0
  110. package/.next/server/app/index.html +1 -1
  111. package/.next/server/app/index.rsc +4 -4
  112. package/.next/server/app/page.js +26 -7
  113. package/.next/server/app/page_client-reference-manifest.js +1 -1
  114. package/.next/server/app-paths-manifest.json +7 -2
  115. package/.next/server/chunks/607.js +2 -2
  116. package/.next/server/chunks/897.js +13 -7
  117. package/.next/server/middleware-build-manifest.js +1 -1
  118. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  119. package/.next/server/pages/404.html +1 -1
  120. package/.next/server/pages/500.html +1 -1
  121. package/.next/static/ELGpn5csIINP2EpeEMCo6/_buildManifest.js +1 -0
  122. package/.next/static/chunks/111acf76-29d5e5905666f1e0.js +18 -0
  123. package/.next/static/chunks/343.91af0d46f6df0f05.js +1 -0
  124. package/.next/static/chunks/873-a995367ae371a5e4.js +1 -0
  125. package/.next/static/chunks/app/api/feature/route-b4fbc89d13fef983.js +1 -0
  126. package/.next/static/chunks/app/api/file/route-b4fbc89d13fef983.js +1 -0
  127. package/.next/static/chunks/app/api/git/route-b4fbc89d13fef983.js +1 -0
  128. package/.next/static/chunks/app/api/personas/route-b4fbc89d13fef983.js +1 -0
  129. package/.next/static/chunks/app/api/phase/route-b4fbc89d13fef983.js +1 -0
  130. package/.next/static/chunks/app/api/state/route-b4fbc89d13fef983.js +1 -0
  131. package/.next/static/chunks/app/api/state/watch/route-b4fbc89d13fef983.js +1 -0
  132. package/.next/static/chunks/app/api/task/route-b4fbc89d13fef983.js +1 -0
  133. package/.next/static/chunks/app/api/terminal/input/route-b4fbc89d13fef983.js +1 -0
  134. package/.next/static/chunks/app/api/terminal/resize/route-b4fbc89d13fef983.js +1 -0
  135. package/.next/static/chunks/app/api/terminal/stream/route-b4fbc89d13fef983.js +1 -0
  136. package/.next/static/chunks/app/page-63ac4ff563941667.js +1 -0
  137. package/.next/static/chunks/webpack-c997c7bd3e8f7cdf.js +1 -0
  138. package/.next/static/css/31544403e38a69f8.css +3 -0
  139. package/.next/static/css/{008a05b0ad6b854a.css → af3f40f14f702ff7.css} +1 -1
  140. package/.next/trace +3 -3
  141. package/.next/types/app/api/git/route.ts +347 -0
  142. package/.next/types/app/api/personas/route.ts +347 -0
  143. package/.next/types/app/api/terminal/input/route.ts +347 -0
  144. package/.next/types/app/api/terminal/resize/route.ts +347 -0
  145. package/.next/types/app/api/terminal/stream/route.ts +347 -0
  146. package/.next/types/routes.d.ts +6 -1
  147. package/.next/types/validator.ts +45 -0
  148. package/bin/adapters/primary/api/terminalManager.js +92 -0
  149. package/bin/adapters/secondary/agent/ProcessAgentRunner.js +124 -33
  150. package/bin/adapters/secondary/fs/FSWorkspaceRepository.js +40 -5
  151. package/bin/adapters/secondary/pty/ptyLoader.js +128 -0
  152. package/bin/app/api/git/route.js +156 -0
  153. package/bin/app/api/personas/route.js +76 -0
  154. package/bin/app/api/phase/route.js +18 -5
  155. package/bin/app/api/state/watch/route.js +7 -2
  156. package/bin/app/api/terminal/input/route.js +18 -0
  157. package/bin/app/api/terminal/resize/route.js +18 -0
  158. package/bin/app/api/terminal/stream/route.js +44 -0
  159. package/bin/domain/models/personas.js +65 -0
  160. package/bin/domain/services/WorkflowService.js +79 -5
  161. package/next.config.mjs +15 -0
  162. package/package.json +10 -2
  163. package/.next/static/QraS9_bCyr2_QD3cQIBT8/_buildManifest.js +0 -1
  164. package/.next/static/chunks/590-a6568595ecd2a994.js +0 -1
  165. package/.next/static/chunks/app/api/feature/route-bb3c1a82e892ab58.js +0 -1
  166. package/.next/static/chunks/app/api/file/route-bb3c1a82e892ab58.js +0 -1
  167. package/.next/static/chunks/app/api/phase/route-bb3c1a82e892ab58.js +0 -1
  168. package/.next/static/chunks/app/api/state/route-bb3c1a82e892ab58.js +0 -1
  169. package/.next/static/chunks/app/api/state/watch/route-bb3c1a82e892ab58.js +0 -1
  170. package/.next/static/chunks/app/api/task/route-bb3c1a82e892ab58.js +0 -1
  171. package/.next/static/chunks/app/page-8a5248f7704cde29.js +0 -1
  172. package/.next/static/chunks/webpack-c460f8e58a9e9eff.js +0 -1
  173. package/.next/static/css/6fd2e11db3a59771.css +0 -3
  174. package/next.config.ts +0 -13
  175. /package/.next/static/{QraS9_bCyr2_QD3cQIBT8 → ELGpn5csIINP2EpeEMCo6}/_ssgManifest.js +0 -0
@@ -6,7 +6,7 @@ const di_1 = require("../../../adapters/di");
6
6
  const utils_1 = require("../../../adapters/primary/api/utils");
7
7
  async function POST(req) {
8
8
  try {
9
- const { action, phase, featureName, agentConfig, prompt } = await req.json();
9
+ const { action, phase, featureName, agentConfig, prompt, text, cols, rows, personaId, personas } = await req.json();
10
10
  if (!action || !phase) {
11
11
  return server_1.NextResponse.json({ error: 'Missing action or phase' }, { status: 400 });
12
12
  }
@@ -19,7 +19,19 @@ async function POST(req) {
19
19
  const state = await di_1.workflowService.discardPhase(workspacePath, phase, featureName);
20
20
  return server_1.NextResponse.json(state);
21
21
  }
22
- if (action === 'run') {
22
+ if (action === 'input') {
23
+ const success = await di_1.workflowService.writeStdin(phase, featureName, text, personaId);
24
+ return server_1.NextResponse.json({ success });
25
+ }
26
+ if (action === 'resize') {
27
+ const success = await di_1.workflowService.resize(phase, featureName, cols, rows, personaId);
28
+ return server_1.NextResponse.json({ success });
29
+ }
30
+ if (action === 'stop') {
31
+ const success = await di_1.workflowService.stop(phase, featureName, personaId);
32
+ return server_1.NextResponse.json({ success });
33
+ }
34
+ if (action === 'run' || action === 'run-gate') {
23
35
  const encoder = new TextEncoder();
24
36
  const customReadableStream = new ReadableStream({
25
37
  async start(controller) {
@@ -27,9 +39,10 @@ async function POST(req) {
27
39
  controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
28
40
  };
29
41
  try {
30
- const finalState = await di_1.workflowService.runPhase(workspacePath, phase, featureName, agentConfig, prompt, (text) => {
31
- sendEvent('log', { text });
32
- });
42
+ const onData = (t) => sendEvent('log', { text: t });
43
+ const finalState = action === 'run-gate'
44
+ ? await di_1.workflowService.runImplementationGate(workspacePath, featureName, agentConfig, (personas ?? []), onData)
45
+ : await di_1.workflowService.runPhase(workspacePath, phase, featureName, agentConfig, prompt, onData, personas);
33
46
  sendEvent('done', { state: finalState });
34
47
  controller.close();
35
48
  }
@@ -71,8 +71,13 @@ async function GET() {
71
71
  };
72
72
  // Watch all change events (add, change, unlink)
73
73
  watcher.on('all', (event, filePath) => {
74
- // Skip changes inside the internal runtime state directory
75
- if (filePath.includes('.runtime') || filePath.includes('.git'))
74
+ if (filePath.includes('.git'))
75
+ return;
76
+ // The internal runtime dir is otherwise noisy, but workflow-state.json
77
+ // holds the phase statuses (e.g. 'running'), so let that one through —
78
+ // it's what tells the Kanban a phase is executing. getWorkflowState is
79
+ // read-only, so re-emitting on it cannot create a write/watch loop.
80
+ if (filePath.includes('.runtime') && !filePath.includes('workflow-state.json'))
76
81
  return;
77
82
  sendUpdate(filePath);
78
83
  });
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const server_1 = require("next/server");
5
+ const terminalManager_1 = require("../../../../adapters/primary/api/terminalManager");
6
+ async function POST(req) {
7
+ try {
8
+ const { text } = await req.json();
9
+ if (text === undefined) {
10
+ return server_1.NextResponse.json({ error: 'Missing text input' }, { status: 400 });
11
+ }
12
+ const success = terminalManager_1.terminalManager.writeInput(text);
13
+ return server_1.NextResponse.json({ success });
14
+ }
15
+ catch (err) {
16
+ return server_1.NextResponse.json({ error: err.message }, { status: 500 });
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const server_1 = require("next/server");
5
+ const terminalManager_1 = require("../../../../adapters/primary/api/terminalManager");
6
+ async function POST(req) {
7
+ try {
8
+ const { cols, rows } = await req.json();
9
+ if (typeof cols !== 'number' || typeof rows !== 'number') {
10
+ return server_1.NextResponse.json({ error: 'cols and rows must be numbers' }, { status: 400 });
11
+ }
12
+ const success = terminalManager_1.terminalManager.resize(cols, rows);
13
+ return server_1.NextResponse.json({ success });
14
+ }
15
+ catch (err) {
16
+ return server_1.NextResponse.json({ error: err.message }, { status: 500 });
17
+ }
18
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dynamic = void 0;
4
+ exports.GET = GET;
5
+ const terminalManager_1 = require("../../../../adapters/primary/api/terminalManager");
6
+ exports.dynamic = 'force-dynamic';
7
+ async function GET() {
8
+ const encoder = new TextEncoder();
9
+ let listener = null;
10
+ let interval = null;
11
+ const stream = new ReadableStream({
12
+ start(controller) {
13
+ listener = (data) => {
14
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ output: data })}\n\n`));
15
+ };
16
+ terminalManager_1.terminalManager.addListener(listener);
17
+ // Keep-alive heartbeat to prevent gateway timeouts
18
+ interval = setInterval(() => {
19
+ try {
20
+ controller.enqueue(encoder.encode(': heartbeat\n\n'));
21
+ }
22
+ catch {
23
+ // ignore
24
+ }
25
+ }, 15000);
26
+ },
27
+ cancel() {
28
+ if (interval) {
29
+ clearInterval(interval);
30
+ }
31
+ if (listener) {
32
+ terminalManager_1.terminalManager.removeListener(listener);
33
+ }
34
+ }
35
+ });
36
+ return new Response(stream, {
37
+ headers: {
38
+ 'Content-Type': 'text/event-stream',
39
+ 'Cache-Control': 'no-cache, no-transform',
40
+ 'Connection': 'keep-alive',
41
+ 'X-Accel-Buffering': 'no'
42
+ }
43
+ });
44
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_PERSONAS = exports.PERSONA_ORDER = void 0;
4
+ exports.personaReportPath = personaReportPath;
5
+ exports.orderPersonas = orderPersonas;
6
+ // Fixed execution order of the implementation review gate. Tech Lead runs last
7
+ // so it can aggregate the findings of the preceding personas and sign off.
8
+ exports.PERSONA_ORDER = ['qa', 'code-review', 'security', 'tech-lead'];
9
+ // Default persona registry. Each persona is backed by a Spec Kit extension
10
+ // slash command (see extensions/spec-kit-personas). Users can override the
11
+ // command or disable a persona from the Agent Config UI.
12
+ exports.DEFAULT_PERSONAS = [
13
+ {
14
+ id: 'qa',
15
+ label: 'QA',
16
+ command: '/speckit.review.qa',
17
+ enabled: true,
18
+ model: 'gemini-2.5-flash',
19
+ description: 'Automated quality assurance agent. Runs tests, verifies regression suite, and inspects validation logs.',
20
+ systemPrompt: 'You are the QA persona in a Spec-Driven Development cycle. Your goal is to run unit/integration tests and check that code changes do not break existing features. Output verification reports with a clear VERDICT: PASS or FAIL.',
21
+ capabilities: ['Runs Vitest/Jest test suites', 'Generates coverage reports', 'Checks edge cases in inputs', 'Identifies code regressions'],
22
+ tools: ['vitest', 'npm test', 'coverage-reporter', 'test-generator']
23
+ },
24
+ {
25
+ id: 'code-review',
26
+ label: 'Code Review',
27
+ command: '/speckit.review.code',
28
+ enabled: true,
29
+ model: 'gemini-2.5-pro',
30
+ description: 'Automated code standard and architectural reviewer. Validates SOLID patterns and code complexity.',
31
+ systemPrompt: 'You are the Code Reviewer persona. Analyze source code diffs for adherence to architectural standards, clean code practices (SOLID, DRY), and formatting. Output feedback with a final VERDICT: PASS or FAIL.',
32
+ capabilities: ['Analyzes design pattern compliance', 'Checks cyclomatic complexity', 'Identifies redundant code blocks', 'Ensures type safety'],
33
+ tools: ['eslint', 'typescript-compiler', 'complexity-analyzer', 'stylelint']
34
+ },
35
+ {
36
+ id: 'security',
37
+ label: 'Security',
38
+ command: '/speckit.review.security',
39
+ enabled: true,
40
+ model: 'claude-3-5-sonnet',
41
+ description: 'Automated security compliance agent. Scans for hardcoded credentials, dependency CVEs, and OWASP vulnerabilities.',
42
+ systemPrompt: 'You are the Security Auditor persona. Scan changes for exposed credentials, API keys, dependency vulnerabilities, injection flaws, and other OWASP Top 10 risks. Output a final VERDICT: PASS or FAIL.',
43
+ capabilities: ['Scans for exposed secrets', 'Identifies dependency CVEs', 'Detects injection vulnerabilities', 'Verifies authentication protocols'],
44
+ tools: ['git-secrets', 'npm audit', 'owasp-scanner', 'trivy']
45
+ },
46
+ {
47
+ id: 'tech-lead',
48
+ label: 'Tech Lead',
49
+ command: '/speckit.review.techlead',
50
+ enabled: true,
51
+ model: 'gemini-2.5-pro',
52
+ description: 'Tech Lead agent responsible for final signature sign-off. Aggregates reviews and signs the implementation gate.',
53
+ systemPrompt: 'You are the Tech Lead persona. You represent the final automated validation step. Aggregate findings from previous agents, confirm architecture completeness, and sign the gate. Output a final VERDICT: PASS or FAIL.',
54
+ capabilities: ['Aggregates reviews and signals', 'Confirms architecture patterns', 'Approves merge readiness', 'Performs final API signature verification'],
55
+ tools: ['git-merge-dryrun', 'dependency-checker', 'signature-verifier']
56
+ }
57
+ ];
58
+ // Relative path (from the feature dir) where a persona writes its review report.
59
+ function personaReportPath(featureName, id) {
60
+ return `specs/${featureName}/reviews/${id}.md`;
61
+ }
62
+ // Sort an arbitrary persona list into the canonical gate order.
63
+ function orderPersonas(personas) {
64
+ return [...personas].sort((a, b) => exports.PERSONA_ORDER.indexOf(a.id) - exports.PERSONA_ORDER.indexOf(b.id));
65
+ }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WorkflowService = void 0;
4
+ const personas_1 = require("../models/personas");
4
5
  const FEATURE_PHASES = [
5
6
  'specification',
6
7
  'clarification',
@@ -40,7 +41,7 @@ class WorkflowService {
40
41
  }
41
42
  return state;
42
43
  }
43
- async runPhase(workspacePath, phase, featureName, agentConfig, userPrompt, onData) {
44
+ async runPhase(workspacePath, phase, featureName, agentConfig, userPrompt, onData, personas) {
44
45
  const state = await this.workspaceRepo.getWorkflowState(workspacePath);
45
46
  const targetFeature = featureName || state.activeFeatureName;
46
47
  // Set status to running
@@ -50,12 +51,20 @@ class WorkflowService {
50
51
  const exitCode = await this.agentRunner.runPhase(workspacePath, phase, targetFeature, agentConfig, userPrompt, onData);
51
52
  // Re-read state in case files changed on disk during run
52
53
  const freshState = await this.workspaceRepo.getWorkflowState(workspacePath);
53
- if (exitCode === 0) {
54
- this.setPhaseStatus(freshState, phase, targetFeature, 'awaiting_review');
55
- }
56
- else {
54
+ if (exitCode !== 0) {
57
55
  this.setPhaseStatus(freshState, phase, targetFeature, 'idle');
56
+ await this.workspaceRepo.saveWorkflowState(workspacePath, freshState);
57
+ return freshState;
58
+ }
59
+ // Implementation: if a review gate is configured, the personas govern the
60
+ // final status (only reaching awaiting_review after Tech Lead signs off).
61
+ const enabledPersonas = (personas ?? []).filter(p => p.enabled);
62
+ if (phase === 'implementation' && targetFeature && enabledPersonas.length > 0) {
63
+ this.setPhaseStatus(freshState, phase, targetFeature, 'running');
64
+ await this.workspaceRepo.saveWorkflowState(workspacePath, freshState);
65
+ return this.runImplementationGate(workspacePath, targetFeature, agentConfig, personas, onData);
58
66
  }
67
+ this.setPhaseStatus(freshState, phase, targetFeature, 'awaiting_review');
59
68
  // Check if tasks are completed to auto-complete implementation
60
69
  if (phase === 'implementation') {
61
70
  await this.checkImplementationAutoReview(workspacePath, freshState, targetFeature);
@@ -70,6 +79,62 @@ class WorkflowService {
70
79
  throw err;
71
80
  }
72
81
  }
82
+ // Runs the implementation review gate: enabled personas execute sequentially
83
+ // (Tech Lead last). The gate stops at the first persona that fails, leaving
84
+ // the implementation phase 'running' (blocked). Only when every persona passes
85
+ // does the phase advance to 'awaiting_review' for human approval.
86
+ async runImplementationGate(workspacePath, featureName, agentConfig, personas, onData) {
87
+ const enabled = (0, personas_1.orderPersonas)(personas.filter(p => p.enabled));
88
+ const state = await this.workspaceRepo.getWorkflowState(workspacePath);
89
+ const implPhase = this.findPhase(state, 'implementation', featureName);
90
+ if (!implPhase)
91
+ return state;
92
+ implPhase.status = 'running';
93
+ implPhase.personas = enabled.map(p => ({
94
+ id: p.id,
95
+ status: 'idle',
96
+ reportPath: (0, personas_1.personaReportPath)(featureName, p.id),
97
+ }));
98
+ await this.workspaceRepo.saveWorkflowState(workspacePath, state);
99
+ for (const persona of enabled) {
100
+ const ps = implPhase.personas.find(x => x.id === persona.id);
101
+ ps.status = 'running';
102
+ await this.workspaceRepo.saveWorkflowState(workspacePath, state);
103
+ onData?.(`\r\n\x1b[36m=== Persona: ${persona.label} (${persona.command}) ===\x1b[0m\r\n`);
104
+ const exitCode = await this.agentRunner.runPersona(workspacePath, featureName, persona, agentConfig, onData);
105
+ const verdict = await this.resolvePersonaVerdict(workspacePath, featureName, persona.id, exitCode);
106
+ ps.status = verdict;
107
+ await this.workspaceRepo.saveWorkflowState(workspacePath, state);
108
+ if (verdict === 'failed') {
109
+ onData?.(`\r\n\x1b[31m✗ ${persona.label} bloqueou o gate — implementação permanece em revisão.\x1b[0m\r\n`);
110
+ return state;
111
+ }
112
+ onData?.(`\r\n\x1b[32m✓ ${persona.label} passou.\x1b[0m\r\n`);
113
+ }
114
+ implPhase.status = 'awaiting_review';
115
+ await this.workspaceRepo.saveWorkflowState(workspacePath, state);
116
+ onData?.(`\r\n\x1b[32m✓ Review gate completo — pronto para aprovação.\x1b[0m\r\n`);
117
+ return state;
118
+ }
119
+ // A persona's verdict is read from its report (specs/<feature>/reviews/<id>.md):
120
+ // an explicit "VERDICT: PASS|FAIL" marker wins; otherwise we fall back to the
121
+ // process exit code. This is robust across heterogeneous agent CLIs that may
122
+ // not control their exit code reliably.
123
+ async resolvePersonaVerdict(workspacePath, featureName, id, exitCode) {
124
+ let report = '';
125
+ try {
126
+ report = await this.workspaceRepo.readFile(workspacePath, (0, personas_1.personaReportPath)(featureName, id));
127
+ }
128
+ catch {
129
+ report = '';
130
+ }
131
+ const text = report.toUpperCase();
132
+ if (/VERDICT:\s*FAIL/.test(text))
133
+ return 'failed';
134
+ if (/VERDICT:\s*PASS/.test(text))
135
+ return 'passed';
136
+ return exitCode === 0 ? 'passed' : 'failed';
137
+ }
73
138
  async approvePhase(workspacePath, phase, featureName) {
74
139
  const state = await this.workspaceRepo.getWorkflowState(workspacePath);
75
140
  const targetFeature = featureName || state.activeFeatureName;
@@ -110,6 +175,15 @@ class WorkflowService {
110
175
  await this.workspaceRepo.saveWorkflowState(workspacePath, state);
111
176
  return state;
112
177
  }
178
+ async writeStdin(phase, featureName, text, personaId) {
179
+ return this.agentRunner.writeStdin(phase, featureName, text, personaId);
180
+ }
181
+ async resize(phase, featureName, cols, rows, personaId) {
182
+ return this.agentRunner.resize(phase, featureName, cols, rows, personaId);
183
+ }
184
+ async stop(phase, featureName, personaId) {
185
+ return this.agentRunner.stop(phase, featureName, personaId);
186
+ }
113
187
  // Helper methods
114
188
  findPhase(state, phase, featureName) {
115
189
  if (phase === 'constitution') {
@@ -0,0 +1,15 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ // node-pty is a native addon and must not be bundled by webpack; load it from
5
+ // node_modules at runtime on the server instead.
6
+ serverExternalPackages: ['node-pty'],
7
+ eslint: {
8
+ ignoreDuringBuilds: true,
9
+ },
10
+ typescript: {
11
+ ignoreBuildErrors: true,
12
+ }
13
+ };
14
+
15
+ export default nextConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speckit-assistant",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Visual orchestrator for Spec-Driven Development (SDD) — Next.js, ReactFlow and Hexagonal Architecture",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "files": [
17
17
  "bin",
18
18
  ".next",
19
- "next.config.ts"
19
+ "next.config.mjs"
20
20
  ],
21
21
  "keywords": [
22
22
  "sdd",
@@ -35,13 +35,21 @@
35
35
  "url": "https://github.com/dmux/speckit-assistant/issues"
36
36
  },
37
37
  "homepage": "https://github.com/dmux/speckit-assistant#readme",
38
+ "pnpm": {
39
+ "onlyBuiltDependencies": [
40
+ "node-pty"
41
+ ]
42
+ },
38
43
  "dependencies": {
44
+ "@xterm/addon-fit": "^0.11.0",
45
+ "@xterm/xterm": "^6.0.0",
39
46
  "@xyflow/react": "^12.3.5",
40
47
  "chokidar": "^5.0.0",
41
48
  "class-variance-authority": "^0.7.1",
42
49
  "clsx": "^2.1.1",
43
50
  "lucide-react": "^0.468.0",
44
51
  "next": "^15.1.0",
52
+ "node-pty": "^1.1.0",
45
53
  "open": "^10.1.0",
46
54
  "react": "^19.0.0",
47
55
  "react-dom": "^19.0.0",
@@ -1 +0,0 @@
1
- self.__BUILD_MANIFEST=function(e,r,t){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:8,errorRate:1e-4,numBits:154,numHashes:14,bitArray:[0,0,e,1,1,e,e,r,e,r,e,r,r,r,r,e,r,r,r,e,r,e,r,r,r,e,e,r,e,e,r,r,e,e,e,e,r,e,r,e,e,e,e,r,r,e,r,e,e,e,r,e,r,e,r,r,e,r,e,r,e,e,r,r,e,r,e,r,e,r,r,e,e,r,e,r,r,r,r,r,r,e,r,r,e,r,e,r,r,r,e,e,r,r,e,e,e,e,r,r,e,e,r,e,r,r,e,e,e,e,e,r,e,e,e,e,r,e,r,e,r,e,r,e,r,e,r,e,e,r,r,e,r,e,e,r,e,e,e,r,r,e,e,r,r,r,r,r,e,r,e,r,r,r]},__routerFilterDynamic:{numItems:e,errorRate:1e-4,numBits:e,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-78b0b3b591df0e73.js"],sortedPages:["/_app","/_error"]}}(0,1,1e-4),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();