@smartmemory/compose 0.1.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.
- package/LICENSE +21 -0
- package/README.md +1014 -0
- package/bin/compose.js +1515 -0
- package/dist/assets/_baseUniq-CQwX6VLz.js +1 -0
- package/dist/assets/arc-SxJ2J1sh.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-BykunY1F.js +36 -0
- package/dist/assets/blockDiagram-DXYQGD6D-ohAKBOUw.js +132 -0
- package/dist/assets/c4Diagram-AHTNJAMY-DBDC3ENB.js +10 -0
- package/dist/assets/channel-DGElom1e.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-Cv93Z7uM.js +1 -0
- package/dist/assets/chunk-4TB4RGXK-DE0WBDkj.js +206 -0
- package/dist/assets/chunk-55IACEB6-CE1EXenG.js +1 -0
- package/dist/assets/chunk-EDXVE4YY-DA7Ana6H.js +1 -0
- package/dist/assets/chunk-FMBD7UC4-CTDIPA3p.js +15 -0
- package/dist/assets/chunk-OYMX7WX6-uGBaPaTX.js +231 -0
- package/dist/assets/chunk-QZHKN3VN-CYlnXuUO.js +1 -0
- package/dist/assets/chunk-YZCP3GAM-ojGkzcZK.js +1 -0
- package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +1 -0
- package/dist/assets/clone-DUJKJXd7.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-Bktn9hL-.js +1 -0
- package/dist/assets/dagre-KV5264BT-DFaSzuRF.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-5BDNPKRD-DnfmDzEm.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-Bm8W9YnG.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-B5-TSKvp.js +43 -0
- package/dist/assets/diagram-TYMM5635-ls4rqlky.js +24 -0
- package/dist/assets/erDiagram-SMLLAGMA-giG6WO-r.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-XvlUuz-7.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-hLBV57oV.js +292 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js +106 -0
- package/dist/assets/graph-D0Cfv00Y.js +1 -0
- package/dist/assets/index-CUd6pFGF.css +1 -0
- package/dist/assets/index-DReRlzZI.js +1144 -0
- package/dist/assets/infoDiagram-42DDH7IO-DbqRsOo3.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-UXIWVN3A-DnCdx7zb.js +70 -0
- package/dist/assets/journeyDiagram-VCZTEJTY-CfD7eNcP.js +139 -0
- package/dist/assets/kanban-definition-6JOO6SKY-BYaO9-mK.js +89 -0
- package/dist/assets/katex-DkKDou_j.js +257 -0
- package/dist/assets/layout-Bj72wOEB.js +1 -0
- package/dist/assets/linear-BRFo114D.js +1 -0
- package/dist/assets/min-GCHnKlJS.js +1 -0
- package/dist/assets/mindmap-definition-QFDTVHPH-n0PMebY4.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pieDiagram-DEJITSTG-pN4CljHF.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-DNoAy8-D.js +7 -0
- package/dist/assets/requirementDiagram-MS252O5E-BhtY05PT.js +84 -0
- package/dist/assets/sankeyDiagram-XADWPNL6-B6AD-16A.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-DShHM-uk.js +157 -0
- package/dist/assets/stateDiagram-FHFEXIEX-DMxn7HTo.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +1 -0
- package/dist/assets/timeline-definition-GMOUNBTQ-Cdu6uq52.js +120 -0
- package/dist/assets/vennDiagram-DHZGUBPP-CpK29iRe.js +34 -0
- package/dist/assets/wardley-RL74JXVD-BQgSkdcO.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-DJHYev6O.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-1d75pbaO.js +7 -0
- package/dist/index.html +30 -0
- package/lib/agent-chains.js +65 -0
- package/lib/agent-string.js +86 -0
- package/lib/budget-ledger.js +86 -0
- package/lib/build-all.js +162 -0
- package/lib/build-dag.js +120 -0
- package/lib/build-stream-writer.js +190 -0
- package/lib/build.js +2997 -0
- package/lib/capability-checker.js +53 -0
- package/lib/cert-inject.js +38 -0
- package/lib/cli-progress.js +483 -0
- package/lib/constants.js +69 -0
- package/lib/cross-layer-audit.js +84 -0
- package/lib/debug-discipline.js +173 -0
- package/lib/feature-json.js +106 -0
- package/lib/gate-prompt.js +291 -0
- package/lib/gate-tiers.js +194 -0
- package/lib/health-history.js +119 -0
- package/lib/health-score.js +227 -0
- package/lib/ideabox.js +570 -0
- package/lib/import.js +244 -0
- package/lib/migrate-roadmap.js +94 -0
- package/lib/model-pricing.js +67 -0
- package/lib/new.js +413 -0
- package/lib/pipeline-cli.js +489 -0
- package/lib/plan-parser.js +103 -0
- package/lib/qa-scoping.js +474 -0
- package/lib/questionnaire.js +200 -0
- package/lib/resolve-port.js +7 -0
- package/lib/result-normalizer.js +349 -0
- package/lib/review-lenses.js +166 -0
- package/lib/roadmap-gen.js +210 -0
- package/lib/roadmap-parser.js +176 -0
- package/lib/server-probe.js +23 -0
- package/lib/staleness.js +87 -0
- package/lib/step-prompt.js +260 -0
- package/lib/step-validator.js +49 -0
- package/lib/stratum-mcp-client.js +365 -0
- package/lib/team-flag.js +46 -0
- package/lib/test-bootstrap.js +401 -0
- package/lib/triage.js +274 -0
- package/lib/vision-writer.js +391 -0
- package/package.json +111 -0
- package/pipelines/bug-fix.stratum.yaml +230 -0
- package/pipelines/build.stratum.yaml +498 -0
- package/pipelines/content.stratum.yaml +112 -0
- package/pipelines/coverage-sweep.stratum.yaml +52 -0
- package/pipelines/refactor.stratum.yaml +169 -0
- package/pipelines/research.stratum.yaml +88 -0
- package/pipelines/review-fix.stratum.yaml +109 -0
- package/presets/team-feature.stratum.yaml +105 -0
- package/presets/team-research.stratum.yaml +108 -0
- package/presets/team-review.stratum.yaml +106 -0
- package/scripts/agent-activity-hook.sh +31 -0
- package/scripts/agent-error-hook.sh +28 -0
- package/scripts/analyze-orphans.mjs +50 -0
- package/scripts/find-orphans.mjs +26 -0
- package/scripts/fix-phases.mjs +49 -0
- package/scripts/generate-stratum-spec.mjs +137 -0
- package/scripts/import-roadmap.mjs +116 -0
- package/scripts/phase-audit.mjs +33 -0
- package/scripts/run-pipeline.mjs +314 -0
- package/scripts/session-end-hook.sh +18 -0
- package/scripts/session-start-hook.sh +38 -0
- package/scripts/vision-hook.sh +104 -0
- package/scripts/vision-track.mjs +554 -0
- package/scripts/wire-all-orphans.mjs +108 -0
- package/scripts/wire-orphans.mjs +164 -0
- package/server/activity-routes.js +123 -0
- package/server/agent-health.js +197 -0
- package/server/agent-hooks.js +102 -0
- package/server/agent-mcp.js +10 -0
- package/server/agent-registry.js +95 -0
- package/server/agent-server.js +290 -0
- package/server/agent-spawn.js +251 -0
- package/server/agent-templates.js +77 -0
- package/server/artifact-manager.js +247 -0
- package/server/artifact-templates/architecture.md +28 -0
- package/server/artifact-templates/blueprint.md +21 -0
- package/server/artifact-templates/design.md +36 -0
- package/server/artifact-templates/plan.md +25 -0
- package/server/artifact-templates/prd.md +43 -0
- package/server/artifact-templates/report.md +40 -0
- package/server/block-tracker.js +90 -0
- package/server/build-stream-bridge.js +502 -0
- package/server/coalescing-buffer.js +46 -0
- package/server/compose-mcp-tools.js +479 -0
- package/server/compose-mcp.js +324 -0
- package/server/connectors/agent-connector.js +78 -0
- package/server/connectors/claude-sdk-connector.js +198 -0
- package/server/connectors/codex-connector.js +240 -0
- package/server/connectors/connector-discovery.js +18 -0
- package/server/connectors/connector-runtime.js +13 -0
- package/server/connectors/opencode-connector.js +200 -0
- package/server/design-routes.js +540 -0
- package/server/design-session.js +161 -0
- package/server/feature-scan.js +593 -0
- package/server/file-watcher.js +284 -0
- package/server/find-root.js +29 -0
- package/server/graph-export.js +343 -0
- package/server/ideabox-cache.js +77 -0
- package/server/ideabox-routes.js +294 -0
- package/server/index.js +156 -0
- package/server/model-tiers.js +49 -0
- package/server/pipeline-routes.js +288 -0
- package/server/policy-evaluator.js +36 -0
- package/server/project-root.js +122 -0
- package/server/security.js +23 -0
- package/server/session-manager.js +403 -0
- package/server/session-routes.js +190 -0
- package/server/session-store.js +107 -0
- package/server/settings-routes.js +35 -0
- package/server/settings-store.js +234 -0
- package/server/stratum-api.js +102 -0
- package/server/stratum-client.js +192 -0
- package/server/stratum-sync.js +193 -0
- package/server/summarizer.js +139 -0
- package/server/supervisor.js +196 -0
- package/server/vision-routes.js +668 -0
- package/server/vision-server.js +393 -0
- package/server/vision-store.js +360 -0
- package/server/vision-utils.js +179 -0
- package/server/worktree-gc.js +137 -0
- package/templates/ROADMAP.md +46 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* capability-checker.js — Runtime capability violation detection (COMP-CAPS-ENFORCE).
|
|
3
|
+
*
|
|
4
|
+
* Compares observed tool names against agent template restrictions.
|
|
5
|
+
* Called after each step to identify policy violations for audit or enforcement.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { resolveTemplate, validateCapabilities } from '../server/agent-templates.js';
|
|
9
|
+
import { parseAgentString } from './agent-string.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check whether a tool use constitutes a capability violation under an agent's template.
|
|
13
|
+
*
|
|
14
|
+
* Violation = tool is in disallowedTools (explicit deny)
|
|
15
|
+
* Warning = tool is not in allowedTools and allowedTools is a non-null list
|
|
16
|
+
* (not explicitly permitted, but not explicitly denied)
|
|
17
|
+
* No issue = tool is permitted or template has no restrictions
|
|
18
|
+
*
|
|
19
|
+
* @param {string} toolName The tool that was observed being used
|
|
20
|
+
* @param {string|null|undefined} agentString Agent string, e.g. "claude:read-only-reviewer"
|
|
21
|
+
* @returns {{ violation: boolean, severity: 'violation'|'warning'|'none', reason: string }}
|
|
22
|
+
*/
|
|
23
|
+
export function checkCapabilityViolation(toolName, agentString) {
|
|
24
|
+
const { template: templateName } = parseAgentString(agentString);
|
|
25
|
+
const template = resolveTemplate(templateName);
|
|
26
|
+
|
|
27
|
+
if (!template) {
|
|
28
|
+
return { violation: false, severity: 'none', reason: 'No template — all tools permitted' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { allowedTools, disallowedTools } = template;
|
|
32
|
+
|
|
33
|
+
// Explicit deny → violation
|
|
34
|
+
if (disallowedTools && disallowedTools.includes(toolName)) {
|
|
35
|
+
return {
|
|
36
|
+
violation: true,
|
|
37
|
+
severity: 'violation',
|
|
38
|
+
reason: `Tool "${toolName}" is in disallowedTools for template "${templateName}"`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Not in allowedTools (when allowedTools is set) → warning
|
|
43
|
+
if (allowedTools !== null && !allowedTools.includes(toolName)) {
|
|
44
|
+
return {
|
|
45
|
+
violation: true,
|
|
46
|
+
severity: 'warning',
|
|
47
|
+
reason: `Tool "${toolName}" is not in allowedTools for template "${templateName}"`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { reason } = validateCapabilities(template, toolName);
|
|
52
|
+
return { violation: false, severity: 'none', reason };
|
|
53
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cert-inject.js — STRAT-CERT reasoning template injection for parallel dispatch tasks.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Python's inject_cert_instructions() from executor.py.
|
|
5
|
+
* Used by build.js to inject structured reasoning prompts into
|
|
6
|
+
* STRAT-REV lens task intents.
|
|
7
|
+
*
|
|
8
|
+
* See: docs/features/STRAT-CERT/design.md (Consumer Wiring addendum)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_CERT_SECTIONS = [
|
|
12
|
+
{ id: 'premises', label: 'Premises', description: 'State every verifiable fact you are using. Each premise must cite a file:line.' },
|
|
13
|
+
{ id: 'trace', label: 'Trace', description: 'Walk through the logic step by step. Reference premises by [P<n>] ID.' },
|
|
14
|
+
{ id: 'conclusion', label: 'Conclusion', description: 'State your finding. Every claim must reference at least one premise.' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Inject structured reasoning certificate instructions into an intent string.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} intent - The original task intent
|
|
21
|
+
* @param {object} template - reasoning_template object with optional sections[] and require_citations
|
|
22
|
+
* @returns {string} Intent with appended certificate structure instructions
|
|
23
|
+
*/
|
|
24
|
+
export function injectCertInstructions(intent, template) {
|
|
25
|
+
if (!template) return intent;
|
|
26
|
+
const sections = template.sections?.length ? template.sections : DEFAULT_CERT_SECTIONS;
|
|
27
|
+
const requireCitations = template.require_citations || false;
|
|
28
|
+
const lines = [intent, '', '---', '', 'You MUST structure your response with these sections:', ''];
|
|
29
|
+
for (const [i, section] of sections.entries()) {
|
|
30
|
+
lines.push(`## ${section.label}`);
|
|
31
|
+
lines.push(section.description);
|
|
32
|
+
if (section.id === 'premises' && requireCitations) lines.push('Format each fact as: [P1] <fact, citing file:line>, [P2] ..., etc.');
|
|
33
|
+
else if (i > 0 && requireCitations) lines.push('Reference premises by their [P<n>] ID.');
|
|
34
|
+
lines.push('');
|
|
35
|
+
}
|
|
36
|
+
lines.push('Include your full structured reasoning in a `reasoning` field in your JSON output.');
|
|
37
|
+
return lines.join('\n');
|
|
38
|
+
}
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-progress.js — Build progress output for compose build.
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* Collapsed (default): last 5 tool events + sticky key hints bar
|
|
6
|
+
* Expanded: all tool events printed as they arrive
|
|
7
|
+
*
|
|
8
|
+
* Keys: t=toggle s=skip r=retry Ctrl+C=abort
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { EventEmitter } from 'node:events';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// ANSI helpers
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
const ESC = '\x1b[';
|
|
18
|
+
const RESET = `${ESC}0m`;
|
|
19
|
+
const BOLD = `${ESC}1m`;
|
|
20
|
+
const DIM = `${ESC}2m`;
|
|
21
|
+
const CYAN = `${ESC}36m`;
|
|
22
|
+
const YELLOW = `${ESC}33m`;
|
|
23
|
+
const GREEN = `${ESC}32m`;
|
|
24
|
+
const RED = `${ESC}31m`;
|
|
25
|
+
const MAGENTA = `${ESC}35m`;
|
|
26
|
+
const GRAY = `${ESC}90m`;
|
|
27
|
+
const WHITE = `${ESC}37m`;
|
|
28
|
+
|
|
29
|
+
const ERASE_LINE = `${ESC}2K\r`;
|
|
30
|
+
const MOVE_UP = (n) => `${ESC}${n}A`;
|
|
31
|
+
const HIDE_CURSOR = `${ESC}?25l`;
|
|
32
|
+
const SHOW_CURSOR = `${ESC}?25h`;
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Tool color mapping
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
const TOOL_COLORS = {
|
|
39
|
+
Bash: CYAN, bash: CYAN,
|
|
40
|
+
Read: GREEN, read: GREEN,
|
|
41
|
+
Write: YELLOW, write: YELLOW,
|
|
42
|
+
Edit: YELLOW, edit: YELLOW,
|
|
43
|
+
Glob: MAGENTA, glob: MAGENTA,
|
|
44
|
+
Grep: MAGENTA, grep: MAGENTA,
|
|
45
|
+
Agent: BOLD + CYAN, agent: BOLD + CYAN,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function colorForTool(tool) {
|
|
49
|
+
return TOOL_COLORS[tool] ?? GRAY;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Constants
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
const COLLAPSED_LINES = 5; // tool lines to show when collapsed
|
|
57
|
+
const HINT_BAR = `${DIM} keys: ${WHITE}t${GRAY}=toggle ${WHITE}s${GRAY}=skip ${WHITE}r${GRAY}=retry ${WHITE}Ctrl+C${GRAY}=abort${RESET}`;
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Pipeline step definitions (COMP-TUI-1)
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
const PIPELINE_STEPS = [
|
|
64
|
+
'explore_design', 'scope', 'design_gate', 'prd', 'architecture',
|
|
65
|
+
'blueprint', 'verification', 'plan_gate', 'decompose', 'execute',
|
|
66
|
+
'review', 'codex_review', 'coverage', 'report', 'docs', 'ship_gate', 'ship',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const STEP_LABELS = {
|
|
70
|
+
explore_design: 'explore', scope: 'scope', design_gate: 'design\u2193',
|
|
71
|
+
prd: 'prd', architecture: 'arch', blueprint: 'blueprint',
|
|
72
|
+
verification: 'verify', plan_gate: 'plan\u2193', decompose: 'decomp',
|
|
73
|
+
execute: 'execute', review: 'review', codex_review: 'codex',
|
|
74
|
+
coverage: 'coverage',
|
|
75
|
+
report: 'report', docs: 'docs', ship_gate: 'ship\u2193', ship: 'ship',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// CliProgress
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
export class CliProgress extends EventEmitter {
|
|
83
|
+
#expanded = false;
|
|
84
|
+
#toolHistory = []; // all tool events for current step
|
|
85
|
+
#toolCount = 0;
|
|
86
|
+
#drawnCollapsedLines = 0; // how many collapsed lines are currently on screen
|
|
87
|
+
#stream;
|
|
88
|
+
#isTTY;
|
|
89
|
+
#onKey;
|
|
90
|
+
#wasRaw;
|
|
91
|
+
#listening = false;
|
|
92
|
+
#heartbeatTimer = null;
|
|
93
|
+
#stepStart = 0;
|
|
94
|
+
#currentStepId = '';
|
|
95
|
+
#pendingAction = null;
|
|
96
|
+
#stepHistory = []; // completed step IDs for pipeline bar
|
|
97
|
+
|
|
98
|
+
constructor({ stream, expanded = false } = {}) {
|
|
99
|
+
super();
|
|
100
|
+
this.#stream = stream ?? process.stderr;
|
|
101
|
+
this.#isTTY = this.#stream.isTTY ?? false;
|
|
102
|
+
this.#expanded = expanded;
|
|
103
|
+
|
|
104
|
+
this.#onKey = (key) => {
|
|
105
|
+
if (key === 't' || key === 'T') this.toggle();
|
|
106
|
+
if (key === 's' || key === 'S') {
|
|
107
|
+
this.#pendingAction = 'skip';
|
|
108
|
+
this.#clearCollapsed();
|
|
109
|
+
this.#stream.write(` ${YELLOW}⏭ Skip requested — interrupting current step...${RESET}\n`);
|
|
110
|
+
this.emit('interrupt');
|
|
111
|
+
}
|
|
112
|
+
if (key === 'r' || key === 'R') {
|
|
113
|
+
this.#pendingAction = 'retry';
|
|
114
|
+
this.#clearCollapsed();
|
|
115
|
+
this.#stream.write(` ${YELLOW}↻ Retry requested — interrupting current step...${RESET}\n`);
|
|
116
|
+
this.emit('interrupt');
|
|
117
|
+
}
|
|
118
|
+
if (key === '\x03') {
|
|
119
|
+
this.finish();
|
|
120
|
+
process.exit(130);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
this.#startListening();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get expanded() { return this.#expanded; }
|
|
128
|
+
|
|
129
|
+
consumeAction() {
|
|
130
|
+
const action = this.#pendingAction;
|
|
131
|
+
this.#pendingAction = null;
|
|
132
|
+
return action;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get pendingAction() { return this.#pendingAction; }
|
|
136
|
+
|
|
137
|
+
// ── Key listener management ───────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
#startListening() {
|
|
140
|
+
if (this.#listening) return;
|
|
141
|
+
if (!this.#isTTY || !process.stdin.isTTY || !process.stdin.setRawMode) return;
|
|
142
|
+
this.#wasRaw = process.stdin.isRaw;
|
|
143
|
+
process.stdin.setRawMode(true);
|
|
144
|
+
process.stdin.resume();
|
|
145
|
+
process.stdin.setEncoding('utf-8');
|
|
146
|
+
process.stdin.on('data', this.#onKey);
|
|
147
|
+
this.#listening = true;
|
|
148
|
+
if (this.#isTTY) this.#stream.write(HIDE_CURSOR);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#stopListening() {
|
|
152
|
+
if (!this.#listening) return;
|
|
153
|
+
process.stdin.removeListener('data', this.#onKey);
|
|
154
|
+
if (process.stdin.setRawMode) {
|
|
155
|
+
try { process.stdin.setRawMode(this.#wasRaw ?? false); } catch { /* ignore */ }
|
|
156
|
+
}
|
|
157
|
+
process.stdin.pause();
|
|
158
|
+
this.#listening = false;
|
|
159
|
+
if (this.#isTTY) this.#stream.write(SHOW_CURSOR);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
pause() {
|
|
163
|
+
this.#clearCollapsed();
|
|
164
|
+
this.#stopListening();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
resume() {
|
|
168
|
+
this.#startListening();
|
|
169
|
+
if (!this.#expanded) this.#drawCollapsed();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
toggle() {
|
|
173
|
+
this.#clearCollapsed();
|
|
174
|
+
this.#expanded = !this.#expanded;
|
|
175
|
+
if (this.#expanded) {
|
|
176
|
+
// Dump full tool history on expand
|
|
177
|
+
for (const t of this.#toolHistory) {
|
|
178
|
+
this.#stream.write(` ${colorForTool(t.tool)}↳ ${t.tool}${RESET}${t.detail ? ': ' + t.detail : ''}\n`);
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
this.#drawCollapsed();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Event handlers ────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
toolUse(tool, detail) {
|
|
188
|
+
this.#toolCount++;
|
|
189
|
+
this.#stepStart = Date.now();
|
|
190
|
+
const short = typeof detail === 'string' && detail.length > 60
|
|
191
|
+
? detail.slice(0, 57) + '...' : (detail || '');
|
|
192
|
+
|
|
193
|
+
this.#toolHistory.push({ tool, detail: short });
|
|
194
|
+
|
|
195
|
+
if (this.#expanded) {
|
|
196
|
+
this.#stream.write(` ${colorForTool(tool)}↳ ${tool}${RESET}${short ? ': ' + short : ''}\n`);
|
|
197
|
+
} else {
|
|
198
|
+
this.#drawCollapsed();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
toolSummary(summary) {
|
|
203
|
+
const short = summary.length > 80 ? summary.slice(0, 77) + '...' : summary;
|
|
204
|
+
this.#toolHistory.push({ tool: '✓', detail: short });
|
|
205
|
+
|
|
206
|
+
if (this.#expanded) {
|
|
207
|
+
this.#stream.write(` ${GREEN}✓${RESET} ${short}\n`);
|
|
208
|
+
} else {
|
|
209
|
+
this.#drawCollapsed();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
toolProgress(tool, elapsed) {
|
|
214
|
+
this.#toolHistory.push({ tool, detail: `(${Math.round(elapsed)}s)` });
|
|
215
|
+
|
|
216
|
+
if (this.#expanded) {
|
|
217
|
+
this.#stream.write(` ${colorForTool(tool)}↳ ${tool}${RESET} ${DIM}(${Math.round(elapsed)}s)${RESET}\n`);
|
|
218
|
+
} else {
|
|
219
|
+
this.#drawCollapsed();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
stepStart(stepNum, totalSteps, stepId) {
|
|
224
|
+
this.#clearCollapsed();
|
|
225
|
+
this.#stopHeartbeat();
|
|
226
|
+
this.#drawPipelineBar(stepId);
|
|
227
|
+
this.#stream.write(`${BOLD}[${stepNum}/${totalSteps}]${RESET} ${stepId}...\n`);
|
|
228
|
+
this.#toolCount = 0;
|
|
229
|
+
this.#toolHistory = [];
|
|
230
|
+
this.#stepStart = Date.now();
|
|
231
|
+
this.#currentStepId = stepId;
|
|
232
|
+
if (!this.#expanded) this.#drawCollapsed();
|
|
233
|
+
this.#startHeartbeat();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
stepDone(stepId) {
|
|
237
|
+
if (!this.#stepHistory.includes(stepId)) {
|
|
238
|
+
this.#stepHistory.push(stepId);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ── Pipeline bar (COMP-TUI-1) ────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
pipelineBar(stepNum, totalSteps, stepId, stepHistory) {
|
|
245
|
+
this.#stepHistory = (stepHistory ?? []).map(h => h.stepId ?? h);
|
|
246
|
+
this.#drawPipelineBar(stepId);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#drawPipelineBar(currentStepId) {
|
|
250
|
+
if (!this.#isTTY) return;
|
|
251
|
+
|
|
252
|
+
const doneSet = new Set(this.#stepHistory);
|
|
253
|
+
const currentIdx = PIPELINE_STEPS.indexOf(currentStepId);
|
|
254
|
+
|
|
255
|
+
// Build the visible window: all done + current + up to 3 pending
|
|
256
|
+
const cols = this.#stream.columns || 80;
|
|
257
|
+
let visibleSteps;
|
|
258
|
+
|
|
259
|
+
// Calculate full bar length to decide if we need a sliding window
|
|
260
|
+
const fullLen = PIPELINE_STEPS.reduce((sum, s, i) => {
|
|
261
|
+
const label = STEP_LABELS[s] || s;
|
|
262
|
+
return sum + label.length + 3 + (i < PIPELINE_STEPS.length - 1 ? 3 : 0); // icon + space + label + separator
|
|
263
|
+
}, 4); // 4 for leading indent
|
|
264
|
+
|
|
265
|
+
if (fullLen <= cols) {
|
|
266
|
+
visibleSteps = PIPELINE_STEPS;
|
|
267
|
+
} else {
|
|
268
|
+
// Sliding window: show done steps, current, and up to 3 pending
|
|
269
|
+
const start = 0;
|
|
270
|
+
const end = Math.min(PIPELINE_STEPS.length, (currentIdx < 0 ? 0 : currentIdx) + 4);
|
|
271
|
+
visibleSteps = PIPELINE_STEPS.slice(start, end);
|
|
272
|
+
|
|
273
|
+
// If window is still too wide, trim from the start (keep current visible)
|
|
274
|
+
while (visibleSteps.length > 3) {
|
|
275
|
+
const windowLen = visibleSteps.reduce((sum, s, i) => {
|
|
276
|
+
const label = STEP_LABELS[s] || s;
|
|
277
|
+
return sum + label.length + 3 + (i < visibleSteps.length - 1 ? 3 : 0);
|
|
278
|
+
}, 4);
|
|
279
|
+
if (windowLen <= cols) break;
|
|
280
|
+
visibleSteps.shift();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const parts = visibleSteps.map(s => {
|
|
285
|
+
const label = STEP_LABELS[s] || s;
|
|
286
|
+
if (s === currentStepId) {
|
|
287
|
+
return `${CYAN}${BOLD}\u25C9 ${label}${RESET}`;
|
|
288
|
+
} else if (doneSet.has(s)) {
|
|
289
|
+
return `${GREEN}\u25CF ${label}${RESET}`;
|
|
290
|
+
} else {
|
|
291
|
+
return `${DIM}\u25CB ${label}${RESET}`;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
this.#stream.write(` ${parts.join(` ${DIM}\u2500${RESET} `)}\n`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
subFlowStep(flowName, stepId) {
|
|
299
|
+
this.#clearCollapsed();
|
|
300
|
+
this.#stopHeartbeat();
|
|
301
|
+
this.#stream.write(` ${CYAN}[${flowName}]${RESET} ${stepId}...\n`);
|
|
302
|
+
this.#toolHistory = [];
|
|
303
|
+
this.#stepStart = Date.now();
|
|
304
|
+
this.#currentStepId = `${flowName}/${stepId}`;
|
|
305
|
+
if (!this.#expanded) this.#drawCollapsed();
|
|
306
|
+
this.#startHeartbeat();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
retry(flowName, stepId, agent) {
|
|
310
|
+
this.#clearCollapsed();
|
|
311
|
+
this.#stream.write(` ${YELLOW}[${flowName}] ↻ Retrying ${stepId}${agent ? ` (${agent})` : ''}${RESET}\n`);
|
|
312
|
+
if (!this.#expanded) this.#drawCollapsed();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
fix(flowName, fixAgent, stepId) {
|
|
316
|
+
this.#clearCollapsed();
|
|
317
|
+
this.#stream.write(` ${YELLOW}[${flowName}] ↻ Fix (${fixAgent}) for ${stepId}${RESET}\n`);
|
|
318
|
+
if (!this.#expanded) this.#drawCollapsed();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
warn(msg) {
|
|
322
|
+
this.#clearCollapsed();
|
|
323
|
+
this.#stream.write(` ${RED}⚠ ${msg}${RESET}\n`);
|
|
324
|
+
if (!this.#expanded) this.#drawCollapsed();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
success(msg) {
|
|
328
|
+
this.#clearCollapsed();
|
|
329
|
+
this.#stream.write(` ${GREEN}✓${RESET} ${msg}\n`);
|
|
330
|
+
if (!this.#expanded) this.#drawCollapsed();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
info(msg) {
|
|
334
|
+
this.#clearCollapsed();
|
|
335
|
+
this.#stream.write(`${msg}\n`);
|
|
336
|
+
if (!this.#expanded) this.#drawCollapsed();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
debug(msg) {
|
|
340
|
+
if (!process.env.COMPOSE_DEBUG) return;
|
|
341
|
+
if (this.#expanded) {
|
|
342
|
+
this.#stream.write(` ${DIM}[debug] ${msg}${RESET}\n`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
finish() {
|
|
347
|
+
this.#clearCollapsed();
|
|
348
|
+
this.#stopHeartbeat();
|
|
349
|
+
this.#stopListening();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── Collapsed view: last N tools + heartbeat + hints ──────────────────
|
|
353
|
+
|
|
354
|
+
#drawCollapsed() {
|
|
355
|
+
if (!this.#isTTY) return;
|
|
356
|
+
|
|
357
|
+
// First erase any previously drawn collapsed block
|
|
358
|
+
this.#eraseCollapsedBlock();
|
|
359
|
+
|
|
360
|
+
const lines = [];
|
|
361
|
+
|
|
362
|
+
// Last N tool events
|
|
363
|
+
const recent = this.#toolHistory.slice(-COLLAPSED_LINES);
|
|
364
|
+
for (const t of recent) {
|
|
365
|
+
const color = colorForTool(t.tool);
|
|
366
|
+
lines.push(` ${color}↳ ${t.tool}${RESET}${t.detail ? ': ' + t.detail : ''}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Heartbeat / status line
|
|
370
|
+
const elapsed = Math.round((Date.now() - this.#stepStart) / 1000);
|
|
371
|
+
const countStr = this.#toolCount > 0 ? `${this.#toolCount} calls` : 'waiting';
|
|
372
|
+
lines.push(` ${DIM}${this.#currentStepId} · ${elapsed}s · ${countStr}${RESET}`);
|
|
373
|
+
|
|
374
|
+
// Key hints
|
|
375
|
+
lines.push(HINT_BAR);
|
|
376
|
+
|
|
377
|
+
// Write all lines
|
|
378
|
+
for (const line of lines) {
|
|
379
|
+
this.#stream.write(line + '\n');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
this.#drawnCollapsedLines = lines.length;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
#clearCollapsed() {
|
|
386
|
+
this.#eraseCollapsedBlock();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
#eraseCollapsedBlock() {
|
|
390
|
+
if (!this.#isTTY || this.#drawnCollapsedLines === 0) return;
|
|
391
|
+
// Move up and erase each line
|
|
392
|
+
for (let i = 0; i < this.#drawnCollapsedLines; i++) {
|
|
393
|
+
this.#stream.write(MOVE_UP(1) + ERASE_LINE);
|
|
394
|
+
}
|
|
395
|
+
this.#drawnCollapsedLines = 0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// ── Heartbeat ─────────────────────────────────────────────────────────
|
|
399
|
+
|
|
400
|
+
#startHeartbeat() {
|
|
401
|
+
this.#stopHeartbeat();
|
|
402
|
+
this.#heartbeatTimer = setInterval(() => {
|
|
403
|
+
if (!this.#expanded) {
|
|
404
|
+
this.#drawCollapsed();
|
|
405
|
+
} else {
|
|
406
|
+
// In expanded mode, just print elapsed time
|
|
407
|
+
const elapsed = Math.round((Date.now() - this.#stepStart) / 1000);
|
|
408
|
+
this.#stream.write(` ${DIM}… ${this.#currentStepId} (${elapsed}s)${RESET}\n`);
|
|
409
|
+
}
|
|
410
|
+
}, 5_000);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
#stopHeartbeat() {
|
|
414
|
+
if (this.#heartbeatTimer) {
|
|
415
|
+
clearInterval(this.#heartbeatTimer);
|
|
416
|
+
this.#heartbeatTimer = null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// ── Findings table (COMP-TUI-3) ──────────────────────────────────────
|
|
421
|
+
|
|
422
|
+
findings(items) {
|
|
423
|
+
if (!items || items.length === 0) return;
|
|
424
|
+
|
|
425
|
+
const SEV_COLORS = { 'must-fix': RED, 'should-fix': YELLOW, nit: GRAY };
|
|
426
|
+
const SEV_ORDER = ['must-fix', 'should-fix', 'nit'];
|
|
427
|
+
|
|
428
|
+
// Parse violation strings into structured rows
|
|
429
|
+
const rows = items.map(item => {
|
|
430
|
+
if (typeof item === 'object' && item.severity) {
|
|
431
|
+
return { sev: item.severity, file: item.file ?? '', desc: item.description ?? item.message ?? '' };
|
|
432
|
+
}
|
|
433
|
+
const s = String(item);
|
|
434
|
+
// Try to extract file:line reference and severity
|
|
435
|
+
const fileMatch = s.match(/(\S+\.\w+:\d+)/);
|
|
436
|
+
const file = fileMatch ? fileMatch[1] : '';
|
|
437
|
+
let sev = 'nit';
|
|
438
|
+
if (/must.?fix|error|critical/i.test(s)) sev = 'must-fix';
|
|
439
|
+
else if (/should.?fix|warning/i.test(s)) sev = 'should-fix';
|
|
440
|
+
// Strip severity prefix, file reference, and connectors to get clean description
|
|
441
|
+
const desc = s
|
|
442
|
+
.replace(/^(must[- ]?fix|should[- ]?fix|nit|error|warning|critical)\s*[:]\s*/i, '')
|
|
443
|
+
.replace(fileMatch?.[0] ?? '', '')
|
|
444
|
+
.replace(/^\s*[-:—]\s*/, '')
|
|
445
|
+
.trim() || s;
|
|
446
|
+
return { sev, file, desc };
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Sort by severity
|
|
450
|
+
rows.sort((a, b) => SEV_ORDER.indexOf(a.sev) - SEV_ORDER.indexOf(b.sev));
|
|
451
|
+
|
|
452
|
+
// Column widths
|
|
453
|
+
const cols = this.#stream.columns || 100;
|
|
454
|
+
const sevW = Math.max(10, ...rows.map(r => r.sev.length + 2));
|
|
455
|
+
const fileW = Math.max(10, ...rows.map(r => r.file.length + 2));
|
|
456
|
+
const descW = Math.max(10, cols - sevW - fileW - 10); // account for borders + padding
|
|
457
|
+
const totalW = sevW + fileW + descW + 4; // 4 for border chars
|
|
458
|
+
|
|
459
|
+
const pad = (str, w) => str.length >= w ? str.slice(0, w) : str + ' '.repeat(w - str.length);
|
|
460
|
+
const title = ' Review Findings ';
|
|
461
|
+
|
|
462
|
+
this.#clearCollapsed();
|
|
463
|
+
|
|
464
|
+
// Top border
|
|
465
|
+
const topRule = '\u2500'.repeat(Math.max(0, totalW - 2 - title.length));
|
|
466
|
+
this.#stream.write(` \u250C\u2500${BOLD}${title}${RESET}${'\u2500'.repeat(Math.max(0, topRule.length))}\u2510\n`);
|
|
467
|
+
|
|
468
|
+
// Header
|
|
469
|
+
this.#stream.write(` \u2502 ${BOLD}${pad('SEV', sevW)}${RESET}\u2502 ${BOLD}${pad('FILE', fileW)}${RESET}\u2502 ${BOLD}${pad('FINDING', descW)}${RESET}\u2502\n`);
|
|
470
|
+
this.#stream.write(` \u251C${'\u2500'.repeat(sevW + 1)}\u253C${'\u2500'.repeat(fileW + 2)}\u253C${'\u2500'.repeat(descW + 2)}\u2524\n`);
|
|
471
|
+
|
|
472
|
+
// Data rows
|
|
473
|
+
for (const row of rows) {
|
|
474
|
+
const sevColor = SEV_COLORS[row.sev] ?? GRAY;
|
|
475
|
+
this.#stream.write(` \u2502 ${sevColor}${pad(row.sev, sevW)}${RESET}\u2502 ${pad(row.file, fileW)}\u2502 ${pad(row.desc, descW)}\u2502\n`);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Bottom border
|
|
479
|
+
this.#stream.write(` \u2514${'\u2500'.repeat(sevW + 1)}\u2534${'\u2500'.repeat(fileW + 2)}\u2534${'\u2500'.repeat(descW + 2)}\u2518\n`);
|
|
480
|
+
|
|
481
|
+
if (!this.#expanded) this.#drawCollapsed();
|
|
482
|
+
}
|
|
483
|
+
}
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for Compose CLI and frontend.
|
|
3
|
+
*
|
|
4
|
+
* Single canonical source for step/gate labels.
|
|
5
|
+
* Importable by both CLI (lib/build.js, lib/new.js) and frontend (if bundled).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Canonical labels for lifecycle steps and gates.
|
|
10
|
+
* Used in summary fallback chains and UI rendering.
|
|
11
|
+
*/
|
|
12
|
+
export const STEP_LABELS = {
|
|
13
|
+
// Lifecycle steps
|
|
14
|
+
explore_design: 'Design',
|
|
15
|
+
prd: 'PRD',
|
|
16
|
+
architecture: 'Architecture',
|
|
17
|
+
blueprint: 'Blueprint',
|
|
18
|
+
verification: 'Verification',
|
|
19
|
+
plan: 'Plan',
|
|
20
|
+
execute: 'Execute',
|
|
21
|
+
report: 'Report',
|
|
22
|
+
docs: 'Docs',
|
|
23
|
+
ship: 'Ship',
|
|
24
|
+
|
|
25
|
+
// Gate steps
|
|
26
|
+
design_gate: 'Design Gate',
|
|
27
|
+
prd_gate: 'PRD Gate',
|
|
28
|
+
plan_gate: 'Plan Gate',
|
|
29
|
+
architecture_gate: 'Architecture Gate',
|
|
30
|
+
report_gate: 'Report Gate',
|
|
31
|
+
ship_gate: 'Ship Gate',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Map gate step IDs to their artifact filenames.
|
|
36
|
+
* Used to derive artifact paths for gate enrichment.
|
|
37
|
+
*/
|
|
38
|
+
export const GATE_ARTIFACTS = {
|
|
39
|
+
design_gate: 'design.md',
|
|
40
|
+
prd_gate: 'prd.md',
|
|
41
|
+
architecture_gate: 'architecture.md',
|
|
42
|
+
plan_gate: 'plan.md',
|
|
43
|
+
report_gate: 'report.md',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Title-case a step ID for display when no label exists.
|
|
48
|
+
* e.g. "some_step" -> "Some Step"
|
|
49
|
+
*/
|
|
50
|
+
export function titleCase(stepId) {
|
|
51
|
+
if (!stepId) return 'Unknown';
|
|
52
|
+
return stepId
|
|
53
|
+
.replace(/_/g, ' ')
|
|
54
|
+
.replace(/\b\w/g, c => c.toUpperCase());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build a human-readable summary for a gate step.
|
|
59
|
+
* Fallback chain: response.summary -> STEP_LABELS + featureCode -> titleCase -> "Gate: stepId"
|
|
60
|
+
*/
|
|
61
|
+
export function buildGateSummary(stepId, featureCode, responseSummary) {
|
|
62
|
+
if (responseSummary) return responseSummary;
|
|
63
|
+
const label = STEP_LABELS[stepId];
|
|
64
|
+
if (label && featureCode) return `${label} for ${featureCode}`;
|
|
65
|
+
if (label) return label;
|
|
66
|
+
const titled = titleCase(stepId);
|
|
67
|
+
if (titled !== 'Unknown') return titled;
|
|
68
|
+
return `Gate: ${stepId}`;
|
|
69
|
+
}
|