@smartmemory/compose 0.2.57-beta → 0.2.58-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 (69) hide show
  1. package/contracts/feature-json.schema.json +16 -0
  2. package/dist/assets/{App-BmhlHOXF.js → App-s4CulNkO.js} +31 -31
  3. package/dist/assets/{abnfDiagram-VRR7QNED-CMOTYAnt.js → abnfDiagram-VRR7QNED-CDwzQ5wr.js} +1 -1
  4. package/dist/assets/{arc-BJAv_6dL.js → arc-kIU8DDth.js} +1 -1
  5. package/dist/assets/{architectureDiagram-ZJ3FMSHR-xa4DLUcp.js → architectureDiagram-ZJ3FMSHR-CleorO_0.js} +1 -1
  6. package/dist/assets/{blockDiagram-677ZJIJ3-BdkDqeQF.js → blockDiagram-677ZJIJ3-NvEFfoMk.js} +1 -1
  7. package/dist/assets/{c4Diagram-LMCZKHZV-DiNrLIuL.js → c4Diagram-LMCZKHZV-BcDQdTVB.js} +1 -1
  8. package/dist/assets/channel-DiJkE9og.js +1 -0
  9. package/dist/assets/{chunk-2Q5K7J3B-BsjtgrSL.js → chunk-2Q5K7J3B-BseRkpm4.js} +1 -1
  10. package/dist/assets/{chunk-32BRIVSS-DpImULWy.js → chunk-32BRIVSS-DCHhvgFV.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-DTdG5JnR.js → chunk-5VM5RSS4-DcTpMdJ4.js} +1 -1
  12. package/dist/assets/{chunk-EX3LRPZG-Cj1jNuKY.js → chunk-EX3LRPZG-BdN8fvWs.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-DwQ0wwWn.js → chunk-JWPE2WC7-DmZJP-8E.js} +1 -1
  14. package/dist/assets/{chunk-MOJQB5TN-DZZa7unc.js → chunk-MOJQB5TN-V74uYM17.js} +1 -1
  15. package/dist/assets/{chunk-RYQCIY6F-CgdjqYQj.js → chunk-RYQCIY6F-CoXUXbn_.js} +1 -1
  16. package/dist/assets/{chunk-V7JOEXUC-DrPIj6XI.js → chunk-V7JOEXUC-DUscIydR.js} +1 -1
  17. package/dist/assets/{chunk-VR4S4FIN-BBUOVC5W.js → chunk-VR4S4FIN-C4UDHq_a.js} +1 -1
  18. package/dist/assets/{chunk-XXDRQBXY-BEIiZ6gS.js → chunk-XXDRQBXY-DIH9i8mZ.js} +1 -1
  19. package/dist/assets/classDiagram-OUVF2IWQ-DS76VBEL.js +1 -0
  20. package/dist/assets/classDiagram-v2-EOCWNBFH-DS76VBEL.js +1 -0
  21. package/dist/assets/{cose-bilkent-JH36ORCC-ClFTQEyD.js → cose-bilkent-JH36ORCC-Dn6Gy6l1.js} +1 -1
  22. package/dist/assets/{cynefin-VYW2F7L2-5Bp7xFO0.js → cynefin-VYW2F7L2-C2LW5fzB.js} +1 -1
  23. package/dist/assets/{cynefinDiagram-TSTJHNR4-C-0QlSda.js → cynefinDiagram-TSTJHNR4-CGoAq7C9.js} +1 -1
  24. package/dist/assets/{dagre-VKFMJZFB-DLkqsfBy.js → dagre-VKFMJZFB-BaOKtuBE.js} +1 -1
  25. package/dist/assets/{diagram-FQU43EPY-Cwh_tJ_r.js → diagram-FQU43EPY-BdQwqt4x.js} +1 -1
  26. package/dist/assets/{diagram-G47NLZAW-CvvAjulf.js → diagram-G47NLZAW-Dkcf0o5z.js} +1 -1
  27. package/dist/assets/{diagram-NH7WQ7WH-CCoyx8_e.js → diagram-NH7WQ7WH-C_fDi59r.js} +1 -1
  28. package/dist/assets/{diagram-OA4YK3LP-DfjhKYCp.js → diagram-OA4YK3LP-jaTQcOxt.js} +1 -1
  29. package/dist/assets/{diagram-WEI45ONY-Vjz0ygfB.js → diagram-WEI45ONY-dEtqVj3s.js} +1 -1
  30. package/dist/assets/{ebnfDiagram-CCIWWBDH-GsA8c6g2.js → ebnfDiagram-CCIWWBDH-s8ImqsSs.js} +1 -1
  31. package/dist/assets/{erDiagram-Q63AITRT-Cc0lJ9sQ.js → erDiagram-Q63AITRT-5Zy29-SA.js} +1 -1
  32. package/dist/assets/{flowDiagram-23GEKE2U-C_sotK-k.js → flowDiagram-23GEKE2U-DV74V9he.js} +1 -1
  33. package/dist/assets/{ganttDiagram-NO4QXBWP-BBtu_WUe.js → ganttDiagram-NO4QXBWP-BND-CiLO.js} +1 -1
  34. package/dist/assets/{gitGraphDiagram-IHSO6WYX-BV_aSB8-.js → gitGraphDiagram-IHSO6WYX-B_-Mf9Jo.js} +1 -1
  35. package/dist/assets/{index-CKfOVv2N.js → index-CBWbmG7W.js} +2 -2
  36. package/dist/assets/{infoDiagram-FWYZ7A6U-DMAvNGtX.js → infoDiagram-FWYZ7A6U-Duyz8rZM.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DfQB91MZ.js → ishikawaDiagram-FXEZZL3T-D2Qft0e5.js} +1 -1
  38. package/dist/assets/{journeyDiagram-5HDEW3XC-Bq4Llm9o.js → journeyDiagram-5HDEW3XC-q6rG4Rig.js} +1 -1
  39. package/dist/assets/{kanban-definition-HUTT4EX6-DTXHiDiE.js → kanban-definition-HUTT4EX6-CsPX7gBL.js} +1 -1
  40. package/dist/assets/{linear-BQSYZcVg.js → linear-CiccpAAd.js} +1 -1
  41. package/dist/assets/{mindmap-definition-LN4V7U3C-KuH2NIj0.js → mindmap-definition-LN4V7U3C-DRW2njpA.js} +1 -1
  42. package/dist/assets/{pegDiagram-2B236MQR-BKoNGQFr.js → pegDiagram-2B236MQR-DoDgm2S_.js} +1 -1
  43. package/dist/assets/{pieDiagram-ENE6RG2P-EpYv162Y.js → pieDiagram-ENE6RG2P-CNSYLVAF.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-ABIIQ3AL-C9deUjQ2.js → quadrantDiagram-ABIIQ3AL-8-Nd6ony.js} +1 -1
  45. package/dist/assets/{railroadDiagram-RFXS5EU6-Csqw2hK9.js → railroadDiagram-RFXS5EU6-bfdg6Bo4.js} +1 -1
  46. package/dist/assets/{requirementDiagram-TGXJPOKE-DCuz3emr.js → requirementDiagram-TGXJPOKE-slDkxCQy.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-HTMAVEWB-BTeTL24f.js → sankeyDiagram-HTMAVEWB-CR3CboiK.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-DBY2YBRQ-Cf1ZkTwO.js → sequenceDiagram-DBY2YBRQ-BIXw1JS1.js} +1 -1
  49. package/dist/assets/{sizeCapture-X5ZJPWSS-D3Lbewzg.js → sizeCapture-X5ZJPWSS-BdKI7JyF.js} +1 -1
  50. package/dist/assets/{stateDiagram-2N3HPSRC-O8l37yJI.js → stateDiagram-2N3HPSRC-ktVgpBZS.js} +1 -1
  51. package/dist/assets/stateDiagram-v2-6OUMAXLB-CzPQnlan.js +1 -0
  52. package/dist/assets/{swimlanes-5IMT3BWC-BikmTh8b.js → swimlanes-5IMT3BWC-CaXpC3FM.js} +2 -2
  53. package/dist/assets/swimlanesDiagram-G3AALYLV-rIDv6EuQ.js +8 -0
  54. package/dist/assets/{timeline-definition-FHXFAJF6-BOxSosG0.js → timeline-definition-FHXFAJF6-CKWSlRUl.js} +1 -1
  55. package/dist/assets/{vennDiagram-L72KCM5P-BSClOErZ.js → vennDiagram-L72KCM5P-CwOhxDyv.js} +1 -1
  56. package/dist/assets/{wardleyDiagram-EHGQE667-DHFRjOK0.js → wardleyDiagram-EHGQE667-DGavMpEB.js} +1 -1
  57. package/dist/assets/{xychartDiagram-FW5EYKEG-Dh1v4x-O.js → xychartDiagram-FW5EYKEG-CWmnmLZz.js} +1 -1
  58. package/dist/index.html +1 -1
  59. package/lib/build.js +46 -31
  60. package/lib/escalation.js +69 -0
  61. package/lib/feature-writer.js +58 -3
  62. package/lib/lane-gate.js +200 -0
  63. package/lib/triage.js +144 -0
  64. package/package.json +1 -1
  65. package/dist/assets/channel-CYErfopw.js +0 -1
  66. package/dist/assets/classDiagram-OUVF2IWQ-qCC_hnXu.js +0 -1
  67. package/dist/assets/classDiagram-v2-EOCWNBFH-qCC_hnXu.js +0 -1
  68. package/dist/assets/stateDiagram-v2-6OUMAXLB-BWqk_9py.js +0 -1
  69. package/dist/assets/swimlanesDiagram-G3AALYLV-C60ICdks.js +0 -8
@@ -0,0 +1,200 @@
1
+ // lib/lane-gate.js
2
+ //
3
+ // COMP-TRIAGE-5 — E3 (Estimate → Execute → Expand) front-of-pipeline scope
4
+ // estimation + verification-gated escalation. Two entry points, extracted from
5
+ // build.js runBuild/executeShipStep so they are unit-testable in isolation:
6
+ //
7
+ // applyFrontTriage() — E3 "Estimate": derive the lane from the RAW REQUEST
8
+ // before any design/plan/blueprint doc is read, and
9
+ // persist validated feature fields (closing the
10
+ // complexity: String(tier) bypass at build.js:997/1006).
11
+ //
12
+ // maybeEscalateLane() — E3 "Expand": on a failed ship-time test gate, escalate
13
+ // the lane so the NEXT build runs wider — persist the
14
+ // escalated lane + a widened profile + a bounded counter
15
+ // and drop a resume checkpoint. STOP → human handoff.
16
+ // Re-entry is via re-invocation reading the escalated
17
+ // lane, NOT inline runBuild surgery.
18
+
19
+ import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
20
+ import { join, resolve } from 'node:path';
21
+ import {
22
+ estimateScope,
23
+ tierToComplexity,
24
+ floorProfileToLane,
25
+ runTriage,
26
+ tierToLane,
27
+ narrowerLane,
28
+ } from './triage.js';
29
+ import { validateFeatureFields } from './feature-writer.js';
30
+ import { escalate } from './escalation.js';
31
+
32
+ // Docs that make a doc-reading refinement meaningful. Absent all three, runTriage
33
+ // would see zero files and return tier 0 ('trivial') — which is "no signal", not
34
+ // "confirmed simple", and must NOT be allowed to narrow away the front safety clamp.
35
+ const REFINEMENT_DOCS = ['design.md', 'plan.md', 'blueprint.md'];
36
+
37
+ /**
38
+ * E3 Estimate. Runs before spec load. Derives {tier, profile, lane} from the raw
39
+ * request (doc-free) and persists validated fields through the shared validator
40
+ * (so the tier can no longer be stringified into `complexity`). Returns the
41
+ * values runBuild needs to toggle skip_if.
42
+ *
43
+ * @param {object} a
44
+ * @param {string} a.featureCode
45
+ * @param {string} [a.request] raw task text (opts.description); falls back to the code
46
+ * @param {object} a.provider build provider (getFeature/createFeature/putFeature)
47
+ * @param {object|null} a.cachedFeature already-fetched feature.json, or null
48
+ * @returns {Promise<{buildProfile:object, tier:number, lane:string, tierLabel:string, rationale:string, cachedFeature:object}>}
49
+ */
50
+ export async function applyFrontTriage({ featureCode, request, provider, cachedFeature, cwd, featuresDir }) {
51
+ // The CLI build path does not thread a description into opts, so fall back to
52
+ // the feature's own persisted description before the (signal-free) code string —
53
+ // otherwise every CLI build would classify the code and land low-confidence.
54
+ const req = request ?? cachedFeature?.description ?? featureCode;
55
+ const front = estimateScope(req);
56
+ let tier = front.tier;
57
+ let lane = front.lane;
58
+ let buildProfile = front.profile;
59
+ let estimateSource = 'front';
60
+
61
+ // E3 refinement (narrow-only): once design/plan/blueprint docs exist, a
62
+ // doc-reading pass may CONFIRM a smaller scope than the raw request implied —
63
+ // but it may only narrow the front lane, never widen it (widening is the
64
+ // escalation path's job). Gated on doc existence so a doc-less tier-0 read
65
+ // cannot undo the front safety clamp.
66
+ if (cwd && featuresDir) {
67
+ const dir = resolve(cwd, featuresDir, featureCode);
68
+ const hasDocs = REFINEMENT_DOCS.some((f) => existsSync(join(dir, f)));
69
+ if (hasDocs) {
70
+ try {
71
+ const refined = await runTriage(featureCode, { cwd, featuresDir });
72
+ const narrowed = narrowerLane(lane, tierToLane(refined.tier));
73
+ if (narrowed !== lane) {
74
+ lane = narrowed;
75
+ tier = refined.tier;
76
+ buildProfile = floorProfileToLane(refined.profile, narrowed);
77
+ estimateSource = 'refined';
78
+ }
79
+ } catch {
80
+ /* triage read failed — keep the front estimate */
81
+ }
82
+ }
83
+ }
84
+
85
+ const fields = {
86
+ complexity: tierToComplexity(tier),
87
+ triageTier: tier,
88
+ lane,
89
+ estimateSource,
90
+ profile: buildProfile,
91
+ triageTimestamp: new Date().toISOString(),
92
+ };
93
+ // Throws on any invalid field — this is the guard the raw provider write bypassed.
94
+ validateFeatureFields(fields);
95
+
96
+ let updated;
97
+ if (!cachedFeature) {
98
+ updated = await provider.createFeature(featureCode, {
99
+ code: featureCode,
100
+ description: req,
101
+ status: 'PLANNED',
102
+ ...fields,
103
+ });
104
+ } else {
105
+ updated = await provider.putFeature(featureCode, { ...cachedFeature, ...fields });
106
+ }
107
+
108
+ return {
109
+ buildProfile,
110
+ tier,
111
+ lane,
112
+ tierLabel: fields.complexity,
113
+ rationale: front.rationale,
114
+ cachedFeature: updated,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * E3 Expand. On a failed ship-time test gate, escalate the lane so the next build
120
+ * runs wider. Bounded via escalationCount; on STOP or exhausted ladder, writes a
121
+ * checkpoint and hands off to a human. Best-effort — the caller never blocks ship
122
+ * on this. Only acts on features that went through front triage (have a `lane`).
123
+ *
124
+ * @param {object} a
125
+ * @param {string} a.featureCode
126
+ * @param {object} a.provider build provider
127
+ * @param {string} [a.featureDir] dir to write the escalation checkpoint into
128
+ * @param {string|null} [a.currentPhase] optional live phase (unused for reEntry today; see escalation.js)
129
+ * @returns {Promise<{action:'none'|'escalate'|'stop', [from]:string, [to]:string, [reEntryPhase]:string, [escalationCount]:number}>}
130
+ */
131
+ export async function maybeEscalateLane({ featureCode, provider, featureDir, currentPhase = null }) {
132
+ const feature = await provider.getFeature(featureCode);
133
+ // Only features that went through the front seam carry a lane — never escalate
134
+ // a feature that never opted into lane-based execution.
135
+ if (!feature?.lane) return { action: 'none' };
136
+
137
+ const lane = feature.lane;
138
+ const escalationCount = feature.escalationCount ?? 0;
139
+ const decision = escalate({ gate: 'test', passed: false }, lane, escalationCount, currentPhase);
140
+
141
+ if (decision === 'STOP') {
142
+ writeCheckpoint(featureDir, { featureCode, lane, decision: 'STOP', reEntryPhase: null, escalationCount });
143
+ return { action: 'stop', lane, escalationCount };
144
+ }
145
+ if (!decision) return { action: 'none' };
146
+
147
+ // Widen the profile to match the ESCALATED lane (not always full) so lane and
148
+ // profile stay consistent and each rung actually adds phases.
149
+ const widenedProfile = floorProfileToLane(feature.profile ?? {}, decision.nextLane);
150
+ validateFeatureFields({ lane: decision.nextLane, estimateSource: 'escalated' });
151
+ // Write the checkpoint BEFORE stamping triageTimestamp so the checkpoint file's
152
+ // mtime is older than the stamp — otherwise isTriageStale would treat the just-
153
+ // escalated feature as stale on the next build and overwrite the escalation.
154
+ writeCheckpoint(featureDir, {
155
+ featureCode,
156
+ lane: decision.nextLane,
157
+ decision: 'escalate',
158
+ reEntryPhase: decision.reEntryPhase,
159
+ escalationCount: escalationCount + 1,
160
+ });
161
+ await provider.putFeature(featureCode, {
162
+ ...feature,
163
+ lane: decision.nextLane,
164
+ estimateSource: 'escalated',
165
+ escalationCount: escalationCount + 1,
166
+ profile: widenedProfile,
167
+ triageTimestamp: new Date().toISOString(),
168
+ });
169
+ return {
170
+ action: 'escalate',
171
+ from: lane,
172
+ to: decision.nextLane,
173
+ reEntryPhase: decision.reEntryPhase,
174
+ escalationCount: escalationCount + 1,
175
+ };
176
+ }
177
+
178
+ function writeCheckpoint(featureDir, { featureCode, lane, decision, reEntryPhase, escalationCount }) {
179
+ if (!featureDir) return;
180
+ try {
181
+ mkdirSync(featureDir, { recursive: true });
182
+ const lines = [
183
+ `# COMP-TRIAGE-5 Escalation Checkpoint — ${featureCode}`,
184
+ '',
185
+ `- Decision: ${decision}`,
186
+ `- Lane: ${lane}`,
187
+ reEntryPhase ? `- Re-enter at phase: ${reEntryPhase}` : '- Re-enter: n/a (STOP — human handoff)',
188
+ `- Escalation count: ${escalationCount}`,
189
+ '- Trigger: ship-time test gate failed (E3 Expand)',
190
+ '',
191
+ decision === 'STOP'
192
+ ? 'Escalation bound reached. A human should investigate before re-running.'
193
+ : `Re-run \`compose build ${featureCode}\` — it reads the escalated lane and runs the heavier phases.`,
194
+ '',
195
+ ];
196
+ writeFileSync(join(featureDir, 'escalation-checkpoint.md'), lines.join('\n'), 'utf-8');
197
+ } catch {
198
+ /* best-effort — a checkpoint failure must never break the build */
199
+ }
200
+ }
package/lib/triage.js CHANGED
@@ -174,6 +174,150 @@ function deriveProfile(signals) {
174
174
  };
175
175
  }
176
176
 
177
+ // ---------------------------------------------------------------------------
178
+ // Front-seam scope estimation (doc-free)
179
+ // ---------------------------------------------------------------------------
180
+
181
+ // Lane order, smaller index = smaller scope / more conservative.
182
+ const LANE_ORDER = { trivial: 0, standard: 1, complex: 2 };
183
+
184
+ // Verbs that denote a single, well-defined action. Presence (+ an explicit
185
+ // file path) is what earns 'high' confidence; absence (+ no path) is what
186
+ // earns 'low' confidence and triggers the safety clamp.
187
+ const UNAMBIGUOUS_VERB_RE =
188
+ /\b(fix(?:e[ds]|ing)?|renam(?:e[ds]?|ing)|add(?:ed|s|ing)?|remov(?:e[ds]?|ing)|delet(?:e[ds]?|ing)|updat(?:e[ds]?|ing)|creat(?:e[ds]?|ing)|implement(?:ed|s|ing)?|refactor(?:ed|s|ing)?|bump(?:ed|s|ing)?|patch(?:ed|es|ing)?|mov(?:e[ds]?|ing)|extract(?:ed|s|ing)?|replac(?:e[ds]?|ing)|revert(?:ed|s|ing)?|rewrit(?:e|es|ing)|rewrote)\b/i;
189
+
190
+ /**
191
+ * Map a triage tier (0-4) to a coarse lane.
192
+ *
193
+ * @param {number} tier
194
+ * @returns {'trivial'|'standard'|'complex'}
195
+ */
196
+ export function tierToLane(tier) {
197
+ if (tier <= 1) return 'trivial';
198
+ if (tier === 2) return 'standard';
199
+ return 'complex';
200
+ }
201
+
202
+ /**
203
+ * Clamp a lane up to at least `minimum` (never narrows).
204
+ *
205
+ * @param {string} lane
206
+ * @param {string} minimum
207
+ * @returns {string}
208
+ */
209
+ function clampLaneMin(lane, minimum) {
210
+ return LANE_ORDER[lane] < LANE_ORDER[minimum] ? minimum : lane;
211
+ }
212
+
213
+ /**
214
+ * Estimate scope from request text alone — the DOC-FREE front seam.
215
+ *
216
+ * Unlike `runTriage`, this never reads plan.md/blueprint.md/design.md (or
217
+ * any file at all). It derives the same `signals` shape `deriveProfile`
218
+ * expects purely from the request text and caller-supplied repo-signal
219
+ * hints, so it can run before any design doc exists — before the genesis
220
+ * phase, at feature-creation time.
221
+ *
222
+ * @param {string} request - Free-text feature/bug request description.
223
+ * @param {{ files?: string[] }} [repoSignals] - Candidate file paths the
224
+ * request plausibly touches (caller-supplied, e.g. from a git-diff or
225
+ * grep hint). Minimal shape: `{ files: string[] }`.
226
+ * @returns {{ tier: number, profile: object, lane: 'trivial'|'standard'|'complex', confidence: 'high'|'medium'|'low', rationale: string }}
227
+ */
228
+ // Reconcile the tier-derived profile with the (possibly clamped) lane so the two
229
+ // never disagree. The safety clamp raises the LANE, but skip_if is driven by the
230
+ // PROFILE — without this floor a clamped-to-standard lane would still skip
231
+ // verification, defeating the clamp. Standard keeps verification; complex keeps
232
+ // every phase; trivial is left as deriveProfile set it.
233
+ export function floorProfileToLane(profile, lane) {
234
+ if (lane === 'complex') {
235
+ return { needs_prd: true, needs_architecture: true, needs_verification: true, needs_report: true };
236
+ }
237
+ if (lane === 'standard') {
238
+ return { ...profile, needs_verification: true };
239
+ }
240
+ return { ...profile };
241
+ }
242
+
243
+ export function estimateScope(request, repoSignals = {}) {
244
+ const text = request ?? '';
245
+ const hintedFiles = Array.isArray(repoSignals?.files) ? repoSignals.files : [];
246
+
247
+ const textPaths = extractFilePaths(text);
248
+ const uniquePaths = new Set([...textPaths, ...hintedFiles]);
249
+ const pathList = [...uniquePaths];
250
+
251
+ const taskCount = countTasks(text);
252
+ const securityPaths = anyMatch(pathList, SECURITY_PATTERNS);
253
+ const corePaths = anyMatch(pathList, CORE_PATTERNS);
254
+
255
+ const signals = {
256
+ fileCount: uniquePaths.size,
257
+ securityPaths,
258
+ corePaths,
259
+ taskCount,
260
+ hasDesignDoc: false,
261
+ };
262
+
263
+ const { tier, profile, rationale } = deriveProfile(signals);
264
+
265
+ let lane = tierToLane(tier);
266
+ // Belt-and-suspenders: a security/core path hit must never resolve below
267
+ // 'standard', independent of how deriveProfile's own tier logic evolves.
268
+ if (securityPaths || corePaths) {
269
+ lane = clampLaneMin(lane, 'standard');
270
+ }
271
+
272
+ const hasPaths = uniquePaths.size > 0;
273
+ const hasUnambiguousVerb = UNAMBIGUOUS_VERB_RE.test(text);
274
+ let confidence;
275
+ if (hasPaths && hasUnambiguousVerb) {
276
+ confidence = 'high';
277
+ } else if (!hasPaths && !hasUnambiguousVerb) {
278
+ confidence = 'low';
279
+ } else {
280
+ confidence = 'medium';
281
+ }
282
+
283
+ // Safety clamp: under-scoping is the only dangerous error. Low confidence
284
+ // (no named paths, no clear verb) can never resolve to 'trivial'.
285
+ if (confidence === 'low') {
286
+ lane = clampLaneMin(lane, 'standard');
287
+ }
288
+
289
+ return { tier, profile: floorProfileToLane(profile, lane), lane, confidence, rationale };
290
+ }
291
+
292
+ /**
293
+ * Map a triage tier to a persisted complexity label.
294
+ *
295
+ * @param {number} tier - 0-4
296
+ * @returns {'S'|'M'|'L'|'XL'}
297
+ */
298
+ export function tierToComplexity(tier) {
299
+ if (tier <= 1) return 'S';
300
+ if (tier === 2) return 'M';
301
+ if (tier === 3) return 'L';
302
+ return 'XL';
303
+ }
304
+
305
+ /**
306
+ * Return the more conservative (smaller-scope) of two lanes.
307
+ * Order: trivial < standard < complex.
308
+ *
309
+ * Used by refinement/escalation call sites to enforce "narrow-only" — a
310
+ * later pass may only shrink scope, never widen it (widening is
311
+ * escalation's job, via `lib/escalation.js`, not refinement's).
312
+ *
313
+ * @param {'trivial'|'standard'|'complex'} a
314
+ * @param {'trivial'|'standard'|'complex'} b
315
+ * @returns {'trivial'|'standard'|'complex'}
316
+ */
317
+ export function narrowerLane(a, b) {
318
+ return LANE_ORDER[a] <= LANE_ORDER[b] ? a : b;
319
+ }
320
+
177
321
  // ---------------------------------------------------------------------------
178
322
  // Public API
179
323
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartmemory/compose",
3
- "version": "0.2.57-beta",
3
+ "version": "0.2.58-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",
@@ -1 +0,0 @@
1
- import{ag as o,ah as n}from"./App-BmhlHOXF.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-V7JOEXUC-DrPIj6XI.js";import{_ as i}from"./App-BmhlHOXF.js";import"./chunk-5VM5RSS4-DTdG5JnR.js";import"./chunk-XXDRQBXY-BEIiZ6gS.js";import"./chunk-VR4S4FIN-BBUOVC5W.js";import"./chunk-32BRIVSS-DpImULWy.js";import"./mobile-PV1zQVwv.js";import"./index-CKfOVv2N.js";import"./graph--wGAvskB.js";var b={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{b as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-V7JOEXUC-DrPIj6XI.js";import{_ as i}from"./App-BmhlHOXF.js";import"./chunk-5VM5RSS4-DTdG5JnR.js";import"./chunk-XXDRQBXY-BEIiZ6gS.js";import"./chunk-VR4S4FIN-BBUOVC5W.js";import"./chunk-32BRIVSS-DpImULWy.js";import"./mobile-PV1zQVwv.js";import"./index-CKfOVv2N.js";import"./graph--wGAvskB.js";var b={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{b as diagram};
@@ -1 +0,0 @@
1
- import{s as r,b as e,a,S as s}from"./chunk-EX3LRPZG-Cj1jNuKY.js";import{_ as i}from"./App-BmhlHOXF.js";import"./chunk-XXDRQBXY-BEIiZ6gS.js";import"./chunk-VR4S4FIN-BBUOVC5W.js";import"./chunk-32BRIVSS-DpImULWy.js";import"./mobile-PV1zQVwv.js";import"./index-CKfOVv2N.js";import"./graph--wGAvskB.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};
@@ -1,8 +0,0 @@
1
- import{c as r,s as e}from"./flowDiagram-23GEKE2U-C_sotK-k.js";import{_ as a}from"./App-BmhlHOXF.js";import"./chunk-5VM5RSS4-DTdG5JnR.js";import"./chunk-XXDRQBXY-BEIiZ6gS.js";import"./chunk-VR4S4FIN-BBUOVC5W.js";import"./chunk-32BRIVSS-DpImULWy.js";import"./channel-CYErfopw.js";import"./mobile-PV1zQVwv.js";import"./index-CKfOVv2N.js";import"./graph--wGAvskB.js";var o=a(t=>`${e(t)}
2
- .swimlane.cluster rect {
3
- stroke: ${t.clusterBorder} !important;
4
- }
5
- [data-look="neo"].cluster rect {
6
- filter: none;
7
- }
8
- `,"getStyles"),m=o,_=r({defaultLayout:"swimlane",styles:m});export{_ as diagram};