@smartmemory/compose 0.1.16-beta → 0.1.17-beta
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/package.json +1 -1
- package/server/feature-scan.js +55 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartmemory/compose",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17-beta",
|
|
4
4
|
"description": "Structured AI dev pipeline — goal-to-product orchestration with gates, iteration loops, and feature lifecycle management.",
|
|
5
5
|
"author": "SmartMemory",
|
|
6
6
|
"license": "MIT",
|
package/server/feature-scan.js
CHANGED
|
@@ -25,6 +25,11 @@ const STATUS_RE = /^\*\*Status:\*\*\s*(.+)$/im;
|
|
|
25
25
|
const DATE_RE = /^\*\*Date:\*\*\s*(.+)$/im;
|
|
26
26
|
const FEATURE_ID_RE = /^\*\*Feature\s*ID:\*\*\s*`?([^`\n]+)`?/im;
|
|
27
27
|
const RELATED_DOC_RE = /\[.*?\]\(\.\.\/([\w-]+)\//g;
|
|
28
|
+
// Match `**Predecessor:** CODE` and `**Successor:** CODE` lines in design docs.
|
|
29
|
+
// Captures the feature code (CODE_RE-shaped) optionally with parenthetical
|
|
30
|
+
// suffix like `COMP-WORKSPACE-{VISION,SESSIONS}` — we only take the bare code.
|
|
31
|
+
const PREDECESSOR_RE = /^\*\*Predecessor:\*\*\s*([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+)\b/gim;
|
|
32
|
+
const SUCCESSOR_RE = /^\*\*Successor:\*\*\s*([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+)\b/gim;
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
35
|
* Parse markdown frontmatter-style metadata from a file.
|
|
@@ -58,6 +63,19 @@ function parseRelatedFeatures(content) {
|
|
|
58
63
|
return [...related];
|
|
59
64
|
}
|
|
60
65
|
|
|
66
|
+
// Returns { predecessors: string[], successors: string[] } from `**Predecessor:**`
|
|
67
|
+
// and `**Successor:**` lines in design.md prose.
|
|
68
|
+
function parseSequenceRefs(content) {
|
|
69
|
+
const predecessors = new Set();
|
|
70
|
+
const successors = new Set();
|
|
71
|
+
let m;
|
|
72
|
+
const pre = new RegExp(PREDECESSOR_RE.source, 'gim');
|
|
73
|
+
while ((m = pre.exec(content)) !== null) predecessors.add(m[1]);
|
|
74
|
+
const suc = new RegExp(SUCCESSOR_RE.source, 'gim');
|
|
75
|
+
while ((m = suc.exec(content)) !== null) successors.add(m[1]);
|
|
76
|
+
return { predecessors: [...predecessors], successors: [...successors] };
|
|
77
|
+
}
|
|
78
|
+
|
|
61
79
|
/**
|
|
62
80
|
* Map free-text status strings to vision store status keys.
|
|
63
81
|
*/
|
|
@@ -139,6 +157,8 @@ export function scanFeatures(featuresDir) {
|
|
|
139
157
|
confidence: 0,
|
|
140
158
|
artifacts: [],
|
|
141
159
|
relatedFeatures: [],
|
|
160
|
+
predecessors: [],
|
|
161
|
+
successors: [],
|
|
142
162
|
};
|
|
143
163
|
|
|
144
164
|
// List artifacts
|
|
@@ -194,12 +214,21 @@ export function scanFeatures(featuresDir) {
|
|
|
194
214
|
for (const rel of parseRelatedFeatures(raw)) {
|
|
195
215
|
allRelated.add(rel);
|
|
196
216
|
}
|
|
217
|
+
// Collect sequence refs (predecessor → this → successor) — only design.md
|
|
218
|
+
// is authoritative; other docs may inherit copy-paste headers.
|
|
219
|
+
if (artifact === 'design.md') {
|
|
220
|
+
const seq = parseSequenceRefs(raw);
|
|
221
|
+
for (const code of seq.predecessors) feature.predecessors.push(code);
|
|
222
|
+
for (const code of seq.successors) feature.successors.push(code);
|
|
223
|
+
}
|
|
197
224
|
} catch { /* skip */ }
|
|
198
225
|
}
|
|
199
226
|
|
|
200
227
|
// Remove self-references
|
|
201
228
|
allRelated.delete(feature.name);
|
|
202
229
|
feature.relatedFeatures = [...allRelated];
|
|
230
|
+
feature.predecessors = [...new Set(feature.predecessors.filter(c => c !== feature.name))];
|
|
231
|
+
feature.successors = [...new Set(feature.successors.filter(c => c !== feature.name))];
|
|
203
232
|
|
|
204
233
|
// Infer phase from artifacts
|
|
205
234
|
feature.phase = inferPhase(feature.artifacts);
|
|
@@ -505,7 +534,7 @@ export function seedFeatures(features, store) {
|
|
|
505
534
|
featureItemMap.set(feature.name, featureItem.id);
|
|
506
535
|
}
|
|
507
536
|
|
|
508
|
-
// Second pass: create connections for related features
|
|
537
|
+
// Second pass: create connections for related features (undirected informs)
|
|
509
538
|
for (const feature of features) {
|
|
510
539
|
const fromId = featureItemMap.get(feature.name);
|
|
511
540
|
if (!fromId || !feature.relatedFeatures.length) continue;
|
|
@@ -528,6 +557,31 @@ export function seedFeatures(features, store) {
|
|
|
528
557
|
}
|
|
529
558
|
}
|
|
530
559
|
|
|
560
|
+
// Third pass: directional sequence edges (predecessor → this → successor).
|
|
561
|
+
// Mined from `**Predecessor:**` / `**Successor:**` lines in design.md.
|
|
562
|
+
// Direction is meaningful: `informs` from earlier-shipping feature toward later.
|
|
563
|
+
const addDirectional = (fromId, toId) => {
|
|
564
|
+
if (!fromId || !toId || fromId === toId) return;
|
|
565
|
+
const exists = Array.from(store.connections.values()).some(
|
|
566
|
+
c => c.fromId === fromId && c.toId === toId
|
|
567
|
+
);
|
|
568
|
+
if (exists) return;
|
|
569
|
+
try {
|
|
570
|
+
store.createConnection({ fromId, toId, type: 'informs' });
|
|
571
|
+
seeded.connections++;
|
|
572
|
+
} catch { /* skip */ }
|
|
573
|
+
};
|
|
574
|
+
for (const feature of features) {
|
|
575
|
+
const thisId = featureItemMap.get(feature.name);
|
|
576
|
+
if (!thisId) continue;
|
|
577
|
+
for (const predCode of feature.predecessors) {
|
|
578
|
+
addDirectional(featureItemMap.get(predCode), thisId);
|
|
579
|
+
}
|
|
580
|
+
for (const succCode of feature.successors) {
|
|
581
|
+
addDirectional(thisId, featureItemMap.get(succCode));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
531
585
|
if (seeded.features || seeded.updated || seeded.connections) {
|
|
532
586
|
console.log(`[vision] Feature scan: ${seeded.features} new, ${seeded.updated} updated, ${seeded.connections} connections`);
|
|
533
587
|
}
|