@smartmemory/compose 0.2.49-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/bin/compose.js +30 -18
- package/dist/assets/{App-DOerhoDp.js → App-Bxyif1yv.js} +53 -53
- package/dist/assets/{arc-B1TqAC_R.js → arc-DeHak63Z.js} +1 -1
- package/dist/assets/{architectureDiagram-3BPJPVTR-C4hfxNOw.js → architectureDiagram-3BPJPVTR-C5Nkyl9y.js} +1 -1
- package/dist/assets/{blockDiagram-GPEHLZMM-DRqkGncy.js → blockDiagram-GPEHLZMM-BilnUBG2.js} +1 -1
- package/dist/assets/{c4Diagram-AAUBKEIU-Dc6swoQo.js → c4Diagram-AAUBKEIU-BsFJmPuS.js} +1 -1
- package/dist/assets/channel-ZgbQ1k0u.js +1 -0
- package/dist/assets/{chunk-2J33WTMH-6HKDSnOw.js → chunk-2J33WTMH-AEu6HIoY.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-CdqXgd68.js → chunk-4BX2VUAB-XO4S2_89.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-D7VauOmU.js → chunk-55IACEB6-CtFSLInj.js} +1 -1
- package/dist/assets/{chunk-727SXJPM-CeKpXDif.js → chunk-727SXJPM-CZeBRpM9.js} +1 -1
- package/dist/assets/{chunk-AQP2D5EJ-y2XL-YHV.js → chunk-AQP2D5EJ-_vmxcBc4.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-C1Z5m8DP.js → chunk-FMBD7UC4-BMGd_Y9a.js} +1 -1
- package/dist/assets/{chunk-ND2GUHAM-DmqZMEQe.js → chunk-ND2GUHAM-BlrOECEr.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-DY9GU2uD.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-B34g9xzV.js → cose-bilkent-S5V4N54A-C3M0mZqk.js} +1 -1
- package/dist/assets/{dagre-BM42HDAG-klZ_GOcw.js → dagre-BM42HDAG-DJlm4fEk.js} +1 -1
- package/dist/assets/{diagram-2AECGRRQ-D0FmM6Wf.js → diagram-2AECGRRQ-BmcONQG5.js} +1 -1
- package/dist/assets/{diagram-5GNKFQAL-DYuhby6q.js → diagram-5GNKFQAL-5JbWGIwd.js} +1 -1
- package/dist/assets/{diagram-KO2AKTUF-CCtQZgal.js → diagram-KO2AKTUF-DAnIvyI0.js} +1 -1
- package/dist/assets/{diagram-LMA3HP47-Cd1OqEln.js → diagram-LMA3HP47-Bmh7tvmI.js} +1 -1
- package/dist/assets/{diagram-OG6HWLK6-CiJrhoxo.js → diagram-OG6HWLK6-hnHVMNnk.js} +1 -1
- package/dist/assets/{erDiagram-TEJ5UH35-C05_Iqmb.js → erDiagram-TEJ5UH35-AMDitV0b.js} +1 -1
- package/dist/assets/{flowDiagram-I6XJVG4X-lXpnb-Ch.js → flowDiagram-I6XJVG4X-C0t2ZGQv.js} +1 -1
- package/dist/assets/{ganttDiagram-6RSMTGT7-EPbL_2Hr.js → ganttDiagram-6RSMTGT7-BPlM-BEN.js} +1 -1
- package/dist/assets/{gitGraphDiagram-PVQCEYII-COCoF_vW.js → gitGraphDiagram-PVQCEYII-Lv_YTxnb.js} +1 -1
- package/dist/assets/index-COq21Zym.js +119 -0
- package/dist/assets/{infoDiagram-5YYISTIA-B1sJw6j5.js → infoDiagram-5YYISTIA-Dul1vdUm.js} +1 -1
- package/dist/assets/{ishikawaDiagram-YF4QCWOH-CgdsWdgu.js → ishikawaDiagram-YF4QCWOH-CCBwgjFs.js} +1 -1
- package/dist/assets/{journeyDiagram-JHISSGLW-CyvHmE3z.js → journeyDiagram-JHISSGLW-zUJN38EU.js} +1 -1
- package/dist/assets/{kanban-definition-UN3LZRKU-Bqjdyv0T.js → kanban-definition-UN3LZRKU-BNxWi-8J.js} +1 -1
- package/dist/assets/{linear-DecnRXf0.js → linear-CRH9b4g7.js} +1 -1
- package/dist/assets/{mindmap-definition-RKZ34NQL-DM1x9vNY.js → mindmap-definition-RKZ34NQL-DolYwq7Y.js} +1 -1
- package/dist/assets/{pieDiagram-4H26LBE5-CRtmCvwV.js → pieDiagram-4H26LBE5-oxNfr2TX.js} +1 -1
- package/dist/assets/{quadrantDiagram-W4KKPZXB-CsBLWg27.js → quadrantDiagram-W4KKPZXB-BmZCvD-z.js} +1 -1
- package/dist/assets/{requirementDiagram-4Y6WPE33-DD-Gg_y5.js → requirementDiagram-4Y6WPE33-DsXZ05jo.js} +1 -1
- package/dist/assets/{sankeyDiagram-5OEKKPKP-BPdMN8UU.js → sankeyDiagram-5OEKKPKP-DsnYaawP.js} +1 -1
- package/dist/assets/{sequenceDiagram-3UESZ5HK-D4AENdze.js → sequenceDiagram-3UESZ5HK-Dt5mu3g8.js} +1 -1
- package/dist/assets/{stateDiagram-AJRCARHV-DDplpCf_.js → stateDiagram-AJRCARHV-BFR5ZINQ.js} +1 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-BDrQD8fR.js +1 -0
- package/dist/assets/{timeline-definition-PNZ67QCA-BPm2AEQj.js → timeline-definition-PNZ67QCA-Bvnhw58e.js} +1 -1
- package/dist/assets/{vennDiagram-CIIHVFJN-DQCDWwlj.js → vennDiagram-CIIHVFJN-5WnyjZqf.js} +1 -1
- package/dist/assets/{wardley-L42UT6IY-WERqOmJt.js → wardley-L42UT6IY-CTxW4xow.js} +1 -1
- package/dist/assets/{wardleyDiagram-YWT4CUSO-5teUSOiI.js → wardleyDiagram-YWT4CUSO-K8Y1EOvb.js} +1 -1
- package/dist/assets/{xychartDiagram-2RQKCTM6-Ba05SXXF.js → xychartDiagram-2RQKCTM6-BCShes6B.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/build-all.js +2 -1
- package/lib/build.js +91 -5
- package/lib/checkpoint/checkpoint-writer.js +3 -2
- package/lib/completion-writer.js +38 -14
- 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/state-migrations.js +3 -3
- package/lib/tracker/local-provider.js +2 -2
- package/lib/triage.js +7 -5
- package/lib/xref-push.js +4 -4
- package/lib/xref-sync.js +4 -4
- package/package.json +1 -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 +7 -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-utils.js +23 -4
- package/dist/assets/channel-KsY1x-7s.js +0 -1
- package/dist/assets/classDiagram-4FO5ZUOK-zDEDoVPk.js +0 -1
- package/dist/assets/classDiagram-v2-Q7XG4LA2-zDEDoVPk.js +0 -1
- package/dist/assets/index-DlgxA8dX.js +0 -123
- package/dist/assets/stateDiagram-v2-BHNVJYJU-J4t9AVzF.js +0 -1
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/state-migrations.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
renameSync, unlinkSync, readdirSync,
|
|
28
28
|
} from 'fs';
|
|
29
29
|
import { join } from 'path';
|
|
30
|
-
import {
|
|
30
|
+
import { resolveFeaturesPath } from './project-paths.js';
|
|
31
31
|
import { writeFeature } from './feature-json.js';
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -283,8 +283,8 @@ export function runStateMigrations(cwd, opts = {}) {
|
|
|
283
283
|
|
|
284
284
|
// --- feature.json walk -----------------------------------------------------
|
|
285
285
|
if (featurePending.length > 0) {
|
|
286
|
-
const featuresDir =
|
|
287
|
-
const featuresRoot =
|
|
286
|
+
const featuresDir = resolveFeaturesPath(cwd); // absolute; honors paths.features override
|
|
287
|
+
const featuresRoot = featuresDir;
|
|
288
288
|
// Own directory walk — do NOT use listFeatures(): it silently skips unreadable
|
|
289
289
|
// files, which would hide exactly the cold-data corruption we must surface.
|
|
290
290
|
let dirs = [];
|
|
@@ -2,7 +2,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, renameS
|
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
|
|
4
4
|
import { readFeature, listFeatures as listFeaturesRaw, writeFeature } from '../feature-json.js';
|
|
5
|
-
import {
|
|
5
|
+
import { resolveFeaturesPath } from '../project-paths.js';
|
|
6
6
|
import { TrackerProvider, CAP } from './provider.js';
|
|
7
7
|
|
|
8
8
|
import { setFeatureStatus, addRoadmapEntry as addRoadmapEntryRaw } from '../feature-writer.js';
|
|
@@ -38,7 +38,7 @@ export class LocalFileProvider extends TrackerProvider {
|
|
|
38
38
|
|
|
39
39
|
async init(cwd) {
|
|
40
40
|
this.cwd = cwd;
|
|
41
|
-
this.featuresDir =
|
|
41
|
+
this.featuresDir = resolveFeaturesPath(cwd);
|
|
42
42
|
return this;
|
|
43
43
|
}
|
|
44
44
|
|
package/lib/triage.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
import { readFileSync, existsSync, statSync, readdirSync } from 'node:fs';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
|
+
import { resolveFeaturesPath } from './project-paths.js';
|
|
15
|
+
import { resolvePathValue } from './paths-core.js';
|
|
14
16
|
|
|
15
17
|
// ---------------------------------------------------------------------------
|
|
16
18
|
// Tier definitions
|
|
@@ -185,8 +187,8 @@ function deriveProfile(signals) {
|
|
|
185
187
|
*/
|
|
186
188
|
export async function runTriage(featureCode, opts = {}) {
|
|
187
189
|
const cwd = opts.cwd ?? process.cwd();
|
|
188
|
-
const featuresDir = opts.featuresDir ??
|
|
189
|
-
const featureDir = join(cwd, featuresDir, featureCode);
|
|
190
|
+
const featuresDir = opts.featuresDir ?? resolveFeaturesPath(cwd);
|
|
191
|
+
const featureDir = join(resolvePathValue(cwd, featuresDir, 'features'), featureCode);
|
|
190
192
|
|
|
191
193
|
// Collect content from key files
|
|
192
194
|
const candidateFiles = ['plan.md', 'blueprint.md', 'design.md', 'prd.md', 'architecture.md'];
|
|
@@ -234,11 +236,11 @@ export async function runTriage(featureCode, opts = {}) {
|
|
|
234
236
|
*
|
|
235
237
|
* @param {string} cwd - Project root
|
|
236
238
|
* @param {string} featureCode - Feature code
|
|
237
|
-
* @param {string} [featuresDir] -
|
|
239
|
+
* @param {string} [featuresDir] - Path to features dir (absolute or relative; default: resolved features path)
|
|
238
240
|
* @returns {boolean}
|
|
239
241
|
*/
|
|
240
|
-
export function isTriageStale(cwd, featureCode, featuresDir =
|
|
241
|
-
const featureDir = join(cwd, featuresDir, featureCode);
|
|
242
|
+
export function isTriageStale(cwd, featureCode, featuresDir = resolveFeaturesPath(cwd)) {
|
|
243
|
+
const featureDir = join(resolvePathValue(cwd, featuresDir, 'features'), featureCode);
|
|
242
244
|
const featureJsonPath = join(featureDir, 'feature.json');
|
|
243
245
|
|
|
244
246
|
if (!existsSync(featureJsonPath)) return true;
|
package/lib/xref-push.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { readdirSync, existsSync, readFileSync } from 'fs';
|
|
19
19
|
import { join } from 'path';
|
|
20
|
-
import {
|
|
20
|
+
import { resolveFeaturesPath } from './project-paths.js';
|
|
21
21
|
import { resolveSiblingRoot } from './xref-local.js';
|
|
22
22
|
|
|
23
23
|
const GITHUB_STATES = new Set(['open', 'closed']);
|
|
@@ -145,7 +145,7 @@ function localResolve(link, cwd) {
|
|
|
145
145
|
const sib = resolveSiblingRoot(cwd, link.repo);
|
|
146
146
|
if (sib.skipped) return sib;
|
|
147
147
|
try {
|
|
148
|
-
const fjPath = join(
|
|
148
|
+
const fjPath = join(resolveFeaturesPath(sib.root), link.to_code, 'feature.json');
|
|
149
149
|
if (!existsSync(fjPath)) return { skipped: true, reason: `local target ${link.repo}/${link.to_code} not found` };
|
|
150
150
|
return { state: JSON.parse(readFileSync(fjPath, 'utf8')).status || null, root: sib.root };
|
|
151
151
|
} catch (e) { return { skipped: true, reason: `unreadable local target: ${e.message}` }; }
|
|
@@ -171,13 +171,13 @@ function localResolve(link, cwd) {
|
|
|
171
171
|
* @returns {Promise<{pushed: Array, skipped: Array, unchanged: number, scanned: number}>}
|
|
172
172
|
*/
|
|
173
173
|
export async function pushExternalRefs(cwd, opts = {}) {
|
|
174
|
-
const featuresDir = opts.featuresDir ??
|
|
174
|
+
const featuresDir = opts.featuresDir ?? resolveFeaturesPath(cwd);
|
|
175
175
|
const clientOpts = { transport: opts.githubTransport ?? null, auth: opts.githubAuth };
|
|
176
176
|
const ghResolve = opts.resolve ?? ((link) => defaultResolve(link, clientOpts));
|
|
177
177
|
const ghWrite = opts.write ?? ((link, patch) => defaultWrite(link, patch, clientOpts));
|
|
178
178
|
const setStatus = opts.setStatus ?? defaultSetStatus;
|
|
179
179
|
const apply = opts.apply === true;
|
|
180
|
-
const dir =
|
|
180
|
+
const dir = featuresDir;
|
|
181
181
|
|
|
182
182
|
const pushed = [];
|
|
183
183
|
const skipped = [];
|
package/lib/xref-sync.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { readdirSync, existsSync, readFileSync } from 'fs';
|
|
17
17
|
import { join } from 'path';
|
|
18
18
|
import { writeFeature } from './feature-json.js';
|
|
19
|
-
import {
|
|
19
|
+
import { resolveFeaturesPath } from './project-paths.js';
|
|
20
20
|
import { resolveSiblingRoot } from './xref-local.js';
|
|
21
21
|
|
|
22
22
|
const RESOLVABLE = new Set(['github', 'local']);
|
|
@@ -80,7 +80,7 @@ async function defaultResolve(link, cwd, featuresDir) {
|
|
|
80
80
|
const citedRoot = sib.root;
|
|
81
81
|
// Resolve the SIBLING's own features dir (it may have its own paths.features).
|
|
82
82
|
try {
|
|
83
|
-
const fjPath = join(
|
|
83
|
+
const fjPath = join(resolveFeaturesPath(citedRoot), link.to_code, 'feature.json');
|
|
84
84
|
if (!existsSync(fjPath)) return { skipped: true, reason: `local target ${link.repo}/${link.to_code} not found` };
|
|
85
85
|
return { state: JSON.parse(readFileSync(fjPath, 'utf8')).status || null };
|
|
86
86
|
} catch (e) { return { skipped: true, reason: `unreadable local target: ${e.message}` }; }
|
|
@@ -100,9 +100,9 @@ async function defaultResolve(link, cwd, featuresDir) {
|
|
|
100
100
|
* @returns {Promise<{synced: Array, skipped: Array, unchanged: number, scanned: number}>}
|
|
101
101
|
*/
|
|
102
102
|
export async function syncExternalRefs(cwd, opts = {}) {
|
|
103
|
-
const featuresDir = opts.featuresDir ??
|
|
103
|
+
const featuresDir = opts.featuresDir ?? resolveFeaturesPath(cwd);
|
|
104
104
|
const resolve = opts.resolve ?? defaultResolve;
|
|
105
|
-
const dir =
|
|
105
|
+
const dir = featuresDir;
|
|
106
106
|
|
|
107
107
|
const synced = [];
|
|
108
108
|
const skipped = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartmemory/compose",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.50-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/design-routes.js
CHANGED
|
@@ -16,6 +16,8 @@ import { randomUUID } from 'node:crypto';
|
|
|
16
16
|
import { parseDecisionBlocks } from '../src/components/vision/designSessionState.js';
|
|
17
17
|
import { StratumMcpClient } from '../lib/stratum-mcp-client.js';
|
|
18
18
|
import { KNOWN_VERSIONS } from '../lib/build-stream-schema.js';
|
|
19
|
+
import { getTargetRoot, resolveProjectPath } from './project-root.js';
|
|
20
|
+
import { relForDisplay } from '../lib/project-paths.js';
|
|
19
21
|
|
|
20
22
|
// Lazy singleton — design conversations share one stratum-mcp connection
|
|
21
23
|
// across the server process lifetime. Concurrent runs are correlation-id scoped.
|
|
@@ -444,16 +446,19 @@ Output ONLY the Markdown content, no code fences.`;
|
|
|
444
446
|
return;
|
|
445
447
|
}
|
|
446
448
|
|
|
447
|
-
// Determine the output path
|
|
448
|
-
|
|
449
|
+
// Determine the output path. Resolve the absolute write target first
|
|
450
|
+
// (features may be relocated outside the root — COMP-PATHS-EXTERNAL),
|
|
451
|
+
// then derive the display-relative string from it.
|
|
452
|
+
let absPath, designDocPath;
|
|
449
453
|
if (scope === 'feature' && featureCode) {
|
|
450
|
-
|
|
454
|
+
absPath = path.join(resolveProjectPath('features'), featureCode, 'design.md');
|
|
455
|
+
designDocPath = relForDisplay(getTargetRoot(), absPath);
|
|
451
456
|
} else {
|
|
452
457
|
designDocPath = path.join('docs', 'design.md');
|
|
458
|
+
absPath = path.join(projectRoot, designDocPath);
|
|
453
459
|
}
|
|
454
460
|
|
|
455
461
|
// Guard: never overwrite an existing doc with empty content
|
|
456
|
-
const absPath = path.join(projectRoot, designDocPath);
|
|
457
462
|
if (!docContent.trim()) {
|
|
458
463
|
console.error('[design] Generated doc is empty — refusing to overwrite');
|
|
459
464
|
res.status(500).json({ error: 'Doc generation produced empty content' });
|
package/server/drift-axes.js
CHANGED
|
@@ -19,6 +19,7 @@ import fs from 'node:fs';
|
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { execSync } from 'node:child_process';
|
|
21
21
|
import { diffContracts } from './contract-diff.js';
|
|
22
|
+
import { resolveFeaturesPath } from '../lib/project-paths.js';
|
|
22
23
|
|
|
23
24
|
// ── Threshold constants (Decision 2) ─────────────────────────────────────────
|
|
24
25
|
|
|
@@ -353,9 +354,10 @@ export function computeDriftAxes(item, projectRoot, now) {
|
|
|
353
354
|
|
|
354
355
|
const ts = now || new Date().toISOString();
|
|
355
356
|
|
|
356
|
-
// Resolve the
|
|
357
|
-
//
|
|
358
|
-
|
|
357
|
+
// Resolve the features/<FC> directory against projectRoot's own config
|
|
358
|
+
// (relocatable artifact paths, COMP-PATHS-EXTERNAL). Byte-identical to
|
|
359
|
+
// projectRoot/docs/features/<FC> for the in-root default.
|
|
360
|
+
const featurePath = path.join(resolveFeaturesPath(projectRoot), featureCode);
|
|
359
361
|
|
|
360
362
|
const pathAxis = computePathDrift(item, projectRoot, featurePath, ts);
|
|
361
363
|
const contractAxis = computeContractDrift(item, projectRoot, featurePath, ts);
|
package/server/feature-scan.js
CHANGED
|
@@ -16,6 +16,7 @@ import fs from 'node:fs';
|
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
|
|
18
18
|
import { getTargetRoot, resolveProjectPath } from './project-root.js';
|
|
19
|
+
import { relForDisplay } from '../lib/project-paths.js';
|
|
19
20
|
import { assertValidLinkShape } from '../lib/feature-write-guard.js';
|
|
20
21
|
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
@@ -608,6 +609,11 @@ export function seedFromRoadmapGraph(store) {
|
|
|
608
609
|
export function seedFeatures(features, store) {
|
|
609
610
|
const seeded = { features: 0, updated: 0, connections: 0 };
|
|
610
611
|
const featureItemMap = new Map(); // featureCode → itemId
|
|
612
|
+
const root = getTargetRoot();
|
|
613
|
+
const featuresBase = resolveProjectPath('features');
|
|
614
|
+
// Root-relative for the in-root default (relForDisplay guarantees byte-identity
|
|
615
|
+
// there); absolute when the features dir is relocated outside the workspace root.
|
|
616
|
+
const artifactPath = (feature, a) => relForDisplay(root, path.join(featuresBase, feature.name, a));
|
|
611
617
|
|
|
612
618
|
// First pass: create/update items
|
|
613
619
|
for (const feature of features) {
|
|
@@ -623,7 +629,7 @@ export function seedFeatures(features, store) {
|
|
|
623
629
|
status: feature.status || 'planned',
|
|
624
630
|
phase: feature.phase || 'planning',
|
|
625
631
|
confidence: feature.confidence,
|
|
626
|
-
files: feature.artifacts.map(a =>
|
|
632
|
+
files: feature.artifacts.map(a => artifactPath(feature, a)),
|
|
627
633
|
...(feature.group ? { group: feature.group } : {}),
|
|
628
634
|
});
|
|
629
635
|
try {
|
|
@@ -643,7 +649,7 @@ export function seedFeatures(features, store) {
|
|
|
643
649
|
if (feature.confidence > (featureItem.confidence || 0)) {
|
|
644
650
|
updates.confidence = feature.confidence;
|
|
645
651
|
}
|
|
646
|
-
const newFiles = feature.artifacts.map(a =>
|
|
652
|
+
const newFiles = feature.artifacts.map(a => artifactPath(feature, a));
|
|
647
653
|
if (JSON.stringify(newFiles) !== JSON.stringify(featureItem.files || [])) {
|
|
648
654
|
updates.files = newFiles;
|
|
649
655
|
}
|
package/server/file-watcher.js
CHANGED
|
@@ -10,6 +10,7 @@ import path from 'node:path';
|
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
|
|
12
12
|
import { getTargetRoot, loadProjectConfig, ensureDataDir } from './project-root.js';
|
|
13
|
+
import { resolveDocsPathFromConfig, resolveFeaturesPathFromConfig } from '../lib/project-paths.js';
|
|
13
14
|
|
|
14
15
|
const PROJECT_ROOT = getTargetRoot();
|
|
15
16
|
|
|
@@ -69,7 +70,8 @@ export class FileWatcherServer {
|
|
|
69
70
|
app.get('/api/files', (_req, res) => {
|
|
70
71
|
const config = loadProjectConfig();
|
|
71
72
|
const docsPrefix = config.paths?.docs || 'docs';
|
|
72
|
-
|
|
73
|
+
// COMP-PATHS-EXTERNAL: list the RESOLVED docs dir (may be relocated).
|
|
74
|
+
const docsDir = resolveDocsPathFromConfig(PROJECT_ROOT, config);
|
|
73
75
|
try {
|
|
74
76
|
const files = this.listMarkdownFiles(docsDir, docsPrefix);
|
|
75
77
|
res.json({ files });
|
|
@@ -164,7 +166,10 @@ export class FileWatcherServer {
|
|
|
164
166
|
if (!filename || !fileFilter(filename)) return;
|
|
165
167
|
|
|
166
168
|
const relativePath = path.join(prefix, filename);
|
|
167
|
-
|
|
169
|
+
// COMP-PATHS-EXTERNAL: derive the real path from the WATCHED dir, not
|
|
170
|
+
// by re-rooting under PROJECT_ROOT — the dir may be relocated outside
|
|
171
|
+
// the workspace. Byte-identical to the old form for an in-root dir.
|
|
172
|
+
const fullPath = path.join(dir, filename);
|
|
168
173
|
|
|
169
174
|
// Debounce: ignore events within 100ms of each other for the same file
|
|
170
175
|
const now = Date.now();
|
|
@@ -180,10 +185,11 @@ export class FileWatcherServer {
|
|
|
180
185
|
}
|
|
181
186
|
};
|
|
182
187
|
|
|
183
|
-
// Watch docs/ — broadcast fileChanged events
|
|
188
|
+
// Watch docs/ — broadcast fileChanged events. COMP-PATHS-EXTERNAL: watch
|
|
189
|
+
// the RESOLVED absolute dir (may be relocated outside PROJECT_ROOT).
|
|
184
190
|
const config = loadProjectConfig();
|
|
185
191
|
const docsPrefix = config.paths?.docs || 'docs';
|
|
186
|
-
watchDir(
|
|
192
|
+
watchDir(resolveDocsPathFromConfig(PROJECT_ROOT, config), docsPrefix, (relativePath, fullPath) => {
|
|
187
193
|
try {
|
|
188
194
|
if (!fs.existsSync(fullPath)) return;
|
|
189
195
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
@@ -195,9 +201,9 @@ export class FileWatcherServer {
|
|
|
195
201
|
|
|
196
202
|
// Watch features/ — notify for auto-reseed into vision store
|
|
197
203
|
const featuresPrefix = config.paths?.features || 'docs/features';
|
|
198
|
-
watchDir(
|
|
199
|
-
// Also broadcast as fileChanged (features are docs)
|
|
200
|
-
|
|
204
|
+
watchDir(resolveFeaturesPathFromConfig(PROJECT_ROOT, config), featuresPrefix, (relativePath, fullPath) => {
|
|
205
|
+
// Also broadcast as fileChanged (features are docs). fullPath comes from
|
|
206
|
+
// the watched dir (COMP-PATHS-EXTERNAL) — do not re-root under PROJECT_ROOT.
|
|
201
207
|
try {
|
|
202
208
|
if (fs.existsSync(fullPath)) {
|
|
203
209
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
package/server/ideabox-routes.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from '../lib/ideabox.js'
|
|
26
26
|
import { writeFeature } from '../lib/feature-json.js'
|
|
27
27
|
import { IdeaboxCache } from './ideabox-cache.js'
|
|
28
|
+
import { resolveIdeaboxPathFromConfig, resolveFeaturesPathFromConfig, relForDisplay } from '../lib/project-paths.js'
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* @param {object} app — Express app
|
|
@@ -41,8 +42,7 @@ export function attachIdeaboxRoutes(app, { getProjectRoot, getDataDir, broadcast
|
|
|
41
42
|
const dataDir = getDataDir()
|
|
42
43
|
if (!_cache || _lastProjectRoot !== projectRoot || _lastDataDir !== dataDir) {
|
|
43
44
|
const config = loadConfig(projectRoot)
|
|
44
|
-
const
|
|
45
|
-
const sourceFile = path.join(projectRoot, ideaboxRel)
|
|
45
|
+
const sourceFile = resolveIdeaboxPathFromConfig(projectRoot, config)
|
|
46
46
|
_cache = new IdeaboxCache(dataDir, sourceFile)
|
|
47
47
|
_lastProjectRoot = projectRoot
|
|
48
48
|
_lastDataDir = dataDir
|
|
@@ -172,16 +172,17 @@ export function attachIdeaboxRoutes(app, { getProjectRoot, getDataDir, broadcast
|
|
|
172
172
|
resolvedCode = `IDEA-${sourceIdea.num}-${slug}`.toUpperCase()
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
// Create feature folder + feature.json (same logic as CLI promote)
|
|
175
|
+
// Create feature folder + feature.json (same logic as CLI promote).
|
|
176
|
+
// COMP-PATHS-EXTERNAL: resolve the ABSOLUTE features base (may be
|
|
177
|
+
// relocated outside projectRoot). Re-rooting under projectRoot would make
|
|
178
|
+
// existsSync check the wrong path and risk overwriting external canon.
|
|
176
179
|
const composeJsonPath = path.join(projectRoot, '.compose', 'compose.json')
|
|
177
|
-
let
|
|
180
|
+
let cfg = {}
|
|
178
181
|
if (fs.existsSync(composeJsonPath)) {
|
|
179
|
-
try {
|
|
180
|
-
const cfg = JSON.parse(fs.readFileSync(composeJsonPath, 'utf-8'))
|
|
181
|
-
featuresRel = cfg?.paths?.features || 'docs/features'
|
|
182
|
-
} catch {}
|
|
182
|
+
try { cfg = JSON.parse(fs.readFileSync(composeJsonPath, 'utf-8')) } catch {}
|
|
183
183
|
}
|
|
184
|
-
const
|
|
184
|
+
const featuresBase = resolveFeaturesPathFromConfig(projectRoot, cfg) // absolute
|
|
185
|
+
const featuresDir = path.join(featuresBase, resolvedCode)
|
|
185
186
|
if (!fs.existsSync(featuresDir)) {
|
|
186
187
|
// COMP-MCP-VALIDATE-1: route through the validated writer instead of a
|
|
187
188
|
// raw fs.writeFileSync so the promoted feature.json is schema-guarded.
|
|
@@ -191,7 +192,7 @@ export function attachIdeaboxRoutes(app, { getProjectRoot, getDataDir, broadcast
|
|
|
191
192
|
status: 'PLANNED',
|
|
192
193
|
promotedFrom: sourceIdea.id,
|
|
193
194
|
createdAt: new Date().toISOString(),
|
|
194
|
-
},
|
|
195
|
+
}, featuresBase)
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
promoteIdea(parsed, id, resolvedCode)
|
|
@@ -200,7 +201,7 @@ export function attachIdeaboxRoutes(app, { getProjectRoot, getDataDir, broadcast
|
|
|
200
201
|
broadcastUpdate()
|
|
201
202
|
|
|
202
203
|
const updated = parsed.ideas.find(i => i.id.toUpperCase() === upper)
|
|
203
|
-
res.json({ ...(updated || { id }), featureCode: resolvedCode, featurePath:
|
|
204
|
+
res.json({ ...(updated || { id }), featureCode: resolvedCode, featurePath: relForDisplay(projectRoot, featuresDir) })
|
|
204
205
|
} catch (err) {
|
|
205
206
|
const status = err.message.includes('not found') ? 404 : 500
|
|
206
207
|
res.status(status).json({ error: err.message })
|
package/server/project-root.js
CHANGED
|
@@ -15,6 +15,7 @@ import path from 'node:path';
|
|
|
15
15
|
import fs from 'node:fs';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
17
|
import { findProjectRoot } from './find-root.js';
|
|
18
|
+
import { DEFAULT_PATHS, resolvePathValue } from '../lib/paths-core.js';
|
|
18
19
|
|
|
19
20
|
export { findProjectRoot } from './find-root.js';
|
|
20
21
|
|
|
@@ -99,7 +100,7 @@ export function ensureDataDir() {
|
|
|
99
100
|
const DEFAULT_CONFIG = Object.freeze({
|
|
100
101
|
version: 1,
|
|
101
102
|
capabilities: Object.freeze({ stratum: true, lifecycle: true }),
|
|
102
|
-
paths:
|
|
103
|
+
paths: DEFAULT_PATHS, // single source of truth (COMP-PATHS-EXTERNAL)
|
|
103
104
|
});
|
|
104
105
|
|
|
105
106
|
function cloneConfig(obj) {
|
|
@@ -120,9 +121,11 @@ export function loadProjectConfig() {
|
|
|
120
121
|
|
|
121
122
|
export function resolveProjectPath(key) {
|
|
122
123
|
const config = loadProjectConfig();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
// resolvePathValue handles in-root / ../-escaping / absolute overrides.
|
|
125
|
+
// Preserve the legacy `|| key` fallback for keys absent from DEFAULT_PATHS.
|
|
126
|
+
// (COMP-PATHS-EXTERNAL)
|
|
127
|
+
const value = config.paths?.[key] ?? DEFAULT_PATHS[key] ?? key;
|
|
128
|
+
return resolvePathValue(getTargetRoot(), value, key);
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
/**
|
package/server/session-routes.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { readSessionsByFeature } from './session-store.js';
|
|
14
|
-
import { getTargetRoot, getDataDir } from './project-root.js';
|
|
14
|
+
import { getTargetRoot, getDataDir, resolveProjectPath } from './project-root.js';
|
|
15
15
|
import { appendPhaseHistory } from './lifecycle-phase-history.js';
|
|
16
16
|
import { reconcile } from '../lib/checkpoint/reconciler.js';
|
|
17
17
|
import { checkpointConfig } from '../lib/checkpoint/checkpoint-writer.js';
|
|
@@ -140,7 +140,7 @@ export function attachSessionRoutes(app, { sessionManager, scheduleBroadcast, br
|
|
|
140
140
|
featureCode,
|
|
141
141
|
item,
|
|
142
142
|
cwd: targetRoot,
|
|
143
|
-
featureDir: path.join(
|
|
143
|
+
featureDir: path.join(resolveProjectPath('features'), featureCode),
|
|
144
144
|
composeDir: path.join(targetRoot, '.compose'),
|
|
145
145
|
dataDir,
|
|
146
146
|
store: cpStore,
|