@smartmemory/compose 0.1.33-beta → 0.1.35-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/README.md +42 -0
- package/bin/compose.js +22 -4
- package/dist/assets/{App-1c_7rycT.js → App-DMCO9aNs.js} +5 -5
- package/dist/assets/{arc-5LGcKaXC.js → arc-DsXb95RZ.js} +1 -1
- package/dist/assets/{architectureDiagram-3BPJPVTR-BGsvZBSe.js → architectureDiagram-3BPJPVTR-BaBYippI.js} +1 -1
- package/dist/assets/{blockDiagram-GPEHLZMM-CppawQ7y.js → blockDiagram-GPEHLZMM-HwB_eL3_.js} +1 -1
- package/dist/assets/{c4Diagram-AAUBKEIU-u6006T49.js → c4Diagram-AAUBKEIU-CPSXghc8.js} +1 -1
- package/dist/assets/channel-TOlxWxU-.js +1 -0
- package/dist/assets/{chunk-2J33WTMH-VIEW3rXo.js → chunk-2J33WTMH-CCN3bc9J.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-NhdSQDlD.js → chunk-4BX2VUAB-DuqFxpoV.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-BmobEJTk.js → chunk-55IACEB6-DT20mkDV.js} +1 -1
- package/dist/assets/{chunk-727SXJPM-CkHPoJZX.js → chunk-727SXJPM-ByMH6Qvp.js} +1 -1
- package/dist/assets/{chunk-AQP2D5EJ-DhlFWYJI.js → chunk-AQP2D5EJ-CLgYtOHw.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-Dzli17sA.js → chunk-FMBD7UC4-BXWmTsAA.js} +1 -1
- package/dist/assets/{chunk-ND2GUHAM-BZM0pKix.js → chunk-ND2GUHAM-C2WgVbpE.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Bw_VU3w3.js → chunk-QZHKN3VN-DFuQRJeh.js} +1 -1
- package/dist/assets/classDiagram-4FO5ZUOK-DZsvwI1V.js +1 -0
- package/dist/assets/classDiagram-v2-Q7XG4LA2-DZsvwI1V.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-CUhMB816.js → cose-bilkent-S5V4N54A-CdyvK5N2.js} +1 -1
- package/dist/assets/{dagre-BM42HDAG-DaUl17l5.js → dagre-BM42HDAG-Drkta_n5.js} +1 -1
- package/dist/assets/{diagram-2AECGRRQ-CPZwMXfB.js → diagram-2AECGRRQ-BRiBkuu5.js} +1 -1
- package/dist/assets/{diagram-5GNKFQAL-B9-N5E5R.js → diagram-5GNKFQAL-IrSBDK26.js} +1 -1
- package/dist/assets/{diagram-KO2AKTUF-CkfEzzGz.js → diagram-KO2AKTUF-BUktYepH.js} +1 -1
- package/dist/assets/{diagram-LMA3HP47-DLAWiWIJ.js → diagram-LMA3HP47-B5erGOiF.js} +1 -1
- package/dist/assets/{diagram-OG6HWLK6-DTCE5jRG.js → diagram-OG6HWLK6-5KoSfwod.js} +1 -1
- package/dist/assets/{erDiagram-TEJ5UH35-BCIuV-ft.js → erDiagram-TEJ5UH35-CXSf-i6t.js} +1 -1
- package/dist/assets/{flowDiagram-I6XJVG4X-DmztP2OP.js → flowDiagram-I6XJVG4X-DiwEgd9q.js} +1 -1
- package/dist/assets/{ganttDiagram-6RSMTGT7-CXZwtSzf.js → ganttDiagram-6RSMTGT7-zQ94YEl2.js} +1 -1
- package/dist/assets/{gitGraphDiagram-PVQCEYII-XDMhXGQ7.js → gitGraphDiagram-PVQCEYII-CWNWantF.js} +1 -1
- package/dist/assets/{index-CCu-56GD.js → index-CyFM4bTc.js} +2 -2
- package/dist/assets/{infoDiagram-5YYISTIA-Da1XvUtC.js → infoDiagram-5YYISTIA-BcnrgEm6.js} +1 -1
- package/dist/assets/{ishikawaDiagram-YF4QCWOH-DTw3_1vI.js → ishikawaDiagram-YF4QCWOH-BRzURsJQ.js} +1 -1
- package/dist/assets/{journeyDiagram-JHISSGLW-1q57kJBE.js → journeyDiagram-JHISSGLW-CdwMwMPo.js} +1 -1
- package/dist/assets/{kanban-definition-UN3LZRKU-CHEt8drj.js → kanban-definition-UN3LZRKU-Difj4Zd-.js} +1 -1
- package/dist/assets/{katex-CQk2-UhE.js → katex-C5jXJg4s.js} +3 -3
- package/dist/assets/{linear-Dm9uGrY9.js → linear-CKwgBFBW.js} +1 -1
- package/dist/assets/{mindmap-definition-RKZ34NQL-AfCfq9He.js → mindmap-definition-RKZ34NQL-C2aCD1L4.js} +1 -1
- package/dist/assets/{pieDiagram-4H26LBE5-BwZQV6vN.js → pieDiagram-4H26LBE5-C9qGrfV0.js} +1 -1
- package/dist/assets/{quadrantDiagram-W4KKPZXB-BSH6R7JT.js → quadrantDiagram-W4KKPZXB-COA0Z2JV.js} +1 -1
- package/dist/assets/{requirementDiagram-4Y6WPE33-DgXRRUBB.js → requirementDiagram-4Y6WPE33-BJCU8yFE.js} +1 -1
- package/dist/assets/{sankeyDiagram-5OEKKPKP-zwgrKh_8.js → sankeyDiagram-5OEKKPKP-vYzK7FJ6.js} +1 -1
- package/dist/assets/{sequenceDiagram-3UESZ5HK-CCbQVXMX.js → sequenceDiagram-3UESZ5HK-Bkh_RWpN.js} +1 -1
- package/dist/assets/{stateDiagram-AJRCARHV-BIBvILDx.js → stateDiagram-AJRCARHV-BlUsbYTW.js} +1 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-_AUWPuja.js +1 -0
- package/dist/assets/{timeline-definition-PNZ67QCA-tTCXjToz.js → timeline-definition-PNZ67QCA-DuYEZwxg.js} +1 -1
- package/dist/assets/{vennDiagram-CIIHVFJN-Bxb5N93O.js → vennDiagram-CIIHVFJN-D9a3Q3Ni.js} +1 -1
- package/dist/assets/{wardley-L42UT6IY-Dw-_rF2D.js → wardley-L42UT6IY-h2fQnc_J.js} +1 -1
- package/dist/assets/{wardleyDiagram-YWT4CUSO-rMkJu1Qs.js → wardleyDiagram-YWT4CUSO-C-_dzSY5.js} +1 -1
- package/dist/assets/{xychartDiagram-2RQKCTM6-B_CW8fFS.js → xychartDiagram-2RQKCTM6-CKxMIB7j.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/build.js +60 -13
- package/lib/changelog-writer.js +111 -83
- package/lib/completion-writer.js +26 -9
- package/lib/feature-writer.js +62 -38
- package/lib/roadmap-gen.js +41 -14
- package/lib/tracker/cli.js +31 -0
- package/lib/tracker/factory.js +93 -0
- package/lib/tracker/github-api.js +115 -0
- package/lib/tracker/github-provider.js +641 -0
- package/lib/tracker/local-provider.js +202 -0
- package/lib/tracker/provider.js +40 -0
- package/lib/tracker/sync-engine.js +131 -0
- package/package.json +3 -2
- package/dist/assets/channel-9SpKIWte.js +0 -1
- package/dist/assets/classDiagram-4FO5ZUOK-1x8GC0fS.js +0 -1
- package/dist/assets/classDiagram-v2-Q7XG4LA2-1x8GC0fS.js +0 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-CpvhwvDI.js +0 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, renameSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
|
|
4
|
+
import { readFeature, listFeatures as listFeaturesRaw, writeFeature } from '../feature-json.js';
|
|
5
|
+
import { loadFeaturesDir } from '../project-paths.js';
|
|
6
|
+
import { TrackerProvider, CAP } from './provider.js';
|
|
7
|
+
|
|
8
|
+
import { setFeatureStatus, addRoadmapEntry as addRoadmapEntryRaw } from '../feature-writer.js';
|
|
9
|
+
import { recordCompletion as recordCompletionRaw } from '../completion-writer.js';
|
|
10
|
+
import { addChangelogEntry } from '../changelog-writer.js';
|
|
11
|
+
import { appendEvent as appendEventRaw, readEvents as readEventsRaw } from '../feature-events.js';
|
|
12
|
+
import { writeRoadmap } from '../roadmap-gen.js';
|
|
13
|
+
import { writeJournalEntry as writeJournalEntryRaw, getJournalEntries } from '../journal-writer.js';
|
|
14
|
+
|
|
15
|
+
// Normalized event type map: maps raw writer tool strings → cross-provider type tokens.
|
|
16
|
+
// Readers downstream use `type`, never `tool`, for portability across providers.
|
|
17
|
+
const TOOL_TYPE = {
|
|
18
|
+
set_feature_status: 'status',
|
|
19
|
+
record_completion: 'completion',
|
|
20
|
+
add_roadmap_entry: 'roadmap',
|
|
21
|
+
add_changelog_entry: 'changelog',
|
|
22
|
+
write_journal_entry: 'journal',
|
|
23
|
+
link_artifact: 'artifact',
|
|
24
|
+
link_features: 'link',
|
|
25
|
+
propose_followup: 'followup',
|
|
26
|
+
roadmap_drift: 'drift',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Inverse map: normalized type token → raw writer tool string (for write-side symmetry).
|
|
30
|
+
const TYPE_TOOL = Object.fromEntries(Object.entries(TOOL_TYPE).map(([tool, type]) => [type, tool]));
|
|
31
|
+
|
|
32
|
+
export class LocalFileProvider extends TrackerProvider {
|
|
33
|
+
name() { return 'local'; }
|
|
34
|
+
|
|
35
|
+
capabilities() {
|
|
36
|
+
return new Set([CAP.FEATURES, CAP.EVENTS, CAP.ROADMAP, CAP.CHANGELOG, CAP.JOURNAL, CAP.VISION]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async init(cwd) {
|
|
40
|
+
this.cwd = cwd;
|
|
41
|
+
this.featuresDir = loadFeaturesDir(cwd);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Feature CRUD
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
async getFeature(code) {
|
|
50
|
+
return readFeature(this.cwd, code, this.featuresDir);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async listFeatures() {
|
|
54
|
+
return listFeaturesRaw(this.cwd, this.featuresDir);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async createFeature(code, obj) {
|
|
58
|
+
const existing = readFeature(this.cwd, code, this.featuresDir);
|
|
59
|
+
if (existing) return existing;
|
|
60
|
+
// writeFeature takes (cwd, featureObj, featuresDir) — featureObj.code drives the path
|
|
61
|
+
writeFeature(this.cwd, { ...obj, code }, this.featuresDir);
|
|
62
|
+
return readFeature(this.cwd, code, this.featuresDir);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async putFeature(code, obj) {
|
|
66
|
+
const cur = readFeature(this.cwd, code, this.featuresDir);
|
|
67
|
+
if (cur && Object.prototype.hasOwnProperty.call(obj, 'status') && obj.status !== cur.status) {
|
|
68
|
+
throw new Error(`putFeature: status delta (${cur.status}->${obj.status}) not allowed; use setStatus`);
|
|
69
|
+
}
|
|
70
|
+
writeFeature(this.cwd, { ...obj, code }, this.featuresDir);
|
|
71
|
+
return readFeature(this.cwd, code, this.featuresDir);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Write full feature object including status with NO transition policy or events.
|
|
75
|
+
// Used by feature-writer.setFeatureStatus after it has validated the transition itself.
|
|
76
|
+
async persistFeatureRaw(code, obj) {
|
|
77
|
+
writeFeature(this.cwd, { ...obj, code }, this.featuresDir);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Status + Completion
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
async setStatus(code, to, meta = {}) {
|
|
85
|
+
return setFeatureStatus(this.cwd, { code, status: to, ...meta });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async recordCompletion(code, rec) {
|
|
89
|
+
return recordCompletionRaw(this.cwd, { feature_code: code, ...rec });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Roadmap
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
async addRoadmapEntry(args) {
|
|
97
|
+
return addRoadmapEntryRaw(this.cwd, args);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async renderRoadmap() {
|
|
101
|
+
return writeRoadmap(this.cwd);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Events — normalize raw writer `tool` field to cross-provider `type` token
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
async appendEvent(code, event) {
|
|
109
|
+
// Validate that we can resolve a tool name (writers always include `tool`;
|
|
110
|
+
// this guard exists for callers that supply a normalized `type` alias instead).
|
|
111
|
+
const tool = event.tool ?? TYPE_TOOL[event.type];
|
|
112
|
+
if (!tool) throw new Error(`appendEvent: cannot resolve writer tool from event (need .tool or a known .type; got type=${event.type})`);
|
|
113
|
+
|
|
114
|
+
// Strip only the normalized `type` alias (never stored in the raw JSONL).
|
|
115
|
+
const { type, ...rest } = event;
|
|
116
|
+
|
|
117
|
+
// KEY-ORDER GUARANTEE for the WRITER PATH:
|
|
118
|
+
// Writers always include `tool` AND `code` in their event objects in the correct
|
|
119
|
+
// key order, so `rest` already contains them — forwarding `rest` directly
|
|
120
|
+
// preserves the writer's original key order and produces byte-identical JSONL
|
|
121
|
+
// output to the pre-fix direct call path.
|
|
122
|
+
//
|
|
123
|
+
// For DIRECT PROVIDER CALLERS (e.g. conformance tests) that pass only `type`
|
|
124
|
+
// (no `tool`) and/or omit `code`, inject the resolved values now. These callers
|
|
125
|
+
// have no pre-existing key order to preserve, so insertion order doesn't matter
|
|
126
|
+
// for byte-identicality (that invariant only applies to the writer path above).
|
|
127
|
+
if (rest.tool && rest.code !== undefined) {
|
|
128
|
+
// Writer path: all fields already present in correct order — forward as-is.
|
|
129
|
+
return appendEventRaw(this.cwd, rest);
|
|
130
|
+
}
|
|
131
|
+
// Direct-caller path: inject any missing routing fields.
|
|
132
|
+
const payload = { ...rest };
|
|
133
|
+
if (!payload.tool) payload.tool = tool;
|
|
134
|
+
if (payload.code === undefined) payload.code = code;
|
|
135
|
+
return appendEventRaw(this.cwd, payload);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async readEvents(code) {
|
|
139
|
+
const raw = readEventsRaw(this.cwd, { code });
|
|
140
|
+
return raw.map(e => ({ ...e, type: e.type ?? TOOL_TYPE[e.tool] ?? e.tool }));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Changelog
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
async getChangelog() {
|
|
148
|
+
const p = join(this.cwd, 'CHANGELOG.md');
|
|
149
|
+
if (!existsSync(p)) return '';
|
|
150
|
+
return readFileSync(p, 'utf-8');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async putChangelog(text) {
|
|
154
|
+
const p = join(this.cwd, 'CHANGELOG.md');
|
|
155
|
+
const tmp = p + '.tmp';
|
|
156
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
157
|
+
try {
|
|
158
|
+
writeFileSync(tmp, text);
|
|
159
|
+
renameSync(tmp, p);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* ignore */ }
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async appendChangelog(entry) {
|
|
167
|
+
return addChangelogEntry(this.cwd, entry);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Journal
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
async readJournal() {
|
|
175
|
+
return getJournalEntries(this.cwd);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async writeJournalEntry(e) {
|
|
179
|
+
return writeJournalEntryRaw(this.cwd, e);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Vision state — instantiate VisionStore per call (cwd-scoped, no singleton)
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
async getVisionState() {
|
|
187
|
+
const { VisionStore } = await import('../../server/vision-store.js');
|
|
188
|
+
const store = new VisionStore(join(this.cwd, '.compose', 'data'));
|
|
189
|
+
return store.getState();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async putVisionState(s) {
|
|
193
|
+
const { VisionStore } = await import('../../server/vision-store.js');
|
|
194
|
+
const store = new VisionStore(join(this.cwd, '.compose', 'data'));
|
|
195
|
+
// Restore items, connections, gates from the supplied state snapshot
|
|
196
|
+
store.items = new Map((s.items ?? []).map(i => [i.id, i]));
|
|
197
|
+
store.connections = new Map((s.connections ?? []).map(c => [c.id, c]));
|
|
198
|
+
store.gates = new Map((s.gates ?? []).map(g => [g.id, g]));
|
|
199
|
+
store._save();
|
|
200
|
+
return store.getState();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const CAP = Object.freeze({
|
|
2
|
+
FEATURES: 'FEATURES', EVENTS: 'EVENTS', ROADMAP: 'ROADMAP',
|
|
3
|
+
CHANGELOG: 'CHANGELOG', JOURNAL: 'JOURNAL', VISION: 'VISION',
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
export class TrackerConfigError extends Error {
|
|
7
|
+
constructor(message, detail = {}) { super(message); this.name = 'TrackerConfigError'; this.detail = detail; }
|
|
8
|
+
}
|
|
9
|
+
export class TrackerConflictError extends Error {
|
|
10
|
+
constructor(message, detail = {}) { super(message); this.name = 'TrackerConflictError'; this.detail = detail; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const NI = (m) => { throw new Error(`TrackerProvider.${m}: not implemented`); };
|
|
14
|
+
|
|
15
|
+
export class TrackerProvider {
|
|
16
|
+
name() { return NI('name'); }
|
|
17
|
+
capabilities() { return new Set(); }
|
|
18
|
+
async init(_cwd, _config) { return this; }
|
|
19
|
+
async health() { return { ok: true, provider: this.name?.() ?? 'base', canonical: 'local', pendingOps: 0, conflicts: 0, mixedSources: [] }; }
|
|
20
|
+
async sync() { return { drained: 0, quarantined: 0 }; }
|
|
21
|
+
async getFeature(_code) { return NI('getFeature'); }
|
|
22
|
+
async listFeatures() { return NI('listFeatures'); }
|
|
23
|
+
async createFeature(_code, _obj) { return NI('createFeature'); }
|
|
24
|
+
async putFeature(_code, _obj) { return NI('putFeature'); }
|
|
25
|
+
async persistFeatureRaw(_code, _obj) { return NI('persistFeatureRaw'); }
|
|
26
|
+
async deleteFeature(_code) { return NI('deleteFeature'); }
|
|
27
|
+
async setStatus(_code, _to, _meta) { return NI('setStatus'); }
|
|
28
|
+
async recordCompletion(_code, _rec) { return NI('recordCompletion'); }
|
|
29
|
+
async addRoadmapEntry(_args) { return NI('addRoadmapEntry'); }
|
|
30
|
+
async appendEvent(_code, _event) { return NI('appendEvent'); }
|
|
31
|
+
async readEvents(_code) { return NI('readEvents'); }
|
|
32
|
+
async renderRoadmap() { return NI('renderRoadmap'); }
|
|
33
|
+
async getChangelog() { return NI('getChangelog'); }
|
|
34
|
+
async putChangelog(_text) { return NI('putChangelog'); }
|
|
35
|
+
async appendChangelog(_entry) { return NI('appendChangelog'); }
|
|
36
|
+
async readJournal() { return NI('readJournal'); }
|
|
37
|
+
async writeJournalEntry(_e) { return NI('writeJournalEntry'); }
|
|
38
|
+
async getVisionState() { return NI('getVisionState'); }
|
|
39
|
+
async putVisionState(_s) { return NI('putVisionState'); }
|
|
40
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { openSync, writeSync, fsyncSync, closeSync, readFileSync, existsSync, writeFileSync, renameSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { randomUUID } from 'crypto';
|
|
4
|
+
|
|
5
|
+
const OPLOG = 'tracker-oplog.jsonl';
|
|
6
|
+
const QUAR = 'tracker-quarantine.jsonl';
|
|
7
|
+
|
|
8
|
+
export class OpLog {
|
|
9
|
+
constructor(dataDir) {
|
|
10
|
+
this.dir = dataDir;
|
|
11
|
+
this.path = join(dataDir, OPLOG);
|
|
12
|
+
this.quarPath = join(dataDir, QUAR);
|
|
13
|
+
}
|
|
14
|
+
_all() {
|
|
15
|
+
if (!existsSync(this.path)) return [];
|
|
16
|
+
return readFileSync(this.path, 'utf8').split('\n').filter(Boolean).map(JSON.parse);
|
|
17
|
+
}
|
|
18
|
+
async pending() { return this._all().filter(o => o.state === 'pending'); }
|
|
19
|
+
async quarantined() {
|
|
20
|
+
if (!existsSync(this.quarPath)) return [];
|
|
21
|
+
return readFileSync(this.quarPath, 'utf8').split('\n').filter(Boolean).map(JSON.parse);
|
|
22
|
+
}
|
|
23
|
+
async append(op) {
|
|
24
|
+
if (op.idempotencyKey) {
|
|
25
|
+
const dup = this._all().find(o => o.idempotencyKey === op.idempotencyKey);
|
|
26
|
+
if (dup) return dup;
|
|
27
|
+
}
|
|
28
|
+
const { id: _i, ts: _t, state: _s, attempts: _a, ...safe } = op;
|
|
29
|
+
const rec = { ...safe, id: randomUUID(), ts: Date.now(), state: 'pending', attempts: 0 };
|
|
30
|
+
const fd = openSync(this.path, 'a');
|
|
31
|
+
try { writeSync(fd, JSON.stringify(rec) + '\n'); fsyncSync(fd); } finally { closeSync(fd); }
|
|
32
|
+
return rec;
|
|
33
|
+
}
|
|
34
|
+
_rewrite(records) {
|
|
35
|
+
const tmp = this.path + '.tmp';
|
|
36
|
+
writeFileSync(tmp, records.map(r => JSON.stringify(r)).join('\n') + (records.length ? '\n' : ''));
|
|
37
|
+
renameSync(tmp, this.path);
|
|
38
|
+
}
|
|
39
|
+
async resolve(id) { this._rewrite(this._all().filter(o => o.id !== id)); }
|
|
40
|
+
async bumpAttempt(id) {
|
|
41
|
+
const all = this._all();
|
|
42
|
+
const o = all.find(x => x.id === id); if (o) o.attempts += 1;
|
|
43
|
+
this._rewrite(all);
|
|
44
|
+
return o;
|
|
45
|
+
}
|
|
46
|
+
async quarantine(id, reason) {
|
|
47
|
+
const all = this._all();
|
|
48
|
+
const o = all.find(x => x.id === id);
|
|
49
|
+
if (o) {
|
|
50
|
+
const fd = openSync(this.quarPath, 'a');
|
|
51
|
+
try { writeSync(fd, JSON.stringify({ ...o, state: 'quarantined', reason }) + '\n'); fsyncSync(fd); }
|
|
52
|
+
finally { closeSync(fd); }
|
|
53
|
+
this._rewrite(all.filter(x => x.id !== id));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class Cache {
|
|
59
|
+
constructor(dataDir) {
|
|
60
|
+
this.dir = join(dataDir, 'tracker-cache');
|
|
61
|
+
mkdirSync(this.dir, { recursive: true });
|
|
62
|
+
this.path = join(this.dir, 'features.json');
|
|
63
|
+
}
|
|
64
|
+
_load() { return existsSync(this.path) ? JSON.parse(readFileSync(this.path, 'utf8')) : {}; }
|
|
65
|
+
_save(s) { const t = this.path + '.tmp'; writeFileSync(t, JSON.stringify(s, null, 2)); renameSync(t, this.path); }
|
|
66
|
+
async get(code) { return this._load()[code]?.value ?? null; }
|
|
67
|
+
async version(code) { return this._load()[code]?.version ?? null; }
|
|
68
|
+
async put(code, value, { version, pending = false } = {}) {
|
|
69
|
+
const s = this._load();
|
|
70
|
+
s[code] = { value, version: version ?? s[code]?.version ?? null,
|
|
71
|
+
pending: pending || s[code]?.pending || false };
|
|
72
|
+
this._save(s);
|
|
73
|
+
}
|
|
74
|
+
async markPending(code) { const s = this._load(); if (s[code]) { s[code].pending = true; this._save(s); } }
|
|
75
|
+
async clearPending(code) { const s = this._load(); if (s[code]) { s[code].pending = false; this._save(s); } }
|
|
76
|
+
async all() { return this._load(); }
|
|
77
|
+
async applyRemote(code, value, { version }) {
|
|
78
|
+
const s = this._load();
|
|
79
|
+
if (s[code]?.pending) return; // shadow: never roll back an entry with a pending op
|
|
80
|
+
s[code] = { value, version, pending: false };
|
|
81
|
+
this._save(s);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class ConflictLedger {
|
|
86
|
+
constructor(dir) { this.path = join(dir, 'tracker-conflicts.jsonl'); }
|
|
87
|
+
async record(entry) {
|
|
88
|
+
const fd = openSync(this.path, 'a');
|
|
89
|
+
try { writeSync(fd, JSON.stringify({ ts: Date.now(), ...entry }) + '\n'); fsyncSync(fd); }
|
|
90
|
+
finally { closeSync(fd); }
|
|
91
|
+
}
|
|
92
|
+
async all() {
|
|
93
|
+
if (!existsSync(this.path)) return [];
|
|
94
|
+
return readFileSync(this.path, 'utf8').split('\n').filter(Boolean).map(JSON.parse);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class Reconciler {
|
|
99
|
+
constructor({ log, cache, dir, apply, maxAttempts = 5 }) {
|
|
100
|
+
this.log = log; this.cache = cache; this.dir = dir;
|
|
101
|
+
this.apply = apply; this.maxAttempts = maxAttempts;
|
|
102
|
+
this.ledger = new ConflictLedger(dir);
|
|
103
|
+
}
|
|
104
|
+
async flush() {
|
|
105
|
+
for (const op of await this.log.pending()) {
|
|
106
|
+
try {
|
|
107
|
+
const res = await this.apply(op);
|
|
108
|
+
await this.cache.clearPending(op.code);
|
|
109
|
+
if (res?.version) {
|
|
110
|
+
const cur = await this.cache.get(op.code);
|
|
111
|
+
if (cur) await this.cache.applyRemote(op.code, cur, { version: res.version });
|
|
112
|
+
}
|
|
113
|
+
await this.log.resolve(op.id);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
if (e.casMismatch) {
|
|
116
|
+
await this.ledger.record({ code: op.code, opId: op.id, kind: 'cas',
|
|
117
|
+
baseVersion: op.baseVersion, remoteVersion: e.casMismatch.remoteVersion });
|
|
118
|
+
await this.log.quarantine(op.id, 'cas');
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (e.rateLimit) { await new Promise(r => setTimeout(r, Math.min(e.rateLimit.resetMs ?? 1000, 60000))); }
|
|
122
|
+
const bumped = await this.log.bumpAttempt(op.id);
|
|
123
|
+
if (bumped && bumped.attempts >= this.maxAttempts) {
|
|
124
|
+
await this.ledger.record({ code: op.code, opId: op.id, kind: 'poison', error: String(e) });
|
|
125
|
+
await this.log.quarantine(op.id, 'poison');
|
|
126
|
+
}
|
|
127
|
+
break; // FIFO: stop on first unresolved op so ordering is preserved
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartmemory/compose",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35-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",
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
"dev:client": "vite",
|
|
20
20
|
"build": "vite build",
|
|
21
21
|
"preview": "vite preview",
|
|
22
|
-
"test": "node --test test/*.test.js test/comp-obs-branch/*.test.js && npm run test:ui",
|
|
22
|
+
"test": "node --test test/*.test.js test/comp-obs-branch/*.test.js && npm run test:ui && npm run test:tracker",
|
|
23
23
|
"test:ui": "vitest run",
|
|
24
|
+
"test:tracker": "vitest run --config vitest.tracker.config.js",
|
|
24
25
|
"test:integration": "node --test test/integration/*.test.js",
|
|
25
26
|
"test:wave-6": "node --test test/wave-6-integration.test.js test/wave-6-contract-compliance.test.js",
|
|
26
27
|
"prepublishOnly": "npm run build"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./App-1c_7rycT.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as e,C as t}from"./chunk-727SXJPM-CkHPoJZX.js";import{b as i}from"./App-1c_7rycT.js";import"./chunk-FMBD7UC4-Dzli17sA.js";import"./chunk-ND2GUHAM-BZM0pKix.js";import"./chunk-55IACEB6-BmobEJTk.js";import"./chunk-2J33WTMH-VIEW3rXo.js";import"./mobile-CsuriFuT.js";import"./index-CCu-56GD.js";import"./graph-DPbJeZyN.js";var f={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{f as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as e,C as t}from"./chunk-727SXJPM-CkHPoJZX.js";import{b as i}from"./App-1c_7rycT.js";import"./chunk-FMBD7UC4-Dzli17sA.js";import"./chunk-ND2GUHAM-BZM0pKix.js";import"./chunk-55IACEB6-BmobEJTk.js";import"./chunk-2J33WTMH-VIEW3rXo.js";import"./mobile-CsuriFuT.js";import"./index-CCu-56GD.js";import"./graph-DPbJeZyN.js";var f={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{f as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as r,a as e,s as a,S as s}from"./chunk-AQP2D5EJ-DhlFWYJI.js";import{b as i}from"./App-1c_7rycT.js";import"./chunk-55IACEB6-BmobEJTk.js";import"./chunk-2J33WTMH-VIEW3rXo.js";import"./mobile-CsuriFuT.js";import"./index-CCu-56GD.js";import"./graph-DPbJeZyN.js";var b={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{b as diagram};
|