@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.
Files changed (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1014 -0
  3. package/bin/compose.js +1515 -0
  4. package/dist/assets/_baseUniq-CQwX6VLz.js +1 -0
  5. package/dist/assets/arc-SxJ2J1sh.js +1 -0
  6. package/dist/assets/architectureDiagram-Q4EWVU46-BykunY1F.js +36 -0
  7. package/dist/assets/blockDiagram-DXYQGD6D-ohAKBOUw.js +132 -0
  8. package/dist/assets/c4Diagram-AHTNJAMY-DBDC3ENB.js +10 -0
  9. package/dist/assets/channel-DGElom1e.js +1 -0
  10. package/dist/assets/chunk-4BX2VUAB-Cv93Z7uM.js +1 -0
  11. package/dist/assets/chunk-4TB4RGXK-DE0WBDkj.js +206 -0
  12. package/dist/assets/chunk-55IACEB6-CE1EXenG.js +1 -0
  13. package/dist/assets/chunk-EDXVE4YY-DA7Ana6H.js +1 -0
  14. package/dist/assets/chunk-FMBD7UC4-CTDIPA3p.js +15 -0
  15. package/dist/assets/chunk-OYMX7WX6-uGBaPaTX.js +231 -0
  16. package/dist/assets/chunk-QZHKN3VN-CYlnXuUO.js +1 -0
  17. package/dist/assets/chunk-YZCP3GAM-ojGkzcZK.js +1 -0
  18. package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +1 -0
  19. package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +1 -0
  20. package/dist/assets/clone-DUJKJXd7.js +1 -0
  21. package/dist/assets/cose-bilkent-S5V4N54A-Bktn9hL-.js +1 -0
  22. package/dist/assets/dagre-KV5264BT-DFaSzuRF.js +4 -0
  23. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  24. package/dist/assets/diagram-5BDNPKRD-DnfmDzEm.js +10 -0
  25. package/dist/assets/diagram-G4DWMVQ6-Bm8W9YnG.js +24 -0
  26. package/dist/assets/diagram-MMDJMWI5-B5-TSKvp.js +43 -0
  27. package/dist/assets/diagram-TYMM5635-ls4rqlky.js +24 -0
  28. package/dist/assets/erDiagram-SMLLAGMA-giG6WO-r.js +85 -0
  29. package/dist/assets/flowDiagram-DWJPFMVM-XvlUuz-7.js +162 -0
  30. package/dist/assets/ganttDiagram-T4ZO3ILL-hLBV57oV.js +292 -0
  31. package/dist/assets/gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js +106 -0
  32. package/dist/assets/graph-D0Cfv00Y.js +1 -0
  33. package/dist/assets/index-CUd6pFGF.css +1 -0
  34. package/dist/assets/index-DReRlzZI.js +1144 -0
  35. package/dist/assets/infoDiagram-42DDH7IO-DbqRsOo3.js +2 -0
  36. package/dist/assets/init-Gi6I4Gst.js +1 -0
  37. package/dist/assets/ishikawaDiagram-UXIWVN3A-DnCdx7zb.js +70 -0
  38. package/dist/assets/journeyDiagram-VCZTEJTY-CfD7eNcP.js +139 -0
  39. package/dist/assets/kanban-definition-6JOO6SKY-BYaO9-mK.js +89 -0
  40. package/dist/assets/katex-DkKDou_j.js +257 -0
  41. package/dist/assets/layout-Bj72wOEB.js +1 -0
  42. package/dist/assets/linear-BRFo114D.js +1 -0
  43. package/dist/assets/min-GCHnKlJS.js +1 -0
  44. package/dist/assets/mindmap-definition-QFDTVHPH-n0PMebY4.js +96 -0
  45. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  46. package/dist/assets/pieDiagram-DEJITSTG-pN4CljHF.js +30 -0
  47. package/dist/assets/quadrantDiagram-34T5L4WZ-DNoAy8-D.js +7 -0
  48. package/dist/assets/requirementDiagram-MS252O5E-BhtY05PT.js +84 -0
  49. package/dist/assets/sankeyDiagram-XADWPNL6-B6AD-16A.js +10 -0
  50. package/dist/assets/sequenceDiagram-FGHM5R23-DShHM-uk.js +157 -0
  51. package/dist/assets/stateDiagram-FHFEXIEX-DMxn7HTo.js +1 -0
  52. package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +1 -0
  53. package/dist/assets/timeline-definition-GMOUNBTQ-Cdu6uq52.js +120 -0
  54. package/dist/assets/vennDiagram-DHZGUBPP-CpK29iRe.js +34 -0
  55. package/dist/assets/wardley-RL74JXVD-BQgSkdcO.js +162 -0
  56. package/dist/assets/wardleyDiagram-NUSXRM2D-DJHYev6O.js +20 -0
  57. package/dist/assets/xychartDiagram-5P7HB3ND-1d75pbaO.js +7 -0
  58. package/dist/index.html +30 -0
  59. package/lib/agent-chains.js +65 -0
  60. package/lib/agent-string.js +86 -0
  61. package/lib/budget-ledger.js +86 -0
  62. package/lib/build-all.js +162 -0
  63. package/lib/build-dag.js +120 -0
  64. package/lib/build-stream-writer.js +190 -0
  65. package/lib/build.js +2997 -0
  66. package/lib/capability-checker.js +53 -0
  67. package/lib/cert-inject.js +38 -0
  68. package/lib/cli-progress.js +483 -0
  69. package/lib/constants.js +69 -0
  70. package/lib/cross-layer-audit.js +84 -0
  71. package/lib/debug-discipline.js +173 -0
  72. package/lib/feature-json.js +106 -0
  73. package/lib/gate-prompt.js +291 -0
  74. package/lib/gate-tiers.js +194 -0
  75. package/lib/health-history.js +119 -0
  76. package/lib/health-score.js +227 -0
  77. package/lib/ideabox.js +570 -0
  78. package/lib/import.js +244 -0
  79. package/lib/migrate-roadmap.js +94 -0
  80. package/lib/model-pricing.js +67 -0
  81. package/lib/new.js +413 -0
  82. package/lib/pipeline-cli.js +489 -0
  83. package/lib/plan-parser.js +103 -0
  84. package/lib/qa-scoping.js +474 -0
  85. package/lib/questionnaire.js +200 -0
  86. package/lib/resolve-port.js +7 -0
  87. package/lib/result-normalizer.js +349 -0
  88. package/lib/review-lenses.js +166 -0
  89. package/lib/roadmap-gen.js +210 -0
  90. package/lib/roadmap-parser.js +176 -0
  91. package/lib/server-probe.js +23 -0
  92. package/lib/staleness.js +87 -0
  93. package/lib/step-prompt.js +260 -0
  94. package/lib/step-validator.js +49 -0
  95. package/lib/stratum-mcp-client.js +365 -0
  96. package/lib/team-flag.js +46 -0
  97. package/lib/test-bootstrap.js +401 -0
  98. package/lib/triage.js +274 -0
  99. package/lib/vision-writer.js +391 -0
  100. package/package.json +111 -0
  101. package/pipelines/bug-fix.stratum.yaml +230 -0
  102. package/pipelines/build.stratum.yaml +498 -0
  103. package/pipelines/content.stratum.yaml +112 -0
  104. package/pipelines/coverage-sweep.stratum.yaml +52 -0
  105. package/pipelines/refactor.stratum.yaml +169 -0
  106. package/pipelines/research.stratum.yaml +88 -0
  107. package/pipelines/review-fix.stratum.yaml +109 -0
  108. package/presets/team-feature.stratum.yaml +105 -0
  109. package/presets/team-research.stratum.yaml +108 -0
  110. package/presets/team-review.stratum.yaml +106 -0
  111. package/scripts/agent-activity-hook.sh +31 -0
  112. package/scripts/agent-error-hook.sh +28 -0
  113. package/scripts/analyze-orphans.mjs +50 -0
  114. package/scripts/find-orphans.mjs +26 -0
  115. package/scripts/fix-phases.mjs +49 -0
  116. package/scripts/generate-stratum-spec.mjs +137 -0
  117. package/scripts/import-roadmap.mjs +116 -0
  118. package/scripts/phase-audit.mjs +33 -0
  119. package/scripts/run-pipeline.mjs +314 -0
  120. package/scripts/session-end-hook.sh +18 -0
  121. package/scripts/session-start-hook.sh +38 -0
  122. package/scripts/vision-hook.sh +104 -0
  123. package/scripts/vision-track.mjs +554 -0
  124. package/scripts/wire-all-orphans.mjs +108 -0
  125. package/scripts/wire-orphans.mjs +164 -0
  126. package/server/activity-routes.js +123 -0
  127. package/server/agent-health.js +197 -0
  128. package/server/agent-hooks.js +102 -0
  129. package/server/agent-mcp.js +10 -0
  130. package/server/agent-registry.js +95 -0
  131. package/server/agent-server.js +290 -0
  132. package/server/agent-spawn.js +251 -0
  133. package/server/agent-templates.js +77 -0
  134. package/server/artifact-manager.js +247 -0
  135. package/server/artifact-templates/architecture.md +28 -0
  136. package/server/artifact-templates/blueprint.md +21 -0
  137. package/server/artifact-templates/design.md +36 -0
  138. package/server/artifact-templates/plan.md +25 -0
  139. package/server/artifact-templates/prd.md +43 -0
  140. package/server/artifact-templates/report.md +40 -0
  141. package/server/block-tracker.js +90 -0
  142. package/server/build-stream-bridge.js +502 -0
  143. package/server/coalescing-buffer.js +46 -0
  144. package/server/compose-mcp-tools.js +479 -0
  145. package/server/compose-mcp.js +324 -0
  146. package/server/connectors/agent-connector.js +78 -0
  147. package/server/connectors/claude-sdk-connector.js +198 -0
  148. package/server/connectors/codex-connector.js +240 -0
  149. package/server/connectors/connector-discovery.js +18 -0
  150. package/server/connectors/connector-runtime.js +13 -0
  151. package/server/connectors/opencode-connector.js +200 -0
  152. package/server/design-routes.js +540 -0
  153. package/server/design-session.js +161 -0
  154. package/server/feature-scan.js +593 -0
  155. package/server/file-watcher.js +284 -0
  156. package/server/find-root.js +29 -0
  157. package/server/graph-export.js +343 -0
  158. package/server/ideabox-cache.js +77 -0
  159. package/server/ideabox-routes.js +294 -0
  160. package/server/index.js +156 -0
  161. package/server/model-tiers.js +49 -0
  162. package/server/pipeline-routes.js +288 -0
  163. package/server/policy-evaluator.js +36 -0
  164. package/server/project-root.js +122 -0
  165. package/server/security.js +23 -0
  166. package/server/session-manager.js +403 -0
  167. package/server/session-routes.js +190 -0
  168. package/server/session-store.js +107 -0
  169. package/server/settings-routes.js +35 -0
  170. package/server/settings-store.js +234 -0
  171. package/server/stratum-api.js +102 -0
  172. package/server/stratum-client.js +192 -0
  173. package/server/stratum-sync.js +193 -0
  174. package/server/summarizer.js +139 -0
  175. package/server/supervisor.js +196 -0
  176. package/server/vision-routes.js +668 -0
  177. package/server/vision-server.js +393 -0
  178. package/server/vision-store.js +360 -0
  179. package/server/vision-utils.js +179 -0
  180. package/server/worktree-gc.js +137 -0
  181. package/templates/ROADMAP.md +46 -0
@@ -0,0 +1,247 @@
1
+ /**
2
+ * artifact-manager.js — Artifact schema definitions, quality assessment,
3
+ * scaffolding, and template loading.
4
+ *
5
+ * Stateless — reads files and schemas, computes signals, returns data.
6
+ * No persistence beyond what the lifecycle manager already stores.
7
+ */
8
+
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ /** Phases that produce named artifacts (derived from contracts/lifecycle.json). */
13
+ const PHASE_ARTIFACTS = {
14
+ explore_design: 'design.md',
15
+ prd: 'prd.md',
16
+ architecture: 'architecture.md',
17
+ blueprint: 'blueprint.md',
18
+ plan: 'plan.md',
19
+ report: 'report.md',
20
+ };
21
+
22
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
+ const TEMPLATES_DIR = path.join(__dirname, 'artifact-templates');
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Artifact schemas
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export const ARTIFACT_SCHEMAS = {
30
+ 'design.md': {
31
+ requiredSections: ['Problem', 'Goal'],
32
+ optionalSections: ['Related Documents', 'Decision \\d+', 'Files', 'Open Questions', 'Resolved Questions'],
33
+ minWordCount: 200,
34
+ },
35
+ 'prd.md': {
36
+ requiredSections: ['Problem Statement', 'Goals & Non-Goals', 'Requirements'],
37
+ optionalSections: ['Success Criteria', 'User Stories', 'Constraints', 'Open Questions'],
38
+ minWordCount: 300,
39
+ },
40
+ 'architecture.md': {
41
+ requiredSections: ['Problem', 'Proposals'],
42
+ optionalSections: ['Trade-offs', 'Decision', 'Component Diagram'],
43
+ minWordCount: 200,
44
+ },
45
+ 'blueprint.md': {
46
+ requiredSections: ['File Plan'],
47
+ optionalSections: ['Corrections Table'],
48
+ minWordCount: 300,
49
+ },
50
+ 'plan.md': {
51
+ requiredSections: ['Task Order', 'Task 1'],
52
+ optionalSections: ['Files Summary'],
53
+ minWordCount: 150,
54
+ },
55
+ 'report.md': {
56
+ requiredSections: ['Summary', 'Files Changed'],
57
+ optionalSections: ['Delivered vs Planned', 'Architecture Deviations', 'Key Decisions', 'Test Coverage', 'Known Issues', 'Lessons Learned'],
58
+ minWordCount: 200,
59
+ },
60
+ };
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Startup invariant: schema keys must match PHASE_ARTIFACTS values
64
+ // ---------------------------------------------------------------------------
65
+
66
+ const schemaKeys = new Set(Object.keys(ARTIFACT_SCHEMAS));
67
+ const artifactValues = new Set(Object.values(PHASE_ARTIFACTS));
68
+ if (schemaKeys.size !== artifactValues.size || [...schemaKeys].some(k => !artifactValues.has(k))) {
69
+ throw new Error('ARTIFACT_SCHEMAS keys and PHASE_ARTIFACTS values are out of sync');
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Private helpers
74
+ // ---------------------------------------------------------------------------
75
+
76
+ function _validateFeatureCode(featureCode) {
77
+ if (!featureCode || !/^[A-Za-z0-9_-]+$/.test(featureCode)) {
78
+ throw new Error(`Invalid featureCode: ${featureCode}`);
79
+ }
80
+ }
81
+
82
+ function _featurePath(featureRoot, featureCode) {
83
+ _validateFeatureCode(featureCode);
84
+ const realRoot = fs.realpathSync(featureRoot);
85
+ const candidate = path.resolve(realRoot, featureCode);
86
+ if (!candidate.startsWith(realRoot + path.sep) && candidate !== realRoot) {
87
+ throw new Error(`Path escapes feature root: ${featureCode}`);
88
+ }
89
+ // Tier 2: if candidate exists, verify real path
90
+ if (fs.existsSync(candidate)) {
91
+ const realCandidate = fs.realpathSync(candidate);
92
+ if (!realCandidate.startsWith(realRoot + path.sep) && realCandidate !== realRoot) {
93
+ throw new Error(`Symlink escapes feature root: ${featureCode}`);
94
+ }
95
+ }
96
+ return candidate;
97
+ }
98
+
99
+ function _extractSections(markdown) {
100
+ const headings = [];
101
+ for (const line of markdown.split('\n')) {
102
+ const m = line.match(/^#{1,4}\s+(.+)$/);
103
+ if (m) {
104
+ let text = m[1].trim();
105
+ // Strip trailing punctuation (with optional leading/trailing whitespace)
106
+ text = text.replace(/\s*[:—–]+\s*$/, '');
107
+ headings.push(text);
108
+ }
109
+ }
110
+ return headings;
111
+ }
112
+
113
+ function _matchPattern(heading, pattern) {
114
+ if (pattern.includes('\\d+')) {
115
+ const re = new RegExp(`^${pattern}$`, 'i');
116
+ return re.test(heading);
117
+ }
118
+ return heading.toLowerCase() === pattern.toLowerCase();
119
+ }
120
+
121
+ function _matchSections(foundHeadings, schema) {
122
+ const found = [];
123
+ const missing = [];
124
+ for (const pattern of schema.requiredSections) {
125
+ if (foundHeadings.some(h => _matchPattern(h, pattern))) {
126
+ found.push(pattern);
127
+ } else {
128
+ missing.push(pattern);
129
+ }
130
+ }
131
+
132
+ const optional = [];
133
+ for (const pattern of schema.optionalSections) {
134
+ if (foundHeadings.some(h => _matchPattern(h, pattern))) {
135
+ optional.push(pattern);
136
+ }
137
+ }
138
+
139
+ return { found, missing, optional };
140
+ }
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // ArtifactManager
144
+ // ---------------------------------------------------------------------------
145
+
146
+ export class ArtifactManager {
147
+ #featureRoot;
148
+
149
+ constructor(featureRoot) {
150
+ this.#featureRoot = featureRoot;
151
+ }
152
+
153
+ assess(featureCode) {
154
+ const artifacts = {};
155
+ for (const filename of Object.keys(ARTIFACT_SCHEMAS)) {
156
+ artifacts[filename] = this.assessOne(featureCode, filename);
157
+ }
158
+ return { artifacts };
159
+ }
160
+
161
+ assessOne(featureCode, filename) {
162
+ if (!ARTIFACT_SCHEMAS[filename]) {
163
+ throw new Error(`Unknown artifact: ${filename}`);
164
+ }
165
+ const dir = _featurePath(this.#featureRoot, featureCode);
166
+ const filePath = path.join(dir, filename);
167
+ const schema = ARTIFACT_SCHEMAS[filename];
168
+
169
+ // Check file-level symlink escape
170
+ if (fs.existsSync(filePath)) {
171
+ const realFile = fs.realpathSync(filePath);
172
+ const realRoot = fs.realpathSync(this.#featureRoot);
173
+ if (!realFile.startsWith(realRoot + path.sep)) {
174
+ throw new Error(`Symlink escapes feature root: ${filename}`);
175
+ }
176
+ }
177
+
178
+ if (!fs.existsSync(filePath)) {
179
+ return {
180
+ exists: false,
181
+ wordCount: 0,
182
+ meetsMinWordCount: false,
183
+ sections: { found: [], missing: [...schema.requiredSections], optional: [] },
184
+ completeness: 0,
185
+ lastModified: null,
186
+ };
187
+ }
188
+
189
+ const content = fs.readFileSync(filePath, 'utf-8');
190
+ const wordCount = content.split(/\s+/).filter(Boolean).length;
191
+ const headings = _extractSections(content);
192
+ const sections = _matchSections(headings, schema);
193
+ const completeness = schema.requiredSections.length > 0
194
+ ? sections.found.length / schema.requiredSections.length
195
+ : 1.0;
196
+ const lastModified = fs.statSync(filePath).mtime.toISOString();
197
+
198
+ return {
199
+ exists: true,
200
+ wordCount,
201
+ meetsMinWordCount: wordCount >= schema.minWordCount,
202
+ sections,
203
+ completeness,
204
+ lastModified,
205
+ };
206
+ }
207
+
208
+ scaffold(featureCode, options) {
209
+ const dir = _featurePath(this.#featureRoot, featureCode);
210
+ const created = [];
211
+ const skipped = [];
212
+
213
+ // Create directories
214
+ fs.mkdirSync(dir, { recursive: true });
215
+ fs.mkdirSync(path.join(dir, 'sessions'), { recursive: true });
216
+
217
+ for (const filename of Object.keys(ARTIFACT_SCHEMAS)) {
218
+ if (options?.only && !options.only.includes(filename)) continue;
219
+
220
+ const filePath = path.join(dir, filename);
221
+ if (fs.existsSync(filePath)) {
222
+ skipped.push(filename);
223
+ } else {
224
+ const template = this.getTemplate(filename);
225
+ fs.writeFileSync(filePath, template, 'utf-8');
226
+ created.push(filename);
227
+ }
228
+ }
229
+
230
+ return { created, skipped };
231
+ }
232
+
233
+ getTemplate(artifactName) {
234
+ if (!ARTIFACT_SCHEMAS[artifactName]) {
235
+ throw new Error(`Unknown artifact: ${artifactName}`);
236
+ }
237
+ const filePath = path.join(TEMPLATES_DIR, artifactName);
238
+ if (!fs.existsSync(filePath)) {
239
+ throw new Error(`Template not found: ${artifactName}`);
240
+ }
241
+ return fs.readFileSync(filePath, 'utf-8');
242
+ }
243
+
244
+ getSchema(artifactName) {
245
+ return ARTIFACT_SCHEMAS[artifactName] || null;
246
+ }
247
+ }
@@ -0,0 +1,28 @@
1
+ # <Feature Name>: Architecture
2
+
3
+ **Status:** ARCHITECTURE
4
+ **Date:** <date>
5
+
6
+ ## Problem
7
+
8
+ <!-- Technical problem statement -->
9
+
10
+ ## Proposals
11
+
12
+ ### Proposal A: <Name>
13
+
14
+ <!-- Description, trade-offs, diagram if helpful -->
15
+
16
+ ### Proposal B: <Name>
17
+
18
+ <!-- Description, trade-offs, diagram if helpful -->
19
+
20
+ ## Trade-offs
21
+
22
+ | Dimension | Proposal A | Proposal B |
23
+ |-----------|-----------|-----------|
24
+ | | | |
25
+
26
+ ## Decision
27
+
28
+ <!-- Which proposal was chosen and why -->
@@ -0,0 +1,21 @@
1
+ # <Feature Name>: Implementation Blueprint
2
+
3
+ **Status:** BLUEPRINT
4
+ **Date:** <date>
5
+ **Design:** [design.md](design.md)
6
+
7
+ ---
8
+
9
+ ## File Plan
10
+
11
+ | File | Action | Purpose |
12
+ |------|--------|---------|
13
+ | | | |
14
+
15
+ ---
16
+
17
+ ## Corrections Table
18
+
19
+ | Spec Assumption | Reality | Resolution |
20
+ |----------------|---------|------------|
21
+ | | | |
@@ -0,0 +1,36 @@
1
+ # <Feature Name>: Design
2
+
3
+ **Status:** DESIGN
4
+ **Date:** <date>
5
+
6
+ ## Related Documents
7
+
8
+ <!-- Link to roadmap, dependencies, and related features -->
9
+
10
+ ---
11
+
12
+ ## Problem
13
+
14
+ <!-- Describe the problem this feature solves -->
15
+
16
+ ## Goal
17
+
18
+ <!-- What does success look like? Scope and non-scope. -->
19
+
20
+ ---
21
+
22
+ ## Decision 1: <Title>
23
+
24
+ <!-- Describe the decision, options considered, and rationale -->
25
+
26
+ ---
27
+
28
+ ## Files
29
+
30
+ | File | Action | Purpose |
31
+ |------|--------|---------|
32
+ | | | |
33
+
34
+ ## Open Questions
35
+
36
+ <!-- List unresolved questions -->
@@ -0,0 +1,25 @@
1
+ # <Feature Name>: Implementation Plan
2
+
3
+ **Status:** PLAN
4
+ **Date:** <date>
5
+ **Blueprint:** [blueprint.md](blueprint.md)
6
+
7
+ ---
8
+
9
+ ## Task Order
10
+
11
+ <!-- List tasks in dependency order -->
12
+
13
+ ## Task 1: <Title>
14
+
15
+ - **File:** `<path>` (new/existing)
16
+ - **What:** <!-- What to implement -->
17
+ - **Pattern:** <!-- Existing pattern to follow -->
18
+ - **Test:** <!-- What test to write -->
19
+ - **Depends on:** <!-- Task dependencies -->
20
+
21
+ ## Files Summary
22
+
23
+ | File | Tasks |
24
+ |------|-------|
25
+ | | |
@@ -0,0 +1,43 @@
1
+ # <Feature Name>: PRD
2
+
3
+ **Status:** PRD
4
+ **Date:** <date>
5
+
6
+ ## Problem Statement
7
+
8
+ <!-- What problem are we solving and for whom? -->
9
+
10
+ ## Goals & Non-Goals
11
+
12
+ ### Goals
13
+ <!-- What this feature WILL do -->
14
+
15
+ ### Non-Goals
16
+ <!-- What this feature will NOT do -->
17
+
18
+ ## Requirements
19
+
20
+ ### MUST
21
+ <!-- Non-negotiable requirements -->
22
+
23
+ ### SHOULD
24
+ <!-- Important but not blocking -->
25
+
26
+ ### MAY
27
+ <!-- Nice-to-have -->
28
+
29
+ ## Success Criteria
30
+
31
+ <!-- How do we know this feature succeeded? -->
32
+
33
+ ## User Stories
34
+
35
+ <!-- As a <role>, I want <goal> so that <benefit> -->
36
+
37
+ ## Constraints
38
+
39
+ <!-- Technical, time, or resource constraints -->
40
+
41
+ ## Open Questions
42
+
43
+ <!-- Unresolved questions -->
@@ -0,0 +1,40 @@
1
+ # <Feature Name>: Implementation Report
2
+
3
+ **Status:** REPORT
4
+ **Date:** <date>
5
+
6
+ ## Summary
7
+
8
+ <!-- 2-3 sentence overview of what was delivered -->
9
+
10
+ ## Delivered vs Planned
11
+
12
+ | Planned | Delivered | Notes |
13
+ |---------|-----------|-------|
14
+ | | | |
15
+
16
+ ## Architecture Deviations
17
+
18
+ <!-- Any deviations from the architecture/blueprint -->
19
+
20
+ ## Key Decisions
21
+
22
+ <!-- Decisions made during implementation -->
23
+
24
+ ## Test Coverage
25
+
26
+ <!-- What's tested, what's not, and why -->
27
+
28
+ ## Files Changed
29
+
30
+ | File | Action | Purpose |
31
+ |------|--------|---------|
32
+ | | | |
33
+
34
+ ## Known Issues
35
+
36
+ <!-- Known bugs, tech debt, or incomplete work -->
37
+
38
+ ## Lessons Learned
39
+
40
+ <!-- What went well, what didn't, what to do differently -->
@@ -0,0 +1,90 @@
1
+ /**
2
+ * block-tracker.js — Work block detection and classification.
3
+ *
4
+ * These functions operate on a session object passed by reference,
5
+ * extracted from SessionManager to keep it under the line limit.
6
+ */
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Block detection
10
+ // ---------------------------------------------------------------------------
11
+
12
+ /**
13
+ * Detect block boundaries: a new block starts when the set of resolved items changes.
14
+ *
15
+ * @param {object} session — the current session object (mutated in place)
16
+ * @param {string[]} itemIds — current resolved item IDs
17
+ * @param {string} now — ISO timestamp
18
+ * @param {string} category — tool category string
19
+ */
20
+ export function updateBlock(session, itemIds, now, category) {
21
+ const currentSet = session.currentBlock?.itemIds;
22
+
23
+ const sameBlock = currentSet
24
+ && itemIds.length === currentSet.size
25
+ && itemIds.every(id => currentSet.has(id));
26
+
27
+ if (sameBlock) {
28
+ session.currentBlock.toolCount++;
29
+ if (category) session.currentBlock.categories.add(category);
30
+ return;
31
+ }
32
+
33
+ // Different set → close current block, start new one
34
+ closeCurrentBlock(session);
35
+
36
+ session.currentBlock = {
37
+ itemIds: new Set(itemIds),
38
+ startedAt: now,
39
+ toolCount: 1,
40
+ categories: new Set(category ? [category] : []),
41
+ };
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Block closing
46
+ // ---------------------------------------------------------------------------
47
+
48
+ /**
49
+ * Close the current block and push it to session.blocks.
50
+ *
51
+ * @param {object} session — the current session object (mutated in place)
52
+ */
53
+ export function closeCurrentBlock(session) {
54
+ if (!session?.currentBlock) return;
55
+
56
+ const block = session.currentBlock;
57
+ session.blocks.push({
58
+ itemIds: Array.from(block.itemIds),
59
+ startedAt: block.startedAt,
60
+ endedAt: new Date().toISOString(),
61
+ toolCount: block.toolCount,
62
+ categories: Array.from(block.categories || []),
63
+ intent: classifyBlockIntent(block),
64
+ });
65
+
66
+ session.currentBlock = null;
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Block classification
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /**
74
+ * Classify a work block's intent from the categories of tools used.
75
+ *
76
+ * @param {object} block — block with a categories Set
77
+ * @returns {'building'|'debugging'|'testing'|'exploring'|'thinking'|'mixed'}
78
+ */
79
+ export function classifyBlockIntent(block) {
80
+ const cats = block.categories || new Set();
81
+ const hasWriting = cats.has('writing');
82
+ const hasExecuting = cats.has('executing');
83
+ const hasReading = cats.has('reading') || cats.has('searching');
84
+ if (hasWriting && !hasExecuting) return 'building';
85
+ if (hasWriting && hasExecuting) return 'debugging';
86
+ if (hasExecuting && !hasWriting) return 'testing';
87
+ if (hasReading && !hasWriting && !hasExecuting) return 'exploring';
88
+ if (cats.size === 0) return 'thinking';
89
+ return 'mixed';
90
+ }