@smartmemory/compose 0.2.48-beta → 0.2.50-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/.claude/skills/compose/SKILL.md +2 -0
- package/.compose-deps.json +13 -0
- package/bin/compose.js +35 -20
- package/dist/assets/{App-D7E7S49Q.js → App-Bxyif1yv.js} +160 -160
- package/dist/assets/{arc-LOW60tiI.js → arc-DeHak63Z.js} +1 -1
- package/dist/assets/{architectureDiagram-3BPJPVTR-BDY00hRy.js → architectureDiagram-3BPJPVTR-C5Nkyl9y.js} +1 -1
- package/dist/assets/{blockDiagram-GPEHLZMM-DiC_6ktq.js → blockDiagram-GPEHLZMM-BilnUBG2.js} +1 -1
- package/dist/assets/{c4Diagram-AAUBKEIU-CXZC8woU.js → c4Diagram-AAUBKEIU-BsFJmPuS.js} +1 -1
- package/dist/assets/channel-ZgbQ1k0u.js +1 -0
- package/dist/assets/{chunk-2J33WTMH-b8Rdk4S-.js → chunk-2J33WTMH-AEu6HIoY.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-BSxn03f-.js → chunk-4BX2VUAB-XO4S2_89.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-uUk1WzZ1.js → chunk-55IACEB6-CtFSLInj.js} +1 -1
- package/dist/assets/{chunk-727SXJPM-zvXhT6iZ.js → chunk-727SXJPM-CZeBRpM9.js} +1 -1
- package/dist/assets/{chunk-AQP2D5EJ-B6GCyrIp.js → chunk-AQP2D5EJ-_vmxcBc4.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DlhrqkBp.js → chunk-FMBD7UC4-BMGd_Y9a.js} +1 -1
- package/dist/assets/{chunk-ND2GUHAM-lo_8kNzw.js → chunk-ND2GUHAM-BlrOECEr.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-on-yD-C0.js → chunk-QZHKN3VN-DHhUJ8RK.js} +1 -1
- package/dist/assets/classDiagram-4FO5ZUOK-KXxOorUx.js +1 -0
- package/dist/assets/classDiagram-v2-Q7XG4LA2-KXxOorUx.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-DxV7Y2bU.js → cose-bilkent-S5V4N54A-C3M0mZqk.js} +1 -1
- package/dist/assets/{dagre-BM42HDAG-BHf9sURy.js → dagre-BM42HDAG-DJlm4fEk.js} +1 -1
- package/dist/assets/{diagram-2AECGRRQ-R9Puxzlh.js → diagram-2AECGRRQ-BmcONQG5.js} +1 -1
- package/dist/assets/{diagram-5GNKFQAL-BbjlClKx.js → diagram-5GNKFQAL-5JbWGIwd.js} +1 -1
- package/dist/assets/{diagram-KO2AKTUF-r89Mx4m1.js → diagram-KO2AKTUF-DAnIvyI0.js} +1 -1
- package/dist/assets/{diagram-LMA3HP47-CstVDJZ3.js → diagram-LMA3HP47-Bmh7tvmI.js} +1 -1
- package/dist/assets/{diagram-OG6HWLK6-xmX57FKh.js → diagram-OG6HWLK6-hnHVMNnk.js} +1 -1
- package/dist/assets/{erDiagram-TEJ5UH35-DhLPxvrl.js → erDiagram-TEJ5UH35-AMDitV0b.js} +1 -1
- package/dist/assets/{flowDiagram-I6XJVG4X-BExoUaNm.js → flowDiagram-I6XJVG4X-C0t2ZGQv.js} +1 -1
- package/dist/assets/{ganttDiagram-6RSMTGT7-BEXa5a22.js → ganttDiagram-6RSMTGT7-BPlM-BEN.js} +1 -1
- package/dist/assets/{gitGraphDiagram-PVQCEYII-Cbes9api.js → gitGraphDiagram-PVQCEYII-Lv_YTxnb.js} +1 -1
- package/dist/assets/index-COq21Zym.js +119 -0
- package/dist/assets/{infoDiagram-5YYISTIA-UBm4ssfm.js → infoDiagram-5YYISTIA-Dul1vdUm.js} +1 -1
- package/dist/assets/{ishikawaDiagram-YF4QCWOH-CksBYHCF.js → ishikawaDiagram-YF4QCWOH-CCBwgjFs.js} +1 -1
- package/dist/assets/{journeyDiagram-JHISSGLW-CKHkIHky.js → journeyDiagram-JHISSGLW-zUJN38EU.js} +1 -1
- package/dist/assets/{kanban-definition-UN3LZRKU-CEahMFzE.js → kanban-definition-UN3LZRKU-BNxWi-8J.js} +1 -1
- package/dist/assets/{linear-CqLVtYRk.js → linear-CRH9b4g7.js} +1 -1
- package/dist/assets/{mindmap-definition-RKZ34NQL-C61zWt4M.js → mindmap-definition-RKZ34NQL-DolYwq7Y.js} +1 -1
- package/dist/assets/{pieDiagram-4H26LBE5-BIpZHyJW.js → pieDiagram-4H26LBE5-oxNfr2TX.js} +1 -1
- package/dist/assets/{quadrantDiagram-W4KKPZXB-Db7nRbZm.js → quadrantDiagram-W4KKPZXB-BmZCvD-z.js} +1 -1
- package/dist/assets/{requirementDiagram-4Y6WPE33-BQqFCAr0.js → requirementDiagram-4Y6WPE33-DsXZ05jo.js} +1 -1
- package/dist/assets/{sankeyDiagram-5OEKKPKP-D-oAAMci.js → sankeyDiagram-5OEKKPKP-DsnYaawP.js} +1 -1
- package/dist/assets/{sequenceDiagram-3UESZ5HK-Dc4Cp0Om.js → sequenceDiagram-3UESZ5HK-Dt5mu3g8.js} +1 -1
- package/dist/assets/{stateDiagram-AJRCARHV-B5DkQ8Pr.js → stateDiagram-AJRCARHV-BFR5ZINQ.js} +1 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-BDrQD8fR.js +1 -0
- package/dist/assets/{timeline-definition-PNZ67QCA-DU9Fl-FW.js → timeline-definition-PNZ67QCA-Bvnhw58e.js} +1 -1
- package/dist/assets/{vennDiagram-CIIHVFJN-Bthz7siw.js → vennDiagram-CIIHVFJN-5WnyjZqf.js} +1 -1
- package/dist/assets/{wardley-L42UT6IY-nqdlFSIA.js → wardley-L42UT6IY-CTxW4xow.js} +1 -1
- package/dist/assets/{wardleyDiagram-YWT4CUSO-BJ1BC5zc.js → wardleyDiagram-YWT4CUSO-K8Y1EOvb.js} +1 -1
- package/dist/assets/{xychartDiagram-2RQKCTM6-Bib5JrbR.js → xychartDiagram-2RQKCTM6-BCShes6B.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/build-all.js +2 -1
- package/lib/build.js +96 -6
- package/lib/checkpoint/checkpoint-writer.js +3 -2
- package/lib/completion-writer.js +38 -14
- package/lib/deps.js +98 -1
- package/lib/feature-json.js +14 -3
- package/lib/feature-validator.js +22 -9
- package/lib/feature-write-guard.js +5 -7
- package/lib/feature-writer.js +3 -3
- package/lib/followup-writer.js +2 -2
- package/lib/get-roadmap.js +2 -2
- package/lib/gsd.js +2 -1
- package/lib/ideabox.js +3 -2
- package/lib/journal-writer.js +3 -3
- package/lib/migrate-roadmap.js +2 -3
- package/lib/paths-core.js +47 -0
- package/lib/project-paths.js +46 -37
- package/lib/roadmap-gen.js +3 -4
- package/lib/roadmap-graph/collect.js +5 -5
- package/lib/roadmap-graph/index.js +2 -2
- package/lib/rtk.js +66 -0
- package/lib/state-migrations.js +211 -42
- package/lib/tracker/local-provider.js +2 -2
- package/lib/triage.js +7 -5
- package/lib/vision-writer.js +6 -13
- package/lib/xref-push.js +4 -4
- package/lib/xref-sync.js +4 -4
- package/package.json +1 -1
- package/server/compose-mcp-tools.js +9 -1
- package/server/design-routes.js +9 -4
- package/server/drift-axes.js +5 -3
- package/server/feature-scan.js +8 -2
- package/server/file-watcher.js +13 -7
- package/server/ideabox-routes.js +12 -11
- package/server/project-root.js +21 -4
- package/server/session-routes.js +2 -2
- package/server/stratum-sync.js +85 -9
- package/server/vision-routes.js +17 -2
- package/server/vision-server.js +4 -3
- package/server/vision-store.js +6 -11
- package/server/vision-utils.js +23 -4
- package/dist/assets/channel-Dh96pu1k.js +0 -1
- package/dist/assets/classDiagram-4FO5ZUOK-BCqpDcaS.js +0 -1
- package/dist/assets/classDiagram-v2-Q7XG4LA2-BCqpDcaS.js +0 -1
- package/dist/assets/index-DNuHtZwR.js +0 -123
- package/dist/assets/stateDiagram-v2-BHNVJYJU-BM1Uf6a_.js +0 -1
package/lib/journal-writer.js
CHANGED
|
@@ -29,12 +29,12 @@ import { join, dirname } from 'node:path';
|
|
|
29
29
|
|
|
30
30
|
import { appendEvent, normalizeSince } from './feature-events.js';
|
|
31
31
|
import { checkOrInsert } from './idempotency.js';
|
|
32
|
+
import { resolveJournalPath } from './project-paths.js';
|
|
32
33
|
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
34
35
|
// Constants & regexes
|
|
35
36
|
// ---------------------------------------------------------------------------
|
|
36
37
|
|
|
37
|
-
const JOURNAL_DIR = 'docs/journal';
|
|
38
38
|
const INDEX_FILE = 'README.md';
|
|
39
39
|
const ENTRIES_HEADING = '## Entries';
|
|
40
40
|
|
|
@@ -649,7 +649,7 @@ export async function writeJournalEntry(cwd, args) {
|
|
|
649
649
|
validateArgs(args);
|
|
650
650
|
|
|
651
651
|
return maybeIdempotent({ ...args, cwd }, async () => {
|
|
652
|
-
const journalDir =
|
|
652
|
+
const journalDir = resolveJournalPath(cwd);
|
|
653
653
|
const indexPath = join(journalDir, INDEX_FILE);
|
|
654
654
|
|
|
655
655
|
// Pre-flight: read + parse index before any disk mutation.
|
|
@@ -857,7 +857,7 @@ export async function writeJournalEntry(cwd, args) {
|
|
|
857
857
|
* @returns {{ entries: Array, count: number }}
|
|
858
858
|
*/
|
|
859
859
|
export function getJournalEntries(cwd, opts = {}) {
|
|
860
|
-
const journalDir =
|
|
860
|
+
const journalDir = resolveJournalPath(cwd);
|
|
861
861
|
if (!existsSync(journalDir)) return { entries: [], count: 0 };
|
|
862
862
|
|
|
863
863
|
const sinceMs = opts.since !== undefined ? normalizeSince(opts.since) : null;
|
package/lib/migrate-roadmap.js
CHANGED
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { readFileSync, existsSync } from 'fs';
|
|
9
|
-
import { join } from 'path';
|
|
10
9
|
import { parseRoadmap } from './roadmap-parser.js';
|
|
11
10
|
import { readFeature, writeFeature } from './feature-json.js';
|
|
12
|
-
import { loadFeaturesDir } from './project-paths.js';
|
|
11
|
+
import { loadFeaturesDir, resolveRoadmapPath } from './project-paths.js';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Migrate ROADMAP.md entries to feature.json files.
|
|
@@ -31,7 +30,7 @@ export function migrateRoadmap(cwd, opts = {}) {
|
|
|
31
30
|
const featuresDir = opts.featuresDir ?? loadFeaturesDir(cwd);
|
|
32
31
|
const externalPrefixes = opts.externalPrefixes ?? [];
|
|
33
32
|
const isExternal = (code) => externalPrefixes.some((p) => code.startsWith(p));
|
|
34
|
-
const roadmapPath =
|
|
33
|
+
const roadmapPath = resolveRoadmapPath(cwd);
|
|
35
34
|
|
|
36
35
|
if (!existsSync(roadmapPath)) {
|
|
37
36
|
throw new Error(`No ROADMAP.md found at ${roadmapPath}`);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* paths-core.js — PURE artifact-path resolution. No fs, no config reading.
|
|
3
|
+
* Single source of truth for default artifact locations (COMP-PATHS-EXTERNAL).
|
|
4
|
+
*
|
|
5
|
+
* `resolvePathValue` uses path.resolve (NOT path.join) so an absolute or
|
|
6
|
+
* ../-escaping `paths.*` override resolves correctly instead of being
|
|
7
|
+
* silently re-rooted under cwd.
|
|
8
|
+
*/
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_PATHS = Object.freeze({
|
|
12
|
+
docs: 'docs',
|
|
13
|
+
roadmap: 'ROADMAP.md',
|
|
14
|
+
features: 'docs/features',
|
|
15
|
+
journal: 'docs/journal',
|
|
16
|
+
context: 'docs/context',
|
|
17
|
+
ideabox: 'docs/product/ideabox.md',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a configured `paths[key]` value to an absolute, normalized path.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} root Absolute workspace root.
|
|
24
|
+
* @param {*} value The configured paths[key] value (any type).
|
|
25
|
+
* @param {string} key Fallback key into DEFAULT_PATHS when value is unusable.
|
|
26
|
+
* @returns {string} Absolute, normalized path.
|
|
27
|
+
*/
|
|
28
|
+
export function resolvePathValue(root, value, key) {
|
|
29
|
+
const v = (typeof value === 'string' && value.trim().length > 0)
|
|
30
|
+
? value
|
|
31
|
+
: DEFAULT_PATHS[key];
|
|
32
|
+
return path.isAbsolute(v) ? path.normalize(v) : path.resolve(root, v);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Display-safe relativization: a clean root-relative string when `abs` is
|
|
37
|
+
* inside `root`, else the absolute path (never a `../`-prefixed string).
|
|
38
|
+
*
|
|
39
|
+
* @param {string} root Absolute workspace root.
|
|
40
|
+
* @param {string} abs Absolute path to relativize for display.
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
export function relForDisplay(root, abs) {
|
|
44
|
+
const rel = path.relative(root, abs);
|
|
45
|
+
if (rel === '') return '.';
|
|
46
|
+
return rel.startsWith('..') || path.isAbsolute(rel) ? abs : rel;
|
|
47
|
+
}
|
package/lib/project-paths.js
CHANGED
|
@@ -1,52 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* project-paths.js — read .compose/compose.json
|
|
3
|
-
* for lib-side
|
|
2
|
+
* project-paths.js — read .compose/compose.json and resolve artifact paths
|
|
3
|
+
* for lib-side code. All path math is delegated to lib/paths-core.js so the
|
|
4
|
+
* server and lib resolvers can never diverge.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* and shouldn't share that cache. This is a tiny per-call read; the file is
|
|
8
|
-
* a few hundred bytes.
|
|
9
|
-
*
|
|
10
|
-
* Introduced by COMP-MCP-MIGRATION-2.
|
|
6
|
+
* Introduced by COMP-MCP-MIGRATION-2; extended to relocatable artifact paths
|
|
7
|
+
* by COMP-PATHS-EXTERNAL (absolute readers + *FromConfig alternate-root form).
|
|
11
8
|
*/
|
|
12
9
|
import { existsSync, readFileSync } from 'fs';
|
|
13
10
|
import { join } from 'path';
|
|
11
|
+
import { DEFAULT_PATHS, resolvePathValue, relForDisplay } from './paths-core.js';
|
|
12
|
+
|
|
13
|
+
function readConfig(cwd) {
|
|
14
|
+
const cfgPath = join(cwd, '.compose', 'compose.json');
|
|
15
|
+
if (!existsSync(cfgPath)) return {};
|
|
16
|
+
try { return JSON.parse(readFileSync(cfgPath, 'utf-8')); }
|
|
17
|
+
catch { return {}; }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Resolve one artifact key against the config on disk at `cwd`. Absolute. */
|
|
21
|
+
function resolveKey(cwd, key) {
|
|
22
|
+
return resolvePathValue(cwd, readConfig(cwd)?.paths?.[key], key);
|
|
23
|
+
}
|
|
24
|
+
/** Resolve one artifact key against an already-loaded config + arbitrary root. Absolute. */
|
|
25
|
+
function resolveKeyFromConfig(root, config, key) {
|
|
26
|
+
return resolvePathValue(root, config?.paths?.[key], key);
|
|
27
|
+
}
|
|
14
28
|
|
|
15
|
-
const
|
|
29
|
+
export const resolveDocsPath = (cwd) => resolveKey(cwd, 'docs');
|
|
30
|
+
export const resolveRoadmapPath = (cwd) => resolveKey(cwd, 'roadmap');
|
|
31
|
+
export const resolveFeaturesPath = (cwd) => resolveKey(cwd, 'features');
|
|
32
|
+
export const resolveJournalPath = (cwd) => resolveKey(cwd, 'journal');
|
|
33
|
+
export const resolveContextPath = (cwd) => resolveKey(cwd, 'context');
|
|
34
|
+
export const resolveIdeaboxPath = (cwd) => resolveKey(cwd, 'ideabox');
|
|
35
|
+
|
|
36
|
+
export const resolveDocsPathFromConfig = (root, config) => resolveKeyFromConfig(root, config, 'docs');
|
|
37
|
+
export const resolveRoadmapPathFromConfig = (root, config) => resolveKeyFromConfig(root, config, 'roadmap');
|
|
38
|
+
export const resolveFeaturesPathFromConfig = (root, config) => resolveKeyFromConfig(root, config, 'features');
|
|
39
|
+
export const resolveJournalPathFromConfig = (root, config) => resolveKeyFromConfig(root, config, 'journal');
|
|
40
|
+
export const resolveContextPathFromConfig = (root, config) => resolveKeyFromConfig(root, config, 'context');
|
|
41
|
+
export const resolveIdeaboxPathFromConfig = (root, config) => resolveKeyFromConfig(root, config, 'ideabox');
|
|
42
|
+
|
|
43
|
+
export { relForDisplay };
|
|
16
44
|
|
|
17
45
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* @returns {string} Relative features dir, e.g. 'docs/features' or 'specs/features'.
|
|
46
|
+
* @deprecated relative form — prefer resolveFeaturesPath (absolute). Kept for
|
|
47
|
+
* any caller that genuinely needs the configured RELATIVE string. Callers that
|
|
48
|
+
* pass the result into feature-json.js should migrate to resolveFeaturesPath,
|
|
49
|
+
* which is absolute-safe (COMP-PATHS-EXTERNAL S2).
|
|
23
50
|
*/
|
|
24
51
|
export function loadFeaturesDir(cwd) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const cfg = JSON.parse(readFileSync(cfgPath, 'utf-8'));
|
|
29
|
-
const rel = cfg?.paths?.features;
|
|
30
|
-
return (typeof rel === 'string' && rel.length > 0) ? rel : DEFAULT_FEATURES_DIR;
|
|
31
|
-
} catch {
|
|
32
|
-
return DEFAULT_FEATURES_DIR;
|
|
33
|
-
}
|
|
52
|
+
const rel = readConfig(cwd)?.paths?.features;
|
|
53
|
+
return (typeof rel === 'string' && rel.length > 0) ? rel : DEFAULT_PATHS.features;
|
|
34
54
|
}
|
|
35
55
|
|
|
36
|
-
/**
|
|
37
|
-
* Read `externalPrefixes` from .compose/compose.json — code prefixes (e.g.
|
|
38
|
-
* ["STRAT-"]) for features owned by OTHER projects, present in this roadmap
|
|
39
|
-
* only as cross-project references. Returns [] if absent/unreadable.
|
|
40
|
-
* @param {string} cwd
|
|
41
|
-
* @returns {string[]}
|
|
42
|
-
*/
|
|
43
56
|
export function loadExternalPrefixes(cwd) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (!existsSync(cfgPath)) return [];
|
|
47
|
-
const cfg = JSON.parse(readFileSync(cfgPath, 'utf-8'));
|
|
48
|
-
return Array.isArray(cfg.externalPrefixes) ? cfg.externalPrefixes : [];
|
|
49
|
-
} catch { return []; }
|
|
57
|
+
const arr = readConfig(cwd)?.externalPrefixes;
|
|
58
|
+
return Array.isArray(arr) ? arr : [];
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
export const _internals = { DEFAULT_FEATURES_DIR };
|
|
61
|
+
export const _internals = { DEFAULT_FEATURES_DIR: DEFAULT_PATHS.features };
|
package/lib/roadmap-gen.js
CHANGED
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
8
|
-
import { join } from 'path';
|
|
9
8
|
import { listFeatures, positionSortKey } from './feature-json.js';
|
|
10
9
|
import { parseStatusToken } from './roadmap-parser.js';
|
|
11
|
-
import { loadFeaturesDir } from './project-paths.js';
|
|
10
|
+
import { loadFeaturesDir, resolveRoadmapPath } from './project-paths.js';
|
|
12
11
|
import {
|
|
13
12
|
readPhaseOverrides,
|
|
14
13
|
readAnonymousRows,
|
|
@@ -207,7 +206,7 @@ export function generateRoadmapFromBase(baseText, features, opts = {}) {
|
|
|
207
206
|
* @returns {string} - Generated ROADMAP.md content
|
|
208
207
|
*/
|
|
209
208
|
export function generateRoadmap(cwd, opts = {}) {
|
|
210
|
-
const roadmapPath =
|
|
209
|
+
const roadmapPath = resolveRoadmapPath(cwd);
|
|
211
210
|
const existingText = existsSync(roadmapPath) ? readFileSync(roadmapPath, 'utf-8') : '';
|
|
212
211
|
|
|
213
212
|
// Narrative-owned workspace: ROADMAP.md is hand-authored. Return it verbatim
|
|
@@ -513,7 +512,7 @@ function buildKeyDocs(features, featuresDir) {
|
|
|
513
512
|
* @param {object} [opts]
|
|
514
513
|
*/
|
|
515
514
|
export function writeRoadmap(cwd, opts = {}) {
|
|
516
|
-
const roadmapPath =
|
|
515
|
+
const roadmapPath = resolveRoadmapPath(cwd);
|
|
517
516
|
|
|
518
517
|
// Narrative-owned workspace: never overwrite hand-authored ROADMAP.md (#39).
|
|
519
518
|
// No-op + warn; the existing file on disk is the source of truth.
|
|
@@ -12,7 +12,7 @@ import { join, dirname, resolve } from 'node:path';
|
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import { parse as parseYaml } from 'yaml';
|
|
14
14
|
import { listFeatures } from '../feature-json.js';
|
|
15
|
-
import {
|
|
15
|
+
import { resolveFeaturesPath, resolveRoadmapPath, loadExternalPrefixes } from '../project-paths.js';
|
|
16
16
|
import { parseRoadmap } from '../roadmap-parser.js';
|
|
17
17
|
import { SchemaValidator } from '../../server/schema-validator.js';
|
|
18
18
|
import { depsToEdges } from './model.js';
|
|
@@ -28,7 +28,7 @@ const FM_KEYS = ['name', 'priority', 'track', 'desc'];
|
|
|
28
28
|
* @param {string} [featuresDir] relative features dir (default from compose.json)
|
|
29
29
|
* @returns {{ nodes: object[], rawEdges: object[], knownCodes: Set<string>, warnings: string[] }}
|
|
30
30
|
*/
|
|
31
|
-
export function collectGraphInputs(cwd, featuresDir =
|
|
31
|
+
export function collectGraphInputs(cwd, featuresDir = resolveFeaturesPath(cwd)) {
|
|
32
32
|
const warnings = [];
|
|
33
33
|
const depsValidator = new SchemaValidator(DEPS_SCHEMA);
|
|
34
34
|
const fmValidator = new SchemaValidator(FRONTMATTER_SCHEMA);
|
|
@@ -48,7 +48,7 @@ export function collectGraphInputs(cwd, featuresDir = loadFeaturesDir(cwd)) {
|
|
|
48
48
|
// An external-prefixed folder is a cross-project reference living here —
|
|
49
49
|
// known (so edges resolve) but never rendered as one of THIS project's nodes.
|
|
50
50
|
if (isExternal(f.code)) { externalCodes.add(f.code); continue; }
|
|
51
|
-
const folder = join(
|
|
51
|
+
const folder = join(featuresDir, f.code);
|
|
52
52
|
const fm = readDisplayMetadata(folder, f, fmValidator, warnings);
|
|
53
53
|
universe.set(f.code, {
|
|
54
54
|
id: f.code,
|
|
@@ -62,7 +62,7 @@ export function collectGraphInputs(cwd, featuresDir = loadFeaturesDir(cwd)) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// (b) ROADMAP.md rows — fallback for features not registered as folders.
|
|
65
|
-
const roadmapPath =
|
|
65
|
+
const roadmapPath = resolveRoadmapPath(cwd);
|
|
66
66
|
if (existsSync(roadmapPath)) {
|
|
67
67
|
let entries = [];
|
|
68
68
|
try { entries = parseRoadmap(readFileSync(roadmapPath, 'utf-8')); } catch { /* ignore */ }
|
|
@@ -88,7 +88,7 @@ export function collectGraphInputs(cwd, featuresDir = loadFeaturesDir(cwd)) {
|
|
|
88
88
|
for (const f of features) {
|
|
89
89
|
if (!f || typeof f.code !== 'string') continue;
|
|
90
90
|
if (isExternal(f.code)) continue;
|
|
91
|
-
const depsPath = join(
|
|
91
|
+
const depsPath = join(featuresDir, f.code, 'deps.yaml');
|
|
92
92
|
if (!existsSync(depsPath)) continue;
|
|
93
93
|
let deps;
|
|
94
94
|
try {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
12
12
|
import { join, isAbsolute, dirname } from 'node:path';
|
|
13
|
-
import {
|
|
13
|
+
import { resolveFeaturesPath } from '../project-paths.js';
|
|
14
14
|
import { collectGraphInputs } from './collect.js';
|
|
15
15
|
import { buildGraph } from './model.js';
|
|
16
16
|
import { renderGraphHtml } from './render.js';
|
|
@@ -22,7 +22,7 @@ import { loadGraphConfig } from './config.js';
|
|
|
22
22
|
* @returns {{ html: string, nodes: object[], edges: object[], dropped: string[], warnings: string[], config: object }}
|
|
23
23
|
*/
|
|
24
24
|
export function buildRoadmapGraph(cwd) {
|
|
25
|
-
const featuresDir =
|
|
25
|
+
const featuresDir = resolveFeaturesPath(cwd);
|
|
26
26
|
const inputs = collectGraphInputs(cwd, featuresDir);
|
|
27
27
|
const graph = buildGraph(inputs); // throws DanglingEdgeError
|
|
28
28
|
const config = loadGraphConfig(cwd);
|
package/lib/rtk.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COMP-RTK-INTEROP — optional RTK (Rust Token Killer) interop.
|
|
3
|
+
*
|
|
4
|
+
* RTK (https://github.com/rtk-ai/rtk) is a LOSSY command-output compressor invoked
|
|
5
|
+
* as a command-PREFIX wrapper: `rtk git diff` runs git diff itself and rewrites
|
|
6
|
+
* stdout, compressing it 60–90% for LLM consumption. There is no stdin-filter mode.
|
|
7
|
+
*
|
|
8
|
+
* INTEGRATION RULE — read before adding a call site:
|
|
9
|
+
* Wrap a command with rtkPrefix() ONLY when its stdout is fed into an LLM prompt
|
|
10
|
+
* (e.g. a diff shown to a Codex reviewer). NEVER wrap a command whose output is
|
|
11
|
+
* parsed by code (filename lists via split('\n'), SHAs/shortstat via regex/.trim())
|
|
12
|
+
* or re-applied mechanically (`git apply` patches) — RTK's lossy rewrite would
|
|
13
|
+
* silently corrupt the parse/patch.
|
|
14
|
+
*
|
|
15
|
+
* Also pass only operator-free bare commands (no `;`, `&&`, `||`, `2>`): RTK runs the
|
|
16
|
+
* command, so shell operators would not be wrapped meaningfully.
|
|
17
|
+
*
|
|
18
|
+
* Detection is memoized once per process. Set COMPOSE_DISABLE_RTK=1 to force-disable
|
|
19
|
+
* (e.g. CI, or if RTK ever misbehaves) — degrades to raw, byte-identical commands.
|
|
20
|
+
*/
|
|
21
|
+
import { spawnSync } from 'node:child_process'
|
|
22
|
+
|
|
23
|
+
let _cached = null // memoized availability; null = not yet probed
|
|
24
|
+
|
|
25
|
+
// Default probe: `rtk --version` exits 0 when installed. Swappable in tests.
|
|
26
|
+
let _prober = () => {
|
|
27
|
+
try {
|
|
28
|
+
const r = spawnSync('rtk', ['--version'], { encoding: 'utf-8', timeout: 3000, stdio: 'pipe' })
|
|
29
|
+
return r.status === 0
|
|
30
|
+
} catch {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether `rtk` is usable in this process. Memoized after the first call.
|
|
37
|
+
* COMPOSE_DISABLE_RTK=1 short-circuits to false (and is cached like any other result).
|
|
38
|
+
*/
|
|
39
|
+
export function isRtkAvailable() {
|
|
40
|
+
if (_cached !== null) return _cached
|
|
41
|
+
if (process.env.COMPOSE_DISABLE_RTK === '1') {
|
|
42
|
+
_cached = false
|
|
43
|
+
return _cached
|
|
44
|
+
}
|
|
45
|
+
_cached = _prober()
|
|
46
|
+
return _cached
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Prefix an LLM-bound command with `rtk` when RTK is available; otherwise return it
|
|
51
|
+
* unchanged (byte-identical degrade). Pass only operator-free bare commands.
|
|
52
|
+
*/
|
|
53
|
+
export function rtkPrefix(command) {
|
|
54
|
+
return isRtkAvailable() ? `rtk ${command}` : command
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --- test seams ------------------------------------------------------------
|
|
58
|
+
/** Replace the availability prober and clear the memoized result. Tests only. */
|
|
59
|
+
export function _setRtkProber(fn) {
|
|
60
|
+
_prober = fn
|
|
61
|
+
_cached = null
|
|
62
|
+
}
|
|
63
|
+
/** Clear the memoized availability result so the next call re-probes. Tests only. */
|
|
64
|
+
export function _resetRtkCache() {
|
|
65
|
+
_cached = null
|
|
66
|
+
}
|