@smartmemory/compose 0.1.34-beta → 0.1.36-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.
Files changed (71) hide show
  1. package/README.md +42 -0
  2. package/bin/compose.js +16 -0
  3. package/dist/assets/{App-DMCO9aNs.js → App-DO9nGI18.js} +8 -8
  4. package/dist/assets/{arc-DsXb95RZ.js → arc-DrI_lR89.js} +1 -1
  5. package/dist/assets/{architectureDiagram-3BPJPVTR-BaBYippI.js → architectureDiagram-3BPJPVTR-DyS14rP-.js} +1 -1
  6. package/dist/assets/{blockDiagram-GPEHLZMM-HwB_eL3_.js → blockDiagram-GPEHLZMM-elTUtlnM.js} +1 -1
  7. package/dist/assets/{c4Diagram-AAUBKEIU-CPSXghc8.js → c4Diagram-AAUBKEIU-CJ_8nsiM.js} +1 -1
  8. package/dist/assets/channel-Cda02Ntm.js +1 -0
  9. package/dist/assets/{chunk-2J33WTMH-CCN3bc9J.js → chunk-2J33WTMH-BllJC9rQ.js} +1 -1
  10. package/dist/assets/{chunk-4BX2VUAB-DuqFxpoV.js → chunk-4BX2VUAB-f3EEUpDr.js} +1 -1
  11. package/dist/assets/{chunk-55IACEB6-DT20mkDV.js → chunk-55IACEB6-CgbUlP7I.js} +1 -1
  12. package/dist/assets/{chunk-727SXJPM-ByMH6Qvp.js → chunk-727SXJPM-D9zRfRSY.js} +1 -1
  13. package/dist/assets/{chunk-AQP2D5EJ-CLgYtOHw.js → chunk-AQP2D5EJ-Bjp1xaJ8.js} +1 -1
  14. package/dist/assets/{chunk-FMBD7UC4-BXWmTsAA.js → chunk-FMBD7UC4-Dyvhvbkr.js} +1 -1
  15. package/dist/assets/{chunk-ND2GUHAM-C2WgVbpE.js → chunk-ND2GUHAM-D4dMBzz5.js} +1 -1
  16. package/dist/assets/{chunk-QZHKN3VN-DFuQRJeh.js → chunk-QZHKN3VN-CVhvkQt1.js} +1 -1
  17. package/dist/assets/classDiagram-4FO5ZUOK-BykFns4A.js +1 -0
  18. package/dist/assets/classDiagram-v2-Q7XG4LA2-BykFns4A.js +1 -0
  19. package/dist/assets/{cose-bilkent-S5V4N54A-CdyvK5N2.js → cose-bilkent-S5V4N54A-CylcCFl3.js} +1 -1
  20. package/dist/assets/{dagre-BM42HDAG-Drkta_n5.js → dagre-BM42HDAG-Dd1Ny0g8.js} +1 -1
  21. package/dist/assets/{diagram-2AECGRRQ-BRiBkuu5.js → diagram-2AECGRRQ-tVs4SxwR.js} +1 -1
  22. package/dist/assets/{diagram-5GNKFQAL-IrSBDK26.js → diagram-5GNKFQAL-UC1fUNjx.js} +1 -1
  23. package/dist/assets/{diagram-KO2AKTUF-BUktYepH.js → diagram-KO2AKTUF-Cxhb85Xt.js} +1 -1
  24. package/dist/assets/{diagram-LMA3HP47-B5erGOiF.js → diagram-LMA3HP47-DJMx1YAp.js} +1 -1
  25. package/dist/assets/{diagram-OG6HWLK6-5KoSfwod.js → diagram-OG6HWLK6-k_cRur_6.js} +1 -1
  26. package/dist/assets/{erDiagram-TEJ5UH35-CXSf-i6t.js → erDiagram-TEJ5UH35-ClfbMYdQ.js} +1 -1
  27. package/dist/assets/{flowDiagram-I6XJVG4X-DiwEgd9q.js → flowDiagram-I6XJVG4X-B9KvjWpm.js} +1 -1
  28. package/dist/assets/{ganttDiagram-6RSMTGT7-zQ94YEl2.js → ganttDiagram-6RSMTGT7-CCqvcQcy.js} +1 -1
  29. package/dist/assets/{gitGraphDiagram-PVQCEYII-CWNWantF.js → gitGraphDiagram-PVQCEYII-DN9gJgDY.js} +1 -1
  30. package/dist/assets/{graph-DPbJeZyN.js → graph-uO5hwVZK.js} +1 -1
  31. package/dist/assets/{index-CyFM4bTc.js → index-CtjbBZuo.js} +3 -3
  32. package/dist/assets/index-Dh2rRpBR.css +1 -0
  33. package/dist/assets/{infoDiagram-5YYISTIA-BcnrgEm6.js → infoDiagram-5YYISTIA-BZjN-ULc.js} +1 -1
  34. package/dist/assets/{ishikawaDiagram-YF4QCWOH-BRzURsJQ.js → ishikawaDiagram-YF4QCWOH-Ck1kwRqz.js} +1 -1
  35. package/dist/assets/{journeyDiagram-JHISSGLW-CdwMwMPo.js → journeyDiagram-JHISSGLW-CHKyH4p5.js} +1 -1
  36. package/dist/assets/{kanban-definition-UN3LZRKU-Difj4Zd-.js → kanban-definition-UN3LZRKU-Bq4CuVJj.js} +1 -1
  37. package/dist/assets/{linear-CKwgBFBW.js → linear-h6UanLnU.js} +1 -1
  38. package/dist/assets/{mindmap-definition-RKZ34NQL-C2aCD1L4.js → mindmap-definition-RKZ34NQL-IQcATrSw.js} +1 -1
  39. package/dist/assets/mobile-BwduHUEq.js +17 -0
  40. package/dist/assets/{pieDiagram-4H26LBE5-C9qGrfV0.js → pieDiagram-4H26LBE5-B7cw_oKH.js} +1 -1
  41. package/dist/assets/{quadrantDiagram-W4KKPZXB-COA0Z2JV.js → quadrantDiagram-W4KKPZXB-CqiNInwZ.js} +1 -1
  42. package/dist/assets/{requirementDiagram-4Y6WPE33-BJCU8yFE.js → requirementDiagram-4Y6WPE33-CynhQVI1.js} +1 -1
  43. package/dist/assets/{sankeyDiagram-5OEKKPKP-vYzK7FJ6.js → sankeyDiagram-5OEKKPKP-aFS3y10_.js} +1 -1
  44. package/dist/assets/{sequenceDiagram-3UESZ5HK-Bkh_RWpN.js → sequenceDiagram-3UESZ5HK-CTvZcil0.js} +1 -1
  45. package/dist/assets/{stateDiagram-AJRCARHV-BlUsbYTW.js → stateDiagram-AJRCARHV-DYXgroJe.js} +1 -1
  46. package/dist/assets/stateDiagram-v2-BHNVJYJU-BVQEXVG-.js +1 -0
  47. package/dist/assets/{timeline-definition-PNZ67QCA-DuYEZwxg.js → timeline-definition-PNZ67QCA-BtN3u7JC.js} +1 -1
  48. package/dist/assets/{vennDiagram-CIIHVFJN-D9a3Q3Ni.js → vennDiagram-CIIHVFJN-DsssmbTq.js} +1 -1
  49. package/dist/assets/{wardley-L42UT6IY-h2fQnc_J.js → wardley-L42UT6IY-BXD_QFvV.js} +1 -1
  50. package/dist/assets/{wardleyDiagram-YWT4CUSO-C-_dzSY5.js → wardleyDiagram-YWT4CUSO-CgZmwLSB.js} +1 -1
  51. package/dist/assets/{xychartDiagram-2RQKCTM6-CKxMIB7j.js → xychartDiagram-2RQKCTM6-BmiRpk6f.js} +1 -1
  52. package/dist/index.html +3 -3
  53. package/lib/build.js +60 -13
  54. package/lib/changelog-writer.js +111 -83
  55. package/lib/completion-writer.js +26 -9
  56. package/lib/feature-writer.js +62 -38
  57. package/lib/roadmap-gen.js +41 -14
  58. package/lib/tracker/cli.js +31 -0
  59. package/lib/tracker/factory.js +93 -0
  60. package/lib/tracker/github-api.js +115 -0
  61. package/lib/tracker/github-provider.js +641 -0
  62. package/lib/tracker/local-provider.js +202 -0
  63. package/lib/tracker/provider.js +40 -0
  64. package/lib/tracker/sync-engine.js +131 -0
  65. package/package.json +3 -2
  66. package/dist/assets/channel-TOlxWxU-.js +0 -1
  67. package/dist/assets/classDiagram-4FO5ZUOK-DZsvwI1V.js +0 -1
  68. package/dist/assets/classDiagram-v2-Q7XG4LA2-DZsvwI1V.js +0 -1
  69. package/dist/assets/index-CHkeTiSt.css +0 -1
  70. package/dist/assets/mobile-CsuriFuT.js +0 -17
  71. package/dist/assets/stateDiagram-v2-BHNVJYJU-_AUWPuja.js +0 -1
@@ -0,0 +1,31 @@
1
+ import { providerFor } from './factory.js';
2
+
3
+ export async function runTrackerCli(cwd, argv) {
4
+ const sub = argv[0];
5
+
6
+ // Guard unknown/missing subcommand BEFORE touching providerFor (no I/O on bad input).
7
+ if (sub !== 'status' && sub !== 'sync') {
8
+ return { output: 'usage: compose tracker <status|sync>', exitCode: 1 };
9
+ }
10
+
11
+ const provider = await providerFor(cwd);
12
+
13
+ if (sub === 'status') {
14
+ const h = await provider.health();
15
+ const output = [
16
+ `tracker provider: ${h.provider}`,
17
+ `canonical: ${h.canonical}`,
18
+ `pendingOps: ${h.pendingOps}`,
19
+ `conflicts: ${h.conflicts}`,
20
+ `mixedSources: ${(h.mixedSources || []).join(', ') || '(none)'}`,
21
+ ].join('\n');
22
+ return { output, exitCode: 0 };
23
+ }
24
+
25
+ // sub === 'sync'
26
+ const r = await provider.sync();
27
+ return {
28
+ output: `sync: drained ${r.drained}, quarantined ${r.quarantined ?? 0}, pending ${r.pending ?? 0}`,
29
+ exitCode: 0,
30
+ };
31
+ }
@@ -0,0 +1,93 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { LocalFileProvider } from './local-provider.js';
4
+ import { TrackerConfigError } from './provider.js';
5
+
6
+ function loadTrackerConfig(cwd) {
7
+ const p = join(cwd, '.compose/compose.json');
8
+ // Absent file → local default (valid, not misconfig).
9
+ if (!existsSync(p)) return { provider: 'local' };
10
+ let parsed;
11
+ try {
12
+ parsed = JSON.parse(readFileSync(p, 'utf8'));
13
+ } catch (e) {
14
+ // File EXISTS but JSON is malformed → fail fast (never silently fall back;
15
+ // silent fallback would mask misconfig — design.md Error Handling).
16
+ throw new TrackerConfigError(
17
+ `compose: tracker config at ${p} contains invalid JSON — ${e.message}`
18
+ );
19
+ }
20
+ // Absent tracker key → local default (valid).
21
+ const tracker = parsed.tracker;
22
+ if (tracker === undefined || tracker === null) return { provider: 'local' };
23
+ // tracker key present but structurally invalid → fail fast.
24
+ if (typeof tracker !== 'object' || Array.isArray(tracker)) {
25
+ throw new TrackerConfigError(
26
+ `compose: tracker config at ${p} has a "tracker" key but it is not an object (got ${Array.isArray(tracker) ? 'array' : typeof tracker})`
27
+ );
28
+ }
29
+ return tracker;
30
+ }
31
+
32
+ const ENTITY_METHODS = {
33
+ JOURNAL: ['readJournal', 'writeJournalEntry'],
34
+ VISION: ['getVisionState', 'putVisionState'],
35
+ };
36
+
37
+ function withFallback(active, local) {
38
+ const caps = active.capabilities();
39
+ return new Proxy(active, {
40
+ get(target, prop, receiver) {
41
+ if (typeof prop !== 'string') return Reflect.get(target, prop, receiver);
42
+ for (const [cap, methods] of Object.entries(ENTITY_METHODS)) {
43
+ if (methods.includes(prop) && !caps.has(cap)) {
44
+ const fn = local[prop];
45
+ return typeof fn === 'function' ? fn.bind(local) : fn;
46
+ }
47
+ }
48
+ const v = target[prop];
49
+ return typeof v === 'function' ? v.bind(target) : v;
50
+ },
51
+ });
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Test transport injection (TEST-ONLY — no production effect)
56
+ // ---------------------------------------------------------------------------
57
+ //
58
+ // Tests that drive PRODUCTION entry points (writers) through a GitHub-configured
59
+ // project need to inject a fixture transport WITHOUT modifying the on-disk config
60
+ // (which can't hold a function). Call `setTestTransport(transport)` before
61
+ // constructing the provider; call `clearTestTransport()` in afterEach.
62
+ //
63
+ // Production behavior: `_testTransport` is undefined by default → no effect.
64
+ //
65
+ let _testTransport = undefined;
66
+
67
+ /**
68
+ * @param {object|null} transport - fixture transport for tests; null to clear.
69
+ */
70
+ export function setTestTransport(transport) {
71
+ _testTransport = transport ?? undefined;
72
+ }
73
+
74
+ export function clearTestTransport() {
75
+ _testTransport = undefined;
76
+ }
77
+
78
+ export async function providerFor(cwd) {
79
+ const cfg = loadTrackerConfig(cwd);
80
+ const local = await new LocalFileProvider().init(cwd, {});
81
+ if (!cfg.provider || cfg.provider === 'local') return local;
82
+ if (cfg.provider === 'github') {
83
+ const { GitHubProvider } = await import('./github-provider.js');
84
+ // Merge in the test-only transport if one has been set via setTestTransport().
85
+ // In production _testTransport is undefined and cfg.github is passed as-is.
86
+ const ghCfg = _testTransport !== undefined
87
+ ? { ...(cfg.github ?? {}), _transport: _testTransport }
88
+ : (cfg.github ?? {});
89
+ const gh = await new GitHubProvider().init(cwd, ghCfg);
90
+ return withFallback(gh, local);
91
+ }
92
+ throw new TrackerConfigError(`unknown tracker provider "${cfg.provider}"`);
93
+ }
@@ -0,0 +1,115 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { TrackerConfigError } from './provider.js';
3
+
4
+ function resolveToken(auth = {}, noGhFallback = false) {
5
+ if (auth.token) return auth.token;
6
+ if (auth.tokenEnv && process.env[auth.tokenEnv]) return process.env[auth.tokenEnv];
7
+ if (noGhFallback) return null;
8
+ try { return execFileSync('gh', ['auth', 'token'], { encoding: 'utf8' }).trim() || null; }
9
+ catch { return null; }
10
+ }
11
+
12
+ export class GitHubApi {
13
+ constructor(cfg, transport = null) {
14
+ this.repo = cfg.repo;
15
+ if (!this.repo || !/^[^/]+\/[^/]+$/.test(this.repo)) {
16
+ throw new TrackerConfigError(`tracker.github.repo must be "owner/name" (got "${this.repo}")`);
17
+ }
18
+ this.token = resolveToken(cfg.auth, cfg.auth?._noGhFallback || cfg._noGhFallback);
19
+ if (!this.token) {
20
+ throw new TrackerConfigError('no GitHub token: set tracker.github.auth.tokenEnv or run `gh auth login`',
21
+ { missing: 'token' });
22
+ }
23
+ this.transport = transport;
24
+ }
25
+ async _req(method, path, body) {
26
+ if (this.transport) return this.transport.request(method, path, body);
27
+ const res = await fetch(`https://api.github.com${path}`, {
28
+ method, headers: { Authorization: `Bearer ${this.token}`, Accept: 'application/vnd.github+json' },
29
+ body: body ? JSON.stringify(body) : undefined,
30
+ });
31
+ const remainingHdr = res.headers.get('x-ratelimit-remaining');
32
+ const resetHdr = res.headers.get('x-ratelimit-reset');
33
+ if (res.status === 403 && remainingHdr !== null && Number(remainingHdr) === 0) {
34
+ const e = new Error('rate limited');
35
+ e.rateLimit = { resetMs: Number(resetHdr) * 1000 - Date.now() };
36
+ throw e;
37
+ }
38
+ return { status: res.status, body: await res.json().catch(() => ({})), headers: res.headers };
39
+ }
40
+ async createIssue({ title, body, labels }) {
41
+ const r = await this._req('POST', `/repos/${this.repo}/issues`, { title, body, labels });
42
+ return r.body;
43
+ }
44
+ async getIssue(number) { return (await this._req('GET', `/repos/${this.repo}/issues/${number}`)).body; }
45
+ async updateIssue(number, patch) { return (await this._req('PATCH', `/repos/${this.repo}/issues/${number}`, patch)).body; }
46
+ async searchFeatureIssues() {
47
+ return (await this._req('GET', `/search/issues?q=repo:${this.repo}+label:compose-feature`)).body.items ?? [];
48
+ }
49
+ async addIssueComment(number, body) {
50
+ return (await this._req('POST', `/repos/${this.repo}/issues/${number}/comments`, { body })).body;
51
+ }
52
+ async listIssueComments(number) {
53
+ return (await this._req('GET', `/repos/${this.repo}/issues/${number}/comments`)).body ?? [];
54
+ }
55
+ async graphql(query, variables) {
56
+ const r = await this._req('POST', '/graphql', { query, variables });
57
+ return { data: r.body?.data, errors: r.body?.errors };
58
+ }
59
+
60
+ /**
61
+ * GET /repos/:repo — lightweight probe to verify token has repo access.
62
+ * Returns { status, body }; does NOT throw on 4xx.
63
+ */
64
+ async getRepo() {
65
+ return this._req('GET', `/repos/${this.repo}`);
66
+ }
67
+
68
+ /**
69
+ * GET /repos/:repo/contents/:path?ref=:ref
70
+ * Returns { text, sha } where text is the decoded file content.
71
+ * If the file does not exist (404), returns { text: '', sha: null }.
72
+ */
73
+ async getContents(path, ref) {
74
+ const query = ref ? `?ref=${encodeURIComponent(ref)}` : '';
75
+ const r = await this._req('GET', `/repos/${this.repo}/contents/${path}${query}`);
76
+ if (r.status === 404) return { text: '', sha: null };
77
+ if (r.status !== 200) {
78
+ throw new Error(
79
+ `getContents ${path}@${ref}: HTTP ${r.status} ${JSON.stringify(r.body)?.slice(0, 200)}`
80
+ );
81
+ }
82
+ const content = r.body?.content ?? '';
83
+ // GitHub returns base64 with embedded newlines — strip them before decoding.
84
+ const text = Buffer.from(content.replace(/\n/g, ''), 'base64').toString('utf-8');
85
+ const sha = r.body?.sha ?? null;
86
+ return { text, sha };
87
+ }
88
+
89
+ /**
90
+ * PUT /repos/:repo/contents/:path
91
+ * Creates or updates a file.
92
+ * @param {string} path - File path in the repo
93
+ * @param {string} text - New file content (UTF-8)
94
+ * @param {{ sha: string|null, branch: string, message: string }} opts
95
+ * sha: current blob SHA (omit or pass null to create a new file)
96
+ * branch: target branch
97
+ * message: commit message
98
+ * On 409 (SHA conflict / optimistic-lock failure) throws with e.shaConflict = true.
99
+ */
100
+ async putContents(path, text, { sha, branch, message }) {
101
+ const body = {
102
+ message,
103
+ content: Buffer.from(text, 'utf-8').toString('base64'),
104
+ branch,
105
+ };
106
+ if (sha) body.sha = sha;
107
+ const r = await this._req('PUT', `/repos/${this.repo}/contents/${path}`, body);
108
+ if (r.status === 409) {
109
+ const e = new Error(`putContents: SHA conflict for ${path}`);
110
+ e.shaConflict = true;
111
+ throw e;
112
+ }
113
+ return r.body;
114
+ }
115
+ }