@codeledger/cli 0.6.9 → 0.7.0

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 (99) hide show
  1. package/dist/commands/activate.d.ts.map +1 -1
  2. package/dist/commands/activate.js +79 -26
  3. package/dist/commands/activate.js.map +1 -1
  4. package/dist/commands/ci.d.ts +16 -0
  5. package/dist/commands/ci.d.ts.map +1 -0
  6. package/dist/commands/ci.js +323 -0
  7. package/dist/commands/ci.js.map +1 -0
  8. package/dist/commands/compare.d.ts.map +1 -1
  9. package/dist/commands/compare.js +8 -0
  10. package/dist/commands/compare.js.map +1 -1
  11. package/dist/commands/context.d.ts +17 -0
  12. package/dist/commands/context.d.ts.map +1 -0
  13. package/dist/commands/context.js +687 -0
  14. package/dist/commands/context.js.map +1 -0
  15. package/dist/commands/doctor.d.ts.map +1 -1
  16. package/dist/commands/doctor.js +65 -0
  17. package/dist/commands/doctor.js.map +1 -1
  18. package/dist/commands/enable.d.ts +7 -0
  19. package/dist/commands/enable.d.ts.map +1 -0
  20. package/dist/commands/enable.js +75 -0
  21. package/dist/commands/enable.js.map +1 -0
  22. package/dist/commands/features.d.ts +2 -0
  23. package/dist/commands/features.d.ts.map +1 -0
  24. package/dist/commands/features.js +182 -0
  25. package/dist/commands/features.js.map +1 -0
  26. package/dist/commands/init.d.ts.map +1 -1
  27. package/dist/commands/init.js +8 -1
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/intent.d.ts +4 -4
  30. package/dist/commands/intent.d.ts.map +1 -1
  31. package/dist/commands/intent.js +70 -5
  32. package/dist/commands/intent.js.map +1 -1
  33. package/dist/commands/ledger.d.ts +14 -0
  34. package/dist/commands/ledger.d.ts.map +1 -0
  35. package/dist/commands/ledger.js +128 -0
  36. package/dist/commands/ledger.js.map +1 -0
  37. package/dist/commands/license.d.ts +45 -0
  38. package/dist/commands/license.d.ts.map +1 -0
  39. package/dist/commands/license.js +240 -0
  40. package/dist/commands/license.js.map +1 -0
  41. package/dist/commands/orchestrate.d.ts +12 -0
  42. package/dist/commands/orchestrate.d.ts.map +1 -0
  43. package/dist/commands/orchestrate.js +119 -0
  44. package/dist/commands/orchestrate.js.map +1 -0
  45. package/dist/commands/policy-simulate.d.ts +9 -0
  46. package/dist/commands/policy-simulate.d.ts.map +1 -0
  47. package/dist/commands/policy-simulate.js +46 -0
  48. package/dist/commands/policy-simulate.js.map +1 -0
  49. package/dist/commands/provenance.d.ts +10 -0
  50. package/dist/commands/provenance.d.ts.map +1 -0
  51. package/dist/commands/provenance.js +87 -0
  52. package/dist/commands/provenance.js.map +1 -0
  53. package/dist/commands/replay.d.ts +2 -0
  54. package/dist/commands/replay.d.ts.map +1 -0
  55. package/dist/commands/replay.js +181 -0
  56. package/dist/commands/replay.js.map +1 -0
  57. package/dist/commands/serve.d.ts +3 -0
  58. package/dist/commands/serve.d.ts.map +1 -1
  59. package/dist/commands/serve.js +73 -1
  60. package/dist/commands/serve.js.map +1 -1
  61. package/dist/commands/session-summary.d.ts.map +1 -1
  62. package/dist/commands/session-summary.js +47 -2
  63. package/dist/commands/session-summary.js.map +1 -1
  64. package/dist/commands/setup-ci.d.ts +17 -1
  65. package/dist/commands/setup-ci.d.ts.map +1 -1
  66. package/dist/commands/setup-ci.js +190 -51
  67. package/dist/commands/setup-ci.js.map +1 -1
  68. package/dist/commands/share.js +1 -1
  69. package/dist/commands/share.js.map +1 -1
  70. package/dist/commands/stats.d.ts +8 -0
  71. package/dist/commands/stats.d.ts.map +1 -0
  72. package/dist/commands/stats.js +164 -0
  73. package/dist/commands/stats.js.map +1 -0
  74. package/dist/commands/team-ledger.d.ts +11 -0
  75. package/dist/commands/team-ledger.d.ts.map +1 -0
  76. package/dist/commands/team-ledger.js +74 -0
  77. package/dist/commands/team-ledger.js.map +1 -0
  78. package/dist/commands/team-metrics.d.ts +8 -0
  79. package/dist/commands/team-metrics.d.ts.map +1 -0
  80. package/dist/commands/team-metrics.js +57 -0
  81. package/dist/commands/team-metrics.js.map +1 -0
  82. package/dist/commands/upgrade.d.ts +6 -0
  83. package/dist/commands/upgrade.d.ts.map +1 -0
  84. package/dist/commands/upgrade.js +39 -0
  85. package/dist/commands/upgrade.js.map +1 -0
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +241 -65
  88. package/dist/index.js.map +1 -1
  89. package/dist/templates/claude-md.d.ts.map +1 -1
  90. package/dist/templates/claude-md.js +14 -4
  91. package/dist/templates/claude-md.js.map +1 -1
  92. package/dist/templates/config.js +4 -4
  93. package/dist/templates/config.js.map +1 -1
  94. package/dist/templates/hooks.d.ts +3 -1
  95. package/dist/templates/hooks.d.ts.map +1 -1
  96. package/dist/templates/hooks.js +62 -2
  97. package/dist/templates/hooks.js.map +1 -1
  98. package/package.json +11 -10
  99. package/LICENSE +0 -27
@@ -0,0 +1,687 @@
1
+ /**
2
+ * context — DCOL v2 context introspection subcommand
3
+ *
4
+ * Subcommands:
5
+ * explain Why each file/symbol was selected
6
+ * diff Show context change between two bundle iterations
7
+ * graph Output Mermaid dependency diagram
8
+ * validate Run execution-aware context validation
9
+ *
10
+ * Usage:
11
+ * codeledger context explain [--bundle <path>] [--json]
12
+ * codeledger context diff [--from <bundle-id>] [--to <bundle-id>] [--json]
13
+ * codeledger context graph [--bundle <path>] [--output <path>]
14
+ * codeledger context validate [--bundle <path>] [--json]
15
+ */
16
+ import { readFileSync, existsSync, writeFileSync, readdirSync, statSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { computeCcs, loadEntries, historicalSuccessContext, checkIntentSufficiency } from '@codeledger/engine';
19
+ // ─── Entry Point ─────────────────────────────────────────────────────────────
20
+ export async function runContext(cwd, flags) {
21
+ const sub = flags['_sub'] ?? 'explain';
22
+ switch (sub) {
23
+ case 'explain':
24
+ return runContextExplain(cwd, flags);
25
+ case 'diff':
26
+ return runContextDiff(cwd, flags);
27
+ case 'graph':
28
+ return runContextGraph(cwd, flags);
29
+ case 'validate':
30
+ return runContextValidate(cwd, flags);
31
+ default:
32
+ console.error(`❌ Unknown context subcommand: ${sub}`);
33
+ console.error(` Valid: explain | diff | graph | validate`);
34
+ process.exit(1);
35
+ }
36
+ }
37
+ // ─── context explain ─────────────────────────────────────────────────────────
38
+ async function runContextExplain(cwd, flags) {
39
+ const bundle = loadBundle(cwd, flags['bundle']);
40
+ if (!bundle)
41
+ return;
42
+ const jsonMode = flags['json'] === 'true';
43
+ if (jsonMode) {
44
+ console.log(JSON.stringify(buildExplainPayload(bundle), null, 2));
45
+ return;
46
+ }
47
+ console.log(`\n📋 Context Explanation — Bundle ${bundle.bundle_id}\n`);
48
+ console.log(` Task: ${bundle.task}`);
49
+ console.log(` Files: ${bundle.files.length} Tokens: ${bundle.total_tokens.toLocaleString()}`);
50
+ if (bundle.confidence) {
51
+ const c = bundle.confidence;
52
+ console.log(` Confidence: ${c.level.toUpperCase()} (${(c.score * 100).toFixed(0)}%)`);
53
+ }
54
+ console.log('');
55
+ for (const file of bundle.files) {
56
+ const reasons = file.reasons.join(', ');
57
+ const score = file.score.toFixed(3);
58
+ const tokens = file.token_estimate?.toLocaleString() ?? '—';
59
+ const stub = file.is_stub ? ' [stub]' : '';
60
+ const exemplar = file.is_exemplar ? ' [exemplar]' : '';
61
+ const shadow = file.shadow_reason ? ` [shadow affinity=${file.shadow_reason.affinity.toFixed(2)}]` : '';
62
+ console.log(` ${score} ${file.path}${stub}${exemplar}${shadow}`);
63
+ console.log(` reasons: ${reasons} tokens: ${tokens}`);
64
+ // Feature breakdown (if explain data present)
65
+ if (bundle.explain?.[file.path]) {
66
+ const f = bundle.explain[file.path];
67
+ const parts = [];
68
+ if (f.keyword > 0)
69
+ parts.push(`keyword=${f.keyword.toFixed(2)}`);
70
+ if (f.centrality > 0)
71
+ parts.push(`centrality=${f.centrality.toFixed(2)}`);
72
+ if (f.churn > 0)
73
+ parts.push(`churn=${f.churn.toFixed(2)}`);
74
+ if (f.recent_touch > 0)
75
+ parts.push(`recent_touch=${f.recent_touch.toFixed(2)}`);
76
+ if (f.test_relevance > 0)
77
+ parts.push(`test=${f.test_relevance.toFixed(2)}`);
78
+ if (f.error_infrastructure > 0)
79
+ parts.push(`error_infra=${f.error_infrastructure.toFixed(2)}`);
80
+ if (f.branch_changed > 0)
81
+ parts.push(`branch=${f.branch_changed.toFixed(2)}`);
82
+ if (f.security_surface > 0)
83
+ parts.push(`security=${f.security_surface.toFixed(2)}`);
84
+ if (parts.length > 0) {
85
+ console.log(` features: ${parts.join(' ')}`);
86
+ }
87
+ }
88
+ console.log('');
89
+ }
90
+ if (bundle.near_misses && bundle.near_misses.length > 0) {
91
+ console.log(` Near-misses (files that almost made the cut):`);
92
+ for (const nm of bundle.near_misses.slice(0, 5)) {
93
+ console.log(` ${nm.score.toFixed(3)} ${nm.path} [gap: ${nm.budget_gap_tokens} tokens]`);
94
+ if (nm.suggestion)
95
+ console.log(` → ${nm.suggestion}`);
96
+ }
97
+ console.log('');
98
+ }
99
+ }
100
+ function buildExplainPayload(bundle) {
101
+ return {
102
+ bundle_id: bundle.bundle_id,
103
+ task: bundle.task,
104
+ total_files: bundle.files.length,
105
+ total_tokens: bundle.total_tokens,
106
+ confidence: bundle.confidence,
107
+ files: bundle.files.map((f) => ({
108
+ path: f.path,
109
+ score: f.score,
110
+ reasons: f.reasons,
111
+ token_estimate: f.token_estimate,
112
+ is_stub: f.is_stub,
113
+ is_exemplar: f.is_exemplar,
114
+ features: bundle.explain?.[f.path],
115
+ shadow_reason: f.shadow_reason,
116
+ })),
117
+ near_misses: bundle.near_misses,
118
+ };
119
+ }
120
+ // ─── context diff ─────────────────────────────────────────────────────────────
121
+ async function runContextDiff(cwd, flags) {
122
+ const jsonMode = flags['json'] === 'true';
123
+ // Load two bundles for comparison
124
+ const fromId = flags['from'];
125
+ const toId = flags['to'];
126
+ const artifactsDir = join(cwd, '.codeledger', 'artifacts');
127
+ const bundlesDir = join(artifactsDir, 'bundles');
128
+ let fromBundle = null;
129
+ let toBundle = null;
130
+ if (fromId) {
131
+ // Try bundles/ subdirectory first, then artifacts/ root
132
+ const p1 = join(bundlesDir, `${fromId}.json`);
133
+ const p2 = join(artifactsDir, `${fromId}.json`);
134
+ const p = existsSync(p1) ? p1 : p2;
135
+ fromBundle = existsSync(p)
136
+ ? JSON.parse(readFileSync(p, 'utf-8'))
137
+ : null;
138
+ }
139
+ // Default: compare previous vs current active bundle
140
+ const activeBundlePath = join(cwd, '.codeledger', 'active-bundle.md');
141
+ // Look in bundles/ subdirectory first, fall back to artifacts/ root
142
+ const bundlesDirResult = loadLatestBundles(bundlesDir, 2);
143
+ const bundleArtifacts = bundlesDirResult.length >= 2
144
+ ? bundlesDirResult
145
+ : loadLatestBundles(artifactsDir, 2);
146
+ if (!fromBundle && bundleArtifacts.length >= 2) {
147
+ fromBundle = bundleArtifacts[bundleArtifacts.length - 2] ?? null;
148
+ }
149
+ if (!toBundle) {
150
+ if (toId) {
151
+ const p1 = join(bundlesDir, `${toId}.json`);
152
+ const p2 = join(artifactsDir, `${toId}.json`);
153
+ const p = existsSync(p1) ? p1 : p2;
154
+ toBundle = existsSync(p)
155
+ ? JSON.parse(readFileSync(p, 'utf-8'))
156
+ : null;
157
+ }
158
+ else {
159
+ toBundle = bundleArtifacts[bundleArtifacts.length - 1] ?? null;
160
+ }
161
+ }
162
+ if (!fromBundle || !toBundle) {
163
+ console.error('❌ Need two bundles to diff. Use --from <id> --to <id> or generate 2+ bundles first.');
164
+ if (!existsSync(activeBundlePath)) {
165
+ console.error(' No active bundle found. Run: codeledger activate --task "..."');
166
+ }
167
+ process.exit(1);
168
+ }
169
+ const diff = computeContextDiff(fromBundle, toBundle);
170
+ if (jsonMode) {
171
+ console.log(JSON.stringify(diff, null, 2));
172
+ return;
173
+ }
174
+ console.log(`\n📊 Context Diff\n`);
175
+ console.log(` From: ${diff.from_bundle_id} → To: ${diff.to_bundle_id}`);
176
+ const sign = diff.token_delta >= 0 ? '+' : '';
177
+ console.log(` Token delta: ${sign}${diff.token_delta.toLocaleString()}\n`);
178
+ if (diff.added.length > 0) {
179
+ console.log(` ✅ Added to context (${diff.added.length}):`);
180
+ for (const f of diff.added)
181
+ console.log(` + ${f}`);
182
+ console.log('');
183
+ }
184
+ if (diff.removed.length > 0) {
185
+ console.log(` ❌ Removed from context (${diff.removed.length}):`);
186
+ for (const f of diff.removed)
187
+ console.log(` - ${f}`);
188
+ console.log('');
189
+ }
190
+ if (diff.score_changed.length > 0) {
191
+ console.log(` 📈 Score changed (${diff.score_changed.length}):`);
192
+ for (const sc of diff.score_changed) {
193
+ const delta = sc.to_score - sc.from_score;
194
+ const sign2 = delta >= 0 ? '+' : '';
195
+ console.log(` ~ ${sc.path} ${sc.from_score.toFixed(3)} → ${sc.to_score.toFixed(3)} (${sign2}${delta.toFixed(3)})`);
196
+ }
197
+ console.log('');
198
+ }
199
+ if (diff.added.length === 0 && diff.removed.length === 0 && diff.score_changed.length === 0) {
200
+ console.log(' ✓ No changes between bundles.');
201
+ }
202
+ }
203
+ function computeContextDiff(from, to) {
204
+ const fromPaths = new Set(from.files.map((f) => f.path));
205
+ const toPaths = new Set(to.files.map((f) => f.path));
206
+ const fromScores = new Map(from.files.map((f) => [f.path, f.score]));
207
+ const toScores = new Map(to.files.map((f) => [f.path, f.score]));
208
+ const added = to.files.filter((f) => !fromPaths.has(f.path)).map((f) => f.path);
209
+ const removed = from.files.filter((f) => !toPaths.has(f.path)).map((f) => f.path);
210
+ const scoreChanged = [];
211
+ for (const path of fromPaths) {
212
+ if (toPaths.has(path)) {
213
+ const fromScore = fromScores.get(path) ?? 0;
214
+ const toScore = toScores.get(path) ?? 0;
215
+ if (Math.abs(toScore - fromScore) > 0.005) {
216
+ scoreChanged.push({ path, from_score: fromScore, to_score: toScore });
217
+ }
218
+ }
219
+ }
220
+ scoreChanged.sort((a, b) => Math.abs(b.to_score - b.from_score) - Math.abs(a.to_score - a.from_score));
221
+ return {
222
+ from_bundle_id: from.bundle_id,
223
+ to_bundle_id: to.bundle_id,
224
+ added,
225
+ removed,
226
+ score_changed: scoreChanged,
227
+ token_delta: to.total_tokens - from.total_tokens,
228
+ generated_at: new Date().toISOString(),
229
+ };
230
+ }
231
+ // ─── context graph ────────────────────────────────────────────────────────────
232
+ async function runContextGraph(cwd, flags) {
233
+ const bundle = loadBundle(cwd, flags['bundle']);
234
+ if (!bundle)
235
+ return;
236
+ const outputPath = flags['output'];
237
+ const graph = buildMermaidGraph(bundle, cwd);
238
+ if (outputPath) {
239
+ writeFileSync(outputPath, graph.markup, 'utf-8');
240
+ console.log(`✅ Mermaid diagram written to: ${outputPath}`);
241
+ console.log(` Nodes: ${graph.included_files.length} files`);
242
+ return;
243
+ }
244
+ console.log(`\n🗂 Context Dependency Graph — Bundle ${bundle.bundle_id}\n`);
245
+ console.log('```mermaid');
246
+ console.log(graph.markup);
247
+ console.log('```');
248
+ console.log(`\n Files: ${graph.included_files.length}`);
249
+ console.log(' Paste the above into https://mermaid.live to visualize.\n');
250
+ }
251
+ function buildMermaidGraph(bundle, _cwd) {
252
+ const files = bundle.files.map((f) => f.path);
253
+ const relationships = bundle.file_relationships ?? [];
254
+ // Shorten paths for readability
255
+ const shortName = (p) => {
256
+ const parts = p.split('/');
257
+ return parts.slice(-2).join('/').replace(/\.[tj]sx?$/, '');
258
+ };
259
+ // Assign layers
260
+ const layerOf = (p) => {
261
+ if (/\.(test|spec)\.[tj]sx?$/.test(p))
262
+ return 'tests';
263
+ if (/\/types?(\/|$)/.test(p) || p.endsWith('.d.ts'))
264
+ return 'types';
265
+ if (/\/models?(\/|$)/.test(p) || /\.(schema|entity|model)\.[tj]sx?$/.test(p))
266
+ return 'models';
267
+ if (/\/routes?(\/|$)/.test(p) || /\.(route|handler|controller)\.[tj]sx?$/.test(p))
268
+ return 'routes';
269
+ if (/\/services?(\/|$)/.test(p) || /\.service\.[tj]sx?$/.test(p))
270
+ return 'services';
271
+ if (/\/config(\/|$)/.test(p) || /\.(config|env)\.[tj]sx?$/.test(p))
272
+ return 'config';
273
+ return 'unknown';
274
+ };
275
+ // Group files by layer for subgraph
276
+ const byLayer = new Map();
277
+ for (const f of files) {
278
+ const layer = layerOf(f);
279
+ const existing = byLayer.get(layer) ?? [];
280
+ existing.push(f);
281
+ byLayer.set(layer, existing);
282
+ }
283
+ // Build node ID map (sanitize for Mermaid)
284
+ const nodeId = (p) => p.replace(/[^a-zA-Z0-9]/g, '_').replace(/__+/g, '_');
285
+ const lines = ['flowchart TD'];
286
+ // Subgraphs per layer
287
+ const layerOrder = ['types', 'models', 'services', 'routes', 'tests', 'config', 'unknown'];
288
+ for (const layer of layerOrder) {
289
+ const layerFiles = byLayer.get(layer);
290
+ if (!layerFiles || layerFiles.length === 0)
291
+ continue;
292
+ lines.push(` subgraph ${layer}`);
293
+ for (const f of layerFiles) {
294
+ const id = nodeId(f);
295
+ const label = shortName(f);
296
+ // High-score files get a highlighted style
297
+ const file = bundle.files.find((bf) => bf.path === f);
298
+ const style = (file?.score ?? 0) >= 0.7 ? `["🔥 ${label}"]` : `["${label}"]`;
299
+ lines.push(` ${id}${style}`);
300
+ }
301
+ lines.push(' end');
302
+ }
303
+ // Edges from file_relationships
304
+ const fileSet = new Set(files);
305
+ for (const rel of relationships) {
306
+ if (fileSet.has(rel.from) && fileSet.has(rel.to)) {
307
+ lines.push(` ${nodeId(rel.from)} --> ${nodeId(rel.to)}`);
308
+ }
309
+ }
310
+ return {
311
+ diagram_type: 'flowchart',
312
+ markup: lines.join('\n'),
313
+ included_files: files,
314
+ generated_at: new Date().toISOString(),
315
+ };
316
+ }
317
+ // ─── context validate ─────────────────────────────────────────────────────────
318
+ async function runContextValidate(cwd, flags) {
319
+ const bundle = loadBundle(cwd, flags['bundle']);
320
+ if (!bundle)
321
+ return;
322
+ const jsonMode = flags['json'] === 'true';
323
+ const forceMode = flags['force'] === 'true';
324
+ // ── ISC — Intent Sufficiency Check (PRE-CONTEXT gate) ─────────────────────
325
+ const taskText = flags['task'] ?? bundle.task ?? '';
326
+ const isc = checkIntentSufficiency(taskText);
327
+ const iscBlocked = isc.decision === 'INSUFFICIENT' && !forceMode;
328
+ if (jsonMode) {
329
+ if (iscBlocked) {
330
+ console.log(JSON.stringify({ isc, ccs: null, validation: null, forced: false }, null, 2));
331
+ process.exit(1);
332
+ return;
333
+ }
334
+ }
335
+ else {
336
+ printIscSummary(isc, forceMode);
337
+ if (iscBlocked) {
338
+ process.exit(1);
339
+ return;
340
+ }
341
+ }
342
+ // ── Structural validation ─────────────────────────────────────────────────
343
+ const result = validateContextBundle(bundle, cwd);
344
+ // ── CCS — Context Confidence Score ────────────────────────────────────────
345
+ const taskKeywords = extractTaskKeywords(bundle, taskText);
346
+ const expansionLevel = parseInt(flags['expansion-level'] ?? '1', 10);
347
+ const ledgerEntries = loadEntries(cwd);
348
+ const intentSig = bundle.bundle_intent_hash ?? '';
349
+ const { rate: histRate, sampleSize } = historicalSuccessContext(ledgerEntries, intentSig);
350
+ // WEAK ISC applies a -0.05 CCS penalty via intentConfidence
351
+ const iscIntentConfidence = isc.decision === 'WEAK' ? Math.max(0, isc.score - 0.05) : isc.score;
352
+ const ccs = computeCcs({
353
+ bundle,
354
+ taskKeywords,
355
+ expansionLevel: isNaN(expansionLevel) ? 1 : expansionLevel,
356
+ historicalSuccessRate: histRate,
357
+ historySampleSize: sampleSize,
358
+ intentConfidence: iscIntentConfidence,
359
+ });
360
+ // Determine final gate decision (--force overrides BLOCK)
361
+ const blocked = !result.passed || ccs.recommendation === 'block';
362
+ const shouldExit = blocked && !forceMode;
363
+ if (jsonMode) {
364
+ console.log(JSON.stringify({ isc, validation: result, ccs, forced: forceMode && blocked }, null, 2));
365
+ if (shouldExit)
366
+ process.exit(1);
367
+ return;
368
+ }
369
+ const validIcon = result.passed ? '✅' : '❌';
370
+ console.log(`\n${validIcon} Context Validation — Bundle ${bundle.bundle_id}\n`);
371
+ // ── CCS Summary ────────────────────────────────────────────────────────────
372
+ printCcsSummary(ccs, forceMode);
373
+ // ── Validation Issues ──────────────────────────────────────────────────────
374
+ if (result.issues.length === 0) {
375
+ console.log(' No structural issues found.\n');
376
+ }
377
+ else {
378
+ const errors = result.issues.filter((i) => i.severity === 'error');
379
+ const warnings = result.issues.filter((i) => i.severity === 'warning');
380
+ const infos = result.issues.filter((i) => i.severity === 'info');
381
+ if (errors.length > 0) {
382
+ console.log(` Errors (${errors.length}):`);
383
+ for (const e of errors)
384
+ printIssue(e, ' ❌');
385
+ }
386
+ if (warnings.length > 0) {
387
+ console.log(` Warnings (${warnings.length}):`);
388
+ for (const w of warnings)
389
+ printIssue(w, ' ⚠️');
390
+ }
391
+ if (infos.length > 0) {
392
+ console.log(` Info (${infos.length}):`);
393
+ for (const i of infos)
394
+ printIssue(i, ' ℹ️');
395
+ }
396
+ console.log('');
397
+ }
398
+ if (result.missing_dependencies.length > 0) {
399
+ console.log(` Missing dependencies (files not in context):`);
400
+ for (const d of result.missing_dependencies)
401
+ console.log(` • ${d}`);
402
+ console.log('');
403
+ }
404
+ if (result.uncovered_files.length > 0) {
405
+ console.log(` Files without test coverage:`);
406
+ for (const f of result.uncovered_files)
407
+ console.log(` • ${f}`);
408
+ console.log('');
409
+ }
410
+ if (result.boundary_violations.length > 0) {
411
+ console.log(` Architecture boundary violations:`);
412
+ for (const v of result.boundary_violations) {
413
+ console.log(` ${v.from} → ${v.to}: ${v.reason}`);
414
+ }
415
+ console.log('');
416
+ }
417
+ if (shouldExit)
418
+ process.exit(1);
419
+ }
420
+ function printIscSummary(isc, forced = false) {
421
+ const icon = isc.decision === 'SUFFICIENT'
422
+ ? '🟢'
423
+ : isc.decision === 'WEAK'
424
+ ? '🟡'
425
+ : forced
426
+ ? '🔴'
427
+ : '🔴';
428
+ const pct = (isc.score * 100).toFixed(0);
429
+ const decisionLabel = isc.decision === 'INSUFFICIENT' && forced
430
+ ? 'INSUFFICIENT (overridden by --force)'
431
+ : isc.decision;
432
+ console.log(` ── Intent Sufficiency Check (ISC) ───────────────────────`);
433
+ console.log(` ${icon} Score: ${pct}% Decision: ${decisionLabel}`);
434
+ const f = isc.factors;
435
+ console.log(` token signal : ${(f.tokenSignalScore * 100).toFixed(0)}%` +
436
+ (isc.detectedOperation ? ` op: ${isc.detectedOperation}` : ''));
437
+ console.log(` operation clarity: ${(f.operationClarityScore * 100).toFixed(0)}%` +
438
+ (isc.detectedDomain ? ` domain: ${isc.detectedDomain}` : ''));
439
+ console.log(` domain clarity : ${(f.domainClarityScore * 100).toFixed(0)}%`);
440
+ console.log(` target specificity: ${(f.targetSpecificityScore * 100).toFixed(0)}%` +
441
+ (isc.detectedTargets && isc.detectedTargets.length > 0
442
+ ? ` targets: ${isc.detectedTargets.slice(0, 3).join(', ')}`
443
+ : ''));
444
+ console.log(` constraint present: ${(f.constraintPresenceScore * 100).toFixed(0)}%`);
445
+ if (isc.issues.length > 0) {
446
+ console.log(`\n ISC issues:`);
447
+ for (const issue of isc.issues)
448
+ console.log(` • ${issue}`);
449
+ }
450
+ if (isc.decision === 'INSUFFICIENT' && !forced) {
451
+ console.log(`\n Context construction blocked. Improve your task prompt:`);
452
+ for (const rec of isc.recommendations)
453
+ console.log(` → ${rec}`);
454
+ console.log(`\n Use --force to bypass this gate.\n`);
455
+ }
456
+ else if (isc.decision === 'WEAK') {
457
+ console.log(`\n Weak intent: CCS receives a -0.05 penalty. Consider refining your prompt:`);
458
+ for (const rec of isc.recommendations)
459
+ console.log(` → ${rec}`);
460
+ }
461
+ console.log('');
462
+ }
463
+ function printCcsSummary(ccs, forced = false) {
464
+ const pct = (ccs.score * 100).toFixed(0);
465
+ const levelIcon = ccs.level === 'high' ? '🟢' : ccs.level === 'medium' ? '🟡' : '🔴';
466
+ const decisionLabel = {
467
+ proceed: 'PROCEED',
468
+ expand: `EXPAND → suggest Level ${ccs.suggestedExpansionLevel ?? '?'}`,
469
+ block: forced ? 'BLOCK (overridden by --force)' : 'BLOCK',
470
+ };
471
+ console.log(` ── Context Confidence Score (CCS) ──────────────────────`);
472
+ console.log(` ${levelIcon} Score: ${pct}% Decision: ${decisionLabel[ccs.recommendation] ?? ccs.recommendation.toUpperCase()}`);
473
+ console.log(` dep coverage : ${(ccs.factors.dependencyCoverage * 100).toFixed(0)}%`);
474
+ console.log(` test coverage : ${(ccs.factors.testCoverageMapping * 100).toFixed(0)}%`);
475
+ // Historical confidence: show sample size + effective weight
476
+ const histPct = (ccs.factors.historicalSuccessRate * 100).toFixed(0);
477
+ const weightPct = (ccs.factors.adjustedHistoricalWeight * 100 / 0.20).toFixed(0); // as % of max
478
+ const sampleLabel = ccs.factors.historySampleSize === 0
479
+ ? 'no data'
480
+ : `${ccs.factors.historySampleSize} entries, weight ${weightPct}% of full`;
481
+ console.log(` hist success : ${histPct}% (${sampleLabel})`);
482
+ console.log(` symbol comp. : ${(ccs.factors.symbolCompleteness * 100).toFixed(0)}%`);
483
+ console.log(` expansion eff.: ${(100 - ccs.factors.expansionLevelPenalty * 100 / 0.3).toFixed(0)}%`);
484
+ if (ccs.issues.length > 0) {
485
+ console.log(`\n CCS issues:`);
486
+ for (const issue of ccs.issues)
487
+ console.log(` • ${issue}`);
488
+ }
489
+ console.log('');
490
+ }
491
+ /** Extract task keywords from bundle task description or explicit --task flag. */
492
+ function extractTaskKeywords(bundle, taskOverride) {
493
+ const raw = taskOverride || bundle.task || '';
494
+ if (!raw)
495
+ return [];
496
+ // Simple tokenization: split on whitespace, strip punctuation, keep ≥3 chars
497
+ return raw
498
+ .toLowerCase()
499
+ .split(/\s+/)
500
+ .map((t) => t.replace(/[^a-z0-9_-]/g, ''))
501
+ .filter((t) => t.length >= 3);
502
+ }
503
+ function printIssue(issue, prefix) {
504
+ const location = issue.file ? ` [${issue.file}${issue.symbol ? `::${issue.symbol}` : ''}]` : '';
505
+ console.log(` ${prefix} [${issue.code}]${location}: ${issue.message}`);
506
+ }
507
+ function validateContextBundle(bundle, cwd) {
508
+ const issues = [];
509
+ const missingDependencies = [];
510
+ const uncoveredFiles = [];
511
+ const boundaryViolations = [];
512
+ const bundlePaths = new Set(bundle.files.map((f) => f.path));
513
+ // 1. Check for missing dependencies (imported files not in bundle)
514
+ if (bundle.file_relationships) {
515
+ for (const rel of bundle.file_relationships) {
516
+ if (!bundlePaths.has(rel.to)) {
517
+ missingDependencies.push(rel.to);
518
+ issues.push({
519
+ code: 'MISSING_DEP',
520
+ severity: 'warning',
521
+ message: `Dependency ${rel.to} is imported but not in context`,
522
+ file: rel.from,
523
+ });
524
+ }
525
+ }
526
+ }
527
+ // 2. Check for files without test coverage
528
+ const testMap = bundle.files.filter((f) => f.path.match(/\.(test|spec)\.[tj]sx?$/));
529
+ const testedSources = new Set();
530
+ for (const t of testMap) {
531
+ // Infer source from test file name
532
+ const source = t.path.replace(/\.(test|spec)\./, '.').replace(/\/__tests__\//, '/');
533
+ testedSources.add(source);
534
+ }
535
+ for (const f of bundle.files) {
536
+ if (!f.path.match(/\.(test|spec)\.[tj]sx?$/) && !f.is_stub && !f.is_exemplar) {
537
+ if (!testedSources.has(f.path) && !f.reasons.includes('test_relevant')) {
538
+ uncoveredFiles.push(f.path);
539
+ }
540
+ }
541
+ }
542
+ if (uncoveredFiles.length > bundle.files.length * 0.5) {
543
+ issues.push({
544
+ code: 'LOW_TEST_COVERAGE',
545
+ severity: 'warning',
546
+ message: `${uncoveredFiles.length}/${bundle.files.length} context files have no linked tests`,
547
+ });
548
+ }
549
+ // 3. Check token budget
550
+ const maxTokens = 16000;
551
+ if (bundle.total_tokens > maxTokens) {
552
+ issues.push({
553
+ code: 'BUDGET_EXCEEDED',
554
+ severity: 'warning',
555
+ message: `Context is ${bundle.total_tokens.toLocaleString()} tokens (recommend ≤ ${maxTokens.toLocaleString()})`,
556
+ });
557
+ }
558
+ // 4. Check confidence (if available)
559
+ if (bundle.confidence && bundle.confidence.level === 'low') {
560
+ issues.push({
561
+ code: 'LOW_CONFIDENCE',
562
+ severity: 'warning',
563
+ message: `Bundle confidence is LOW (${(bundle.confidence.score * 100).toFixed(0)}%). Consider running with --expand.`,
564
+ });
565
+ }
566
+ // 5. Check for architecture boundary violations
567
+ // (test files importing from non-test files at the wrong layer)
568
+ const layerOf = (p) => {
569
+ if (/\.(test|spec)\.[tj]sx?$/.test(p))
570
+ return 'tests';
571
+ if (/\/types?(\/|$)/.test(p))
572
+ return 'types';
573
+ if (/\/routes?(\/|$)/.test(p))
574
+ return 'routes';
575
+ if (/\/services?(\/|$)/.test(p))
576
+ return 'services';
577
+ return 'impl';
578
+ };
579
+ if (bundle.file_relationships) {
580
+ for (const rel of bundle.file_relationships) {
581
+ const fromLayer = layerOf(rel.from);
582
+ const toLayer = layerOf(rel.to);
583
+ // Routes should not import from tests
584
+ if (fromLayer === 'routes' && toLayer === 'tests') {
585
+ boundaryViolations.push({
586
+ from: rel.from,
587
+ to: rel.to,
588
+ reason: 'Route file imports from test file',
589
+ });
590
+ }
591
+ }
592
+ }
593
+ // 6. Check for stale/addressed files
594
+ if (bundle.addressed_files && bundle.addressed_files.length > 0) {
595
+ const ratio = bundle.addressed_files.length / bundle.files.length;
596
+ if (ratio > 0.5) {
597
+ issues.push({
598
+ code: 'STALE_BUNDLE',
599
+ severity: 'info',
600
+ message: `${bundle.addressed_files.length} of ${bundle.files.length} bundle files have been committed. Consider refreshing: codeledger activate --task "..."`,
601
+ });
602
+ }
603
+ }
604
+ // Read existence checks only if cwd is provided
605
+ if (cwd) {
606
+ for (const f of bundle.files) {
607
+ const abs = join(cwd, f.path);
608
+ if (!existsSync(abs)) {
609
+ issues.push({
610
+ code: 'FILE_NOT_FOUND',
611
+ severity: 'error',
612
+ message: `Bundle file no longer exists on disk`,
613
+ file: f.path,
614
+ });
615
+ }
616
+ }
617
+ }
618
+ const hasErrors = issues.some((i) => i.severity === 'error');
619
+ return {
620
+ passed: !hasErrors,
621
+ issues,
622
+ missing_dependencies: [...new Set(missingDependencies)],
623
+ uncovered_files: uncoveredFiles,
624
+ boundary_violations: boundaryViolations,
625
+ validated_at: new Date().toISOString(),
626
+ };
627
+ }
628
+ // ─── Shared Utilities ─────────────────────────────────────────────────────────
629
+ function loadBundle(cwd, bundlePath) {
630
+ // If explicit path provided
631
+ if (bundlePath) {
632
+ if (!existsSync(bundlePath)) {
633
+ console.error(`❌ Bundle file not found: ${bundlePath}`);
634
+ process.exit(1);
635
+ }
636
+ try {
637
+ return JSON.parse(readFileSync(bundlePath, 'utf-8'));
638
+ }
639
+ catch {
640
+ console.error(`❌ Failed to parse bundle: ${bundlePath}`);
641
+ process.exit(1);
642
+ }
643
+ }
644
+ // Try latest artifact in .codeledger/artifacts/bundles/ then artifacts/
645
+ const artifactsDir = join(cwd, '.codeledger', 'artifacts');
646
+ const bundlesDir = join(artifactsDir, 'bundles');
647
+ const bundlesDirResult = loadLatestBundles(bundlesDir, 1);
648
+ const bundles = bundlesDirResult.length > 0
649
+ ? bundlesDirResult
650
+ : loadLatestBundles(artifactsDir, 1);
651
+ if (bundles.length > 0)
652
+ return bundles[0];
653
+ // Try active-bundle.md (extract JSON if present)
654
+ const activePath = join(cwd, '.codeledger', 'active-bundle.md');
655
+ if (!existsSync(activePath)) {
656
+ console.error('❌ No active bundle found. Run: codeledger activate --task "..."');
657
+ process.exit(1);
658
+ }
659
+ console.error('⚠️ Active bundle is in Markdown format. Use --bundle <json-path> for full data.');
660
+ console.error(' JSON bundles are written to .codeledger/artifacts/bundles/ by default.');
661
+ process.exit(1);
662
+ }
663
+ function loadLatestBundles(artifactsDir, limit) {
664
+ if (!existsSync(artifactsDir))
665
+ return [];
666
+ try {
667
+ const files = readdirSync(artifactsDir)
668
+ .filter((f) => f.endsWith('.json') && !f.includes('policy') && !f.includes('manifest'))
669
+ .map((f) => ({ name: f, mtime: statSync(join(artifactsDir, f)).mtime.getTime() }))
670
+ .sort((a, b) => b.mtime - a.mtime)
671
+ .slice(0, limit)
672
+ .map((f) => {
673
+ try {
674
+ return JSON.parse(readFileSync(join(artifactsDir, f.name), 'utf-8'));
675
+ }
676
+ catch {
677
+ return null;
678
+ }
679
+ })
680
+ .filter((b) => b !== null);
681
+ return files;
682
+ }
683
+ catch {
684
+ return [];
685
+ }
686
+ }
687
+ //# sourceMappingURL=context.js.map