ai-control-center 1.15.2

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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +584 -0
  3. package/bin/aicc.js +772 -0
  4. package/lib/actions/approve.js +71 -0
  5. package/lib/actions/assign-project.js +132 -0
  6. package/lib/actions/browser-test.js +64 -0
  7. package/lib/actions/cleanup.js +174 -0
  8. package/lib/actions/debug.js +298 -0
  9. package/lib/actions/deploy.js +1229 -0
  10. package/lib/actions/fix-bug.js +134 -0
  11. package/lib/actions/new-feature.js +255 -0
  12. package/lib/actions/reject.js +307 -0
  13. package/lib/actions/review.js +706 -0
  14. package/lib/actions/status.js +47 -0
  15. package/lib/agents/browser-qa-agent.js +611 -0
  16. package/lib/agents/payment-agent.js +116 -0
  17. package/lib/agents/suggestion-agent.js +88 -0
  18. package/lib/cli.js +303 -0
  19. package/lib/config.js +243 -0
  20. package/lib/hub/hub-server.js +440 -0
  21. package/lib/hub/project-poller.js +75 -0
  22. package/lib/hub/skill-registry.js +89 -0
  23. package/lib/hub/state-aggregator.js +204 -0
  24. package/lib/index.js +471 -0
  25. package/lib/init/doctor.js +523 -0
  26. package/lib/init/presets.js +222 -0
  27. package/lib/init/skill-fetcher.js +77 -0
  28. package/lib/init/wizard.js +973 -0
  29. package/lib/integrations/codex-runner.js +128 -0
  30. package/lib/integrations/github-actions.js +248 -0
  31. package/lib/integrations/github-reporter.js +229 -0
  32. package/lib/integrations/screenshot-store.js +102 -0
  33. package/lib/openclaw/bridge.js +650 -0
  34. package/lib/openclaw/generate-skill.js +235 -0
  35. package/lib/openclaw/openclaw.json +64 -0
  36. package/lib/orchestrator/autonomous-loop.js +429 -0
  37. package/lib/orchestrator/thread-triggers.js +63 -0
  38. package/lib/roleplay/agent-messenger.js +75 -0
  39. package/lib/roleplay/discussion-threads.js +303 -0
  40. package/lib/roleplay/health-monitor.js +121 -0
  41. package/lib/roleplay/pm-agent.js +513 -0
  42. package/lib/roleplay/roleplay-config.js +25 -0
  43. package/lib/roleplay/room.js +164 -0
  44. package/lib/shared/action-runner.js +2330 -0
  45. package/lib/shared/event-bus.js +185 -0
  46. package/lib/slack/bot.js +378 -0
  47. package/lib/telegram/bot.js +416 -0
  48. package/lib/telegram/commands.js +1267 -0
  49. package/lib/telegram/keyboards.js +113 -0
  50. package/lib/telegram/notifications.js +247 -0
  51. package/lib/twitch/bot.js +354 -0
  52. package/lib/twitch/commands.js +302 -0
  53. package/lib/twitch/notifications.js +63 -0
  54. package/lib/utils/achievements.js +191 -0
  55. package/lib/utils/activity-log.js +182 -0
  56. package/lib/utils/agent-leaderboard.js +119 -0
  57. package/lib/utils/audit-logger.js +232 -0
  58. package/lib/utils/codebase-context.js +288 -0
  59. package/lib/utils/codebase-indexer.js +381 -0
  60. package/lib/utils/config-schema.js +230 -0
  61. package/lib/utils/context-compressor.js +172 -0
  62. package/lib/utils/correlation.js +63 -0
  63. package/lib/utils/cost-tracker.js +423 -0
  64. package/lib/utils/cron-scheduler.js +53 -0
  65. package/lib/utils/db-adapter.js +293 -0
  66. package/lib/utils/display.js +272 -0
  67. package/lib/utils/errors.js +116 -0
  68. package/lib/utils/format.js +134 -0
  69. package/lib/utils/intent-engine.js +464 -0
  70. package/lib/utils/mcp-client.js +238 -0
  71. package/lib/utils/model-ab-test.js +164 -0
  72. package/lib/utils/notify.js +122 -0
  73. package/lib/utils/persona-loader.js +80 -0
  74. package/lib/utils/pipeline-lock.js +73 -0
  75. package/lib/utils/pipeline.js +214 -0
  76. package/lib/utils/plugin-runner.js +234 -0
  77. package/lib/utils/rate-limiter.js +84 -0
  78. package/lib/utils/rbac.js +74 -0
  79. package/lib/utils/runner.js +1809 -0
  80. package/lib/utils/security.js +191 -0
  81. package/lib/utils/self-healer.js +144 -0
  82. package/lib/utils/skill-loader.js +255 -0
  83. package/lib/utils/spinner.js +132 -0
  84. package/lib/utils/stage-queue.js +50 -0
  85. package/lib/utils/state-machine.js +89 -0
  86. package/lib/utils/status-bar.js +327 -0
  87. package/lib/utils/token-estimator.js +101 -0
  88. package/lib/utils/ux-analyzer.js +101 -0
  89. package/lib/utils/webhook-emitter.js +83 -0
  90. package/lib/web/public/css/styles.css +417 -0
  91. package/lib/web/public/dark-mode.js +44 -0
  92. package/lib/web/public/hub/kanban.html +206 -0
  93. package/lib/web/public/index.html +45 -0
  94. package/lib/web/public/js/app.js +71 -0
  95. package/lib/web/public/js/ask.js +110 -0
  96. package/lib/web/public/js/dashboard.js +165 -0
  97. package/lib/web/public/js/deploy.js +72 -0
  98. package/lib/web/public/js/feature.js +79 -0
  99. package/lib/web/public/js/health.js +65 -0
  100. package/lib/web/public/js/logs.js +93 -0
  101. package/lib/web/public/js/review.js +123 -0
  102. package/lib/web/public/js/ws-client.js +82 -0
  103. package/lib/web/public/office/css/office.css +678 -0
  104. package/lib/web/public/office/index.html +148 -0
  105. package/lib/web/public/office/js/achievements-ui.js +117 -0
  106. package/lib/web/public/office/js/character.js +1056 -0
  107. package/lib/web/public/office/js/chat-bubbles.js +177 -0
  108. package/lib/web/public/office/js/cost-overlay.js +123 -0
  109. package/lib/web/public/office/js/day-night.js +68 -0
  110. package/lib/web/public/office/js/effects.js +632 -0
  111. package/lib/web/public/office/js/engine.js +146 -0
  112. package/lib/web/public/office/js/feature-ticket.js +216 -0
  113. package/lib/web/public/office/js/hub-client.js +60 -0
  114. package/lib/web/public/office/js/main.js +1757 -0
  115. package/lib/web/public/office/js/office-layout.js +1524 -0
  116. package/lib/web/public/office/js/pathfinding.js +144 -0
  117. package/lib/web/public/office/js/pixel-sprites.js +1454 -0
  118. package/lib/web/public/office/js/progress-bars.js +117 -0
  119. package/lib/web/public/office/js/replay.js +191 -0
  120. package/lib/web/public/office/js/sound-effects.js +91 -0
  121. package/lib/web/public/office/js/sprite-renderer.js +211 -0
  122. package/lib/web/public/office/js/stamina-system.js +89 -0
  123. package/lib/web/public/office/js/ui.js +107 -0
  124. package/lib/web/public/onboarding/index.html +243 -0
  125. package/lib/web/public/timeline/index.html +195 -0
  126. package/lib/web/routes/api.js +499 -0
  127. package/lib/web/routes/logs.js +20 -0
  128. package/lib/web/routes/metrics.js +99 -0
  129. package/lib/web/server.js +183 -0
  130. package/lib/web/ws/handler.js +65 -0
  131. package/package.json +67 -0
  132. package/templates/agent-architect.md +69 -0
  133. package/templates/agent-gemini-pm.md +49 -0
  134. package/templates/agent-gemini-reviewer.md +52 -0
  135. package/templates/copilot-instructions.md +36 -0
  136. package/templates/pipelines/mobile.json +27 -0
  137. package/templates/pipelines/nodejs-api.json +27 -0
  138. package/templates/pipelines/python.json +27 -0
  139. package/templates/pipelines/react.json +27 -0
  140. package/templates/pipelines/salesforce.json +27 -0
  141. package/templates/role-gemini.md +97 -0
  142. package/templates/skill-architect.md +114 -0
  143. package/templates/skill-browser-qa.md +50 -0
  144. package/templates/skill-bug-from-qa.md +58 -0
  145. package/templates/skill-chatbot.md +93 -0
  146. package/templates/skill-implement.md +78 -0
  147. package/templates/skill-openclaw.md +174 -0
  148. package/templates/skill-payment.md +110 -0
  149. package/templates/skill-pm-spec.md +77 -0
  150. package/templates/skill-requirement-capture.md +97 -0
  151. package/templates/skill-review.md +108 -0
  152. package/templates/skill-reviewer-qa.md +44 -0
  153. package/templates/skill-suggestion.md +45 -0
  154. package/templates/skill-template.md +142 -0
@@ -0,0 +1,134 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import { newFeatureAction } from './new-feature.js';
4
+ import { getLatestQAReport } from '../agents/browser-qa-agent.js';
5
+ import { getWorkflowDir } from '../utils/pipeline.js';
6
+
7
+ /**
8
+ * Build bug context from QA report (browser evidence).
9
+ * Returns null if no QA report exists.
10
+ */
11
+ function buildQABugContext() {
12
+ try {
13
+ const report = getLatestQAReport();
14
+ if (!report || report.failed.length === 0) return null;
15
+
16
+ return report.failed.map(f => ({
17
+ url: f.url,
18
+ errors: f.errors,
19
+ screenshot: f.screenshot,
20
+ warnings: f.warnings,
21
+ }));
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Build bug context from latest fix notes (AI-generated fix summaries).
29
+ */
30
+ function getLatestFixNotes() {
31
+ try {
32
+ const qaDir = resolve(getWorkflowDir(), 'qa-reports');
33
+ if (!existsSync(qaDir)) return '';
34
+ const fixFiles = readdirSync(qaDir)
35
+ .filter(f => f.startsWith('FIX-'))
36
+ .sort()
37
+ .reverse();
38
+ if (fixFiles.length === 0) return '';
39
+ return readFileSync(resolve(qaDir, fixFiles[0]), 'utf8');
40
+ } catch {
41
+ return '';
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Save QA evidence to a context file that the bug-fix pipeline can read.
47
+ */
48
+ function saveQAContext() {
49
+ const qaContext = buildQABugContext();
50
+ const fixNotes = getLatestFixNotes();
51
+ if (!qaContext && !fixNotes) return;
52
+
53
+ const contextDir = resolve(getWorkflowDir(), 'qa-reports');
54
+ if (!existsSync(contextDir)) mkdirSync(contextDir, { recursive: true });
55
+
56
+ const contextPath = resolve(contextDir, 'bugfix-context.json');
57
+ writeFileSync(contextPath, JSON.stringify({
58
+ qaEvidence: qaContext,
59
+ previousFixNotes: fixNotes.slice(0, 1000),
60
+ generatedAt: new Date().toISOString(),
61
+ }, null, 2));
62
+ }
63
+
64
+ /**
65
+ * Fix bugs found by QA.
66
+ *
67
+ * When called from the autonomous bugfix loop (fromQA: true), runs
68
+ * non-interactively using runNewFeature() from action-runner.
69
+ * When called from the interactive menu, delegates to newFeatureAction()
70
+ * which shows the mode prompt.
71
+ *
72
+ * @param {object} [options]
73
+ * @param {string} [options.featureId] - project/feature ID for context
74
+ * @param {boolean} [options.fromQA] - true when auto-triggered by browser QA
75
+ * @param {string} [options.mode] - 'auto' | 'manual' (default: 'auto' when fromQA)
76
+ * @param {string} [options.description] - override bug description
77
+ */
78
+ export async function fixBugAction(options = {}) {
79
+ // Save QA evidence to context file before running the bug-fix pipeline
80
+ saveQAContext();
81
+
82
+ const { fromQA = false, mode, description } = options;
83
+
84
+ // ── Autonomous mode (triggered by browser QA bugfix loop) ───────────
85
+ if (fromQA) {
86
+ const { runNewFeature } = await import('../shared/action-runner.js');
87
+
88
+ // Build description from QA report if not provided
89
+ let bugDescription = description;
90
+ if (!bugDescription) {
91
+ const qaContext = buildQABugContext();
92
+ if (qaContext && qaContext.length > 0) {
93
+ // Group failures by error type for clearer AI instructions
94
+ const errorGroups = {};
95
+ for (const f of qaContext) {
96
+ for (const err of (f.errors || ['Unknown error'])) {
97
+ const key = err.replace(/https?:\/\/[^\s]+/g, '<url>').slice(0, 80);
98
+ if (!errorGroups[key]) errorGroups[key] = [];
99
+ errorGroups[key].push(f.url);
100
+ }
101
+ }
102
+
103
+ const lines = [];
104
+ for (const [error, urls] of Object.entries(errorGroups)) {
105
+ lines.push(`## ${error}`);
106
+ for (const u of urls.slice(0, 10)) lines.push(`- ${u}`);
107
+ if (urls.length > 10) lines.push(`- ... and ${urls.length - 10} more`);
108
+ }
109
+
110
+ bugDescription = [
111
+ `Fix: Browser QA found ${qaContext.length} broken route(s).`,
112
+ `Read the full QA report at .ai-workflow/qa-reports/bugfix-context.json for screenshots and details.`,
113
+ ``,
114
+ `Failures by category:`,
115
+ ...lines,
116
+ ``,
117
+ `Instructions:`,
118
+ `1. Read the bugfix-context.json for full error details and screenshots`,
119
+ `2. For "Page appears to be a 404 error" — the route exists in navigation but has no page component. Create the missing page or fix the broken link.`,
120
+ `3. For console errors — fix the underlying code issue`,
121
+ `4. For visual regressions — check recent CSS/layout changes`,
122
+ `5. Run the project tests after fixing to ensure no regressions`,
123
+ ].join('\n');
124
+ } else {
125
+ bugDescription = 'Fix bugs found by Browser QA agent (see .ai-workflow/qa-reports/bugfix-context.json for full details)';
126
+ }
127
+ }
128
+
129
+ return runNewFeature(bugDescription, mode || 'auto', 'bug', { awaitPipeline: true });
130
+ }
131
+
132
+ // ── Interactive mode (from terminal menu) ───────────────────────────
133
+ return newFeatureAction('bug');
134
+ }
@@ -0,0 +1,255 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
3
+ import inquirer from 'inquirer';
4
+ import { resolve } from 'path';
5
+ import { getConfig } from '../config.js';
6
+ import { logActivity, printActivityFooter, printActivityHeader } from '../utils/activity-log.js';
7
+ import { printWarning } from '../utils/display.js';
8
+ import { getLatestFilePath, getRootDir, getStatus, getWorkflowDir, updateStatus } from '../utils/pipeline.js';
9
+ import { isAvailable, runClaude, runCopilot, runGemini } from '../utils/runner.js';
10
+ import { injectSkills } from '../utils/skill-loader.js';
11
+ import { spinCycle, spinner } from '../utils/spinner.js';
12
+ import { reviewAction } from './review.js';
13
+
14
+ // ─── Load a .claude/skills/*.md file, stripping YAML frontmatter ──────────────
15
+ // Legacy loader — kept for backward compat with project-specific skill names
16
+ function loadSkill(name) {
17
+ const root = getRootDir();
18
+ const skillPath = resolve(root, `.claude/skills/${name}.md`);
19
+ if (!existsSync(skillPath)) return null;
20
+ const raw = readFileSync(skillPath, 'utf8');
21
+ return raw.replace(/^---[\s\S]*?---\n*/m, '').trim();
22
+ }
23
+
24
+ export async function newFeatureAction(type = 'feature') {
25
+ const label = type === 'bug' ? 'Bug Fix' : 'Feature';
26
+ const placeholder = type === 'bug'
27
+ ? 'e.g. Fix: Authentication token not refreshing after expiry'
28
+ : 'e.g. Add user notification system for pipeline events';
29
+
30
+ // ── Mode selection (BEFORE pipeline starts) ──────────────────────────────────
31
+ // rawlist lets user type 1 or 2 + Enter — works on mobile without arrow keys.
32
+ const { pipelineMode } = await inquirer.prompt([{
33
+ type: 'rawlist',
34
+ name: 'pipelineMode',
35
+ message: 'Pipeline mode:',
36
+ prefix: '◆',
37
+ choices: [
38
+ { name: 'Auto — run entire pipeline, notify when ready to deploy', value: 'auto' },
39
+ { name: 'Manual — step through each stage yourself', value: 'manual' },
40
+ { name: chalk.dim('Cancel — back to main menu'), value: 'cancel' },
41
+ ],
42
+ }]);
43
+
44
+ if (pipelineMode === 'cancel') return false;
45
+
46
+ const { description } = await inquirer.prompt([{
47
+ type: 'input',
48
+ name: 'description',
49
+ message: `Describe your ${label.toLowerCase()}:`,
50
+ suffix: `\n ${placeholder}\n ${chalk.dim('(leave blank to cancel)')}\n `,
51
+ validate: v => v.trim().length === 0 || v.trim().length >= 10 || 'Please provide at least 10 characters',
52
+ }]);
53
+
54
+ if (!description.trim()) return false;
55
+
56
+ const prefix = type === 'bug' ? 'Fix: ' : '';
57
+ const fullDescription = `${prefix}${description.trim()}`;
58
+
59
+ printActivityHeader(`PIPELINE — New ${label}`);
60
+
61
+ // ── STEP 1: Create inbox file ────────────────────────────────────────────────
62
+ logActivity('PIPELINE', `Submitting: "${fullDescription}"`);
63
+
64
+ spinner.start('Pipeline · Creating feature inbox file...');
65
+
66
+ // Create inbox file inline (no external .sh dependency)
67
+ const _now = new Date();
68
+ const _pad = n => String(n).padStart(2, '0');
69
+ const _tsStamp = `${_now.getFullYear()}${_pad(_now.getMonth()+1)}${_pad(_now.getDate())}-${_pad(_now.getHours())}${_pad(_now.getMinutes())}${_pad(_now.getSeconds())}`;
70
+ const _featureId = `FEATURE-${_tsStamp}`;
71
+ const _inboxDir = resolve(getWorkflowDir(), 'inbox');
72
+ if (!existsSync(_inboxDir)) mkdirSync(_inboxDir, { recursive: true });
73
+ writeFileSync(resolve(_inboxDir, `${_featureId}.md`), [
74
+ `# Feature Request: ${_featureId}`,
75
+ `**Submitted:** ${_now.toLocaleString()}`,
76
+ `**Status:** INBOX`,
77
+ ``,
78
+ `## Description`,
79
+ fullDescription,
80
+ ``,
81
+ `## Notes`,
82
+ `(Add any additional context, constraints, or business rules here)`,
83
+ ``,
84
+ ].join('\n'));
85
+ const _inboxHistory = [...(getStatus().history || []), { id: _featureId, stage: 'inbox', time: _now.toISOString() }];
86
+ updateStatus({ current_feature: _featureId, stage: 'inbox', next: 'pm_spec', history: _inboxHistory });
87
+
88
+ spinner.stop();
89
+
90
+ // Persist the chosen mode and reset the auto-review counter for this feature
91
+ updateStatus({ pipeline_mode: pipelineMode, auto_review_count: 0 });
92
+
93
+ const status = getStatus();
94
+ const featureId = status.current_feature;
95
+ const workflowDir = getWorkflowDir();
96
+ const root = getRootDir();
97
+
98
+ logActivity('PIPELINE', `Feature ID: ${featureId}`, 'success');
99
+ logActivity('PIPELINE', `Inbox file created at: .ai-workflow/inbox/${featureId}.md`);
100
+
101
+ // ── STEP 2 + 3a: Gemini PM spec + Claude codebase exploration (parallel) ─────
102
+ // Gemini writes the PM spec while Claude simultaneously explores the codebase.
103
+ // This saves ~30-60s — Claude's architecture step then resumes the same session
104
+ // and already has full codebase context.
105
+ const geminiAvailable = await isAvailable('gemini');
106
+ const claudeAvailable = await isAvailable('claude');
107
+ const geminiMd = resolve(root, 'GEMINI.md');
108
+ const inboxFile = resolve(workflowDir, `inbox/${featureId}.md`);
109
+ const timestamp = featureId.replace('FEATURE-', '');
110
+ const specFile = resolve(workflowDir, `specs/SPEC-${timestamp}.md`);
111
+
112
+ // Gemini PM spec promise
113
+ const geminiPromise = (geminiAvailable && existsSync(geminiMd))
114
+ ? (async () => {
115
+ logActivity('GEMINI', 'Starting PM spec generation...');
116
+
117
+ let prompt =
118
+ `${readFileSync(geminiMd, 'utf8')}\n\n## Feature Request\n${readFileSync(inboxFile, 'utf8')}\n\n` +
119
+ `Create a complete PM spec with User Stories, Acceptance Criteria, Technical Risks, Dependencies, and Priority for ${(() => { try { return getConfig().name; } catch { return 'this project'; } })()}.`;
120
+
121
+ // Inject OpenClaw/project skills for the spec stage
122
+ prompt = injectSkills(prompt, 'spec');
123
+
124
+ const stopCycle = spinCycle('Gemini', [
125
+ 'Analyzing feature requirements...',
126
+ 'Writing user stories and acceptance criteria...',
127
+ 'Identifying technical risks and dependencies...',
128
+ 'Defining priority and effort estimates...',
129
+ 'Finalizing PM spec...',
130
+ ], 4000, 'cyan');
131
+
132
+ await runGemini(prompt, specFile);
133
+
134
+ stopCycle();
135
+ spinner.succeed(`PM spec written → specs/SPEC-${timestamp}.md`);
136
+ updateStatus({ stage: 'spec_complete', next: 'architect' });
137
+ logActivity('GEMINI', `Spec ready: .ai-workflow/specs/SPEC-${timestamp}.md`, 'success');
138
+ })()
139
+ : (logActivity('GEMINI', 'CLI not found — Claude will handle PM spec', 'warn'), Promise.resolve());
140
+
141
+ // Claude codebase exploration promise (runs while Gemini writes spec)
142
+ const claudeExplorePromise = claudeAvailable
143
+ ? runClaude(
144
+ `Explore the ${(() => { try { return getConfig().name; } catch { return 'project'; } })()} codebase to prepare for architecture design.\n\n` +
145
+ `1. List key source files and their purpose\n` +
146
+ `2. Identify existing patterns and reusable components\n` +
147
+ `3. Note any partial or incomplete implementations relevant to the upcoming feature\n\n` +
148
+ `Summarize your findings briefly. Architecture design will follow in the next step.`,
149
+ { featureId }
150
+ ).catch(err => logActivity('CLAUDE', `Codebase exploration skipped: ${err.message}`, 'warn'))
151
+ : Promise.resolve();
152
+
153
+ if (claudeAvailable) {
154
+ logActivity('PIPELINE', 'Running Gemini PM spec + Claude codebase exploration in parallel...');
155
+ }
156
+
157
+ await Promise.all([geminiPromise, claudeExplorePromise]);
158
+
159
+ // ── STEP 3: Claude as Architect — resumes session with codebase knowledge ─────
160
+ if (claudeAvailable) {
161
+ logActivity('CLAUDE', 'Starting architecture design (resuming codebase exploration session)...');
162
+
163
+ const specContent = existsSync(specFile) ? readFileSync(specFile, 'utf8') : fullDescription;
164
+ const architectSkill = loadSkill('xconn-architect');
165
+
166
+ // Build architecture prompt — prefer OpenClaw/project skill, fall back to legacy or inline
167
+ let architectPrompt;
168
+ if (architectSkill) {
169
+ architectPrompt = architectSkill.replace(/\{timestamp\}/g, timestamp) + `\n\n## Feature Spec\n${specContent}`;
170
+ } else {
171
+ architectPrompt = `${(() => { try { return getConfig().ai.system + '\n\n'; } catch { return ''; } })()}You are the System Architect. Read this feature spec carefully. Your codebase exploration from the previous step gives you full context.
172
+
173
+ Your job:
174
+ 1. Write a detailed architecture doc to: .ai-workflow/architecture/ARCH-${timestamp}.md
175
+ Include: Data flow, new classes/components, any schema changes needed
176
+ 2. Write step-by-step implementation tasks to: .ai-workflow/tasks/TASKS-${timestamp}.md
177
+ Each task must reference exact file paths and class/function names
178
+
179
+ ## Feature Spec
180
+ ${specContent}`;
181
+ }
182
+ // Inject OpenClaw/project skills for the architecture stage
183
+ architectPrompt = injectSkills(architectPrompt, 'arch');
184
+
185
+ const stopCycle = spinCycle('Claude', [
186
+ 'Resuming codebase exploration context...',
187
+ 'Designing Apex architecture...',
188
+ 'Planning LWC components...',
189
+ 'Writing implementation tasks for Copilot...',
190
+ 'Reviewing governor limit considerations...',
191
+ 'Finalizing architecture document...',
192
+ ], 5000, 'magenta');
193
+
194
+ await runClaude(architectPrompt, { featureId });
195
+
196
+ stopCycle();
197
+ spinner.succeed(`Architecture + Tasks written`);
198
+
199
+ updateStatus({ stage: 'arch_complete', next: 'copilot_implement' });
200
+ logActivity('CLAUDE', `Architecture ready: .ai-workflow/architecture/ARCH-${timestamp}.md`, 'success');
201
+ logActivity('CLAUDE', `Tasks ready: .ai-workflow/tasks/TASKS-${timestamp}.md`, 'success');
202
+ } else {
203
+ logActivity('CLAUDE', 'CLI not found — paste into Claude Code:', 'warn');
204
+ printWarning(`"Read .ai-workflow/specs/SPEC-${timestamp}.md and create architecture + tasks"`);
205
+ }
206
+
207
+ // ── STEP 4: Copilot implements the tasks ──────────────────────────────────────
208
+ // Claude's role ends at architecture. Copilot codes every task.
209
+ const tasksFile = getLatestFilePath('tasks');
210
+ if (tasksFile) {
211
+ const relPath = tasksFile.replace(root + '/', '');
212
+ const archRelPath = relPath.replace('tasks/TASKS', 'architecture/ARCH');
213
+
214
+ logActivity('COPILOT', 'Starting implementation...');
215
+ logActivity('COPILOT', `Tasks: ${relPath}`);
216
+ logActivity('COPILOT', `Arch: ${archRelPath}`);
217
+
218
+ const copilotSkill = loadSkill('xconn-copilot-implement');
219
+ let copilotPrompt =
220
+ `Read ${relPath} and implement every task listed there.\n` +
221
+ `Follow the architecture at ${archRelPath}.\n\n` +
222
+ (copilotSkill || `Implement every task in the task list following the architecture document. Make minimal, precise changes to existing files. Do not break existing functionality.`);
223
+ // Inject OpenClaw/project skills for the implementation stage
224
+ copilotPrompt = injectSkills(copilotPrompt, 'impl');
225
+
226
+ const stopImpl = spinCycle('Copilot', [
227
+ 'Reading architecture and task list...',
228
+ 'Implementing Apex classes...',
229
+ 'Writing test classes...',
230
+ 'Implementing LWC components...',
231
+ 'Applying governor limit safeguards...',
232
+ 'Finalizing implementation...',
233
+ ], 6000, 'blue');
234
+
235
+ await runCopilot(copilotPrompt, { featureId });
236
+
237
+ stopImpl();
238
+ spinner.succeed('Copilot implementation complete');
239
+
240
+ updateStatus({ stage: 'implementation_complete', next: 'review' });
241
+ logActivity('COPILOT', 'All tasks implemented', 'success');
242
+ }
243
+
244
+ printActivityFooter();
245
+
246
+ // ── Auto mode: trigger review immediately; Manual mode: menu handles it ──────
247
+ if (pipelineMode === 'auto') {
248
+ logActivity('PIPELINE', 'Auto mode — starting code review...', 'info');
249
+ await reviewAction();
250
+ } else {
251
+ logActivity('PIPELINE', 'Manual mode — select Review Code from the menu when ready.', 'info');
252
+ }
253
+
254
+ return true;
255
+ }