@a-company/paradigm 3.1.0 → 3.1.4

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/dist/mcp.js CHANGED
@@ -525,15 +525,15 @@ var require_sql_wasm = __commonJS({
525
525
  "undefined" != typeof __filename ? ya = __filename : ba && (ya = self.location.href);
526
526
  var za = "", Aa, Ba;
527
527
  if (ca) {
528
- var fs26 = __require("fs");
528
+ var fs27 = __require("fs");
529
529
  za = __dirname + "/";
530
530
  Ba = (a) => {
531
531
  a = Ca(a) ? new URL(a) : a;
532
- return fs26.readFileSync(a);
532
+ return fs27.readFileSync(a);
533
533
  };
534
534
  Aa = async (a) => {
535
535
  a = Ca(a) ? new URL(a) : a;
536
- return fs26.readFileSync(a, void 0);
536
+ return fs27.readFileSync(a, void 0);
537
537
  };
538
538
  1 < process.argv.length && (wa = process.argv[1].replace(/\\/g, "/"));
539
539
  process.argv.slice(2);
@@ -815,7 +815,7 @@ var require_sql_wasm = __commonJS({
815
815
  if (ca) {
816
816
  var b = Buffer.alloc(256), c = 0, d = process.stdin.fd;
817
817
  try {
818
- c = fs26.readSync(d, b, 0, 256);
818
+ c = fs27.readSync(d, b, 0, 256);
819
819
  } catch (e) {
820
820
  if (e.toString().includes("EOF")) c = 0;
821
821
  else throw e;
@@ -3986,6 +3986,7 @@ var SessionTracker = class {
3986
3986
  session;
3987
3987
  rootDir = null;
3988
3988
  _recovered = false;
3989
+ lastLoreEntryId = null;
3989
3990
  constructor() {
3990
3991
  this.session = this.createNewSession();
3991
3992
  }
@@ -4163,6 +4164,18 @@ var SessionTracker = class {
4163
4164
  } catch {
4164
4165
  }
4165
4166
  }
4167
+ /**
4168
+ * Set the last lore entry ID recorded in this session
4169
+ */
4170
+ setLastLoreEntryId(id) {
4171
+ this.lastLoreEntryId = id;
4172
+ }
4173
+ /**
4174
+ * Get the last lore entry ID recorded in this session
4175
+ */
4176
+ getLastLoreEntryId() {
4177
+ return this.lastLoreEntryId;
4178
+ }
4166
4179
  /**
4167
4180
  * Check whether auto-recovery has already fired this session.
4168
4181
  */
@@ -4278,8 +4291,8 @@ var SessionTracker = class {
4278
4291
  * Extract resource type from URI
4279
4292
  */
4280
4293
  extractResourceType(uri) {
4281
- const path30 = uri.replace("paradigm://", "");
4282
- const firstPart = path30.split("/")[0];
4294
+ const path31 = uri.replace("paradigm://", "");
4295
+ const firstPart = path31.split("/")[0];
4283
4296
  return firstPart || "unknown";
4284
4297
  }
4285
4298
  /**
@@ -4393,6 +4406,7 @@ var SessionTracker = class {
4393
4406
  reset() {
4394
4407
  this.session = this.createNewSession();
4395
4408
  this._recovered = false;
4409
+ this.lastLoreEntryId = null;
4396
4410
  }
4397
4411
  };
4398
4412
  var tracker = null;
@@ -6989,7 +7003,7 @@ function navigateExplore(config, target, rootDir) {
6989
7003
  }
6990
7004
  if (result.paths.length === 0) {
6991
7005
  const areaSymbols = Object.entries(config.symbols).filter(
6992
- ([sym, path30]) => sym.toLowerCase().includes(targetLower) || path30.toLowerCase().includes(targetLower)
7006
+ ([sym, path31]) => sym.toLowerCase().includes(targetLower) || path31.toLowerCase().includes(targetLower)
6993
7007
  ).slice(0, 10);
6994
7008
  result.paths = [...new Set(areaSymbols.map(([, p]) => p))];
6995
7009
  result.symbols = areaSymbols.map(([s]) => s);
@@ -12081,6 +12095,10 @@ async function handleTagsTool(name, args, ctx) {
12081
12095
  }
12082
12096
  }
12083
12097
 
12098
+ // ../paradigm-mcp/src/tools/purpose-portal.ts
12099
+ import * as fs21 from "fs";
12100
+ import * as path23 from "path";
12101
+
12084
12102
  // ../paradigm-mcp/src/utils/purpose-writer.ts
12085
12103
  import * as fs19 from "fs";
12086
12104
  import * as path21 from "path";
@@ -13053,6 +13071,21 @@ async function handleAddAspect(args, ctx, reloadContext2) {
13053
13071
  }
13054
13072
  }
13055
13073
  const filePath = resolvePurposeFilePath(purposeFile, ctx.rootDir);
13074
+ const purposeDir = path23.dirname(filePath);
13075
+ for (const anchor of anchors) {
13076
+ const anchorFile = anchor.replace(/:.*$/, "");
13077
+ const resolved = path23.resolve(purposeDir, anchorFile);
13078
+ if (!fs21.existsSync(resolved)) {
13079
+ const rootResolved = path23.resolve(ctx.rootDir, anchorFile);
13080
+ if (fs21.existsSync(rootResolved)) {
13081
+ const corrected = path23.relative(purposeDir, rootResolved);
13082
+ const idx = anchors.indexOf(anchor);
13083
+ anchors[idx] = anchor.replace(anchorFile, corrected);
13084
+ } else {
13085
+ return err(`Anchor file not found: "${anchorFile}". Anchors must be relative to the .purpose file directory (${purposeDir}).`);
13086
+ }
13087
+ }
13088
+ }
13056
13089
  const data = readPurposeFile(filePath);
13057
13090
  if (!data.aspects) data.aspects = {};
13058
13091
  const bareId = stripSymbolPrefix(id);
@@ -13452,12 +13485,12 @@ async function handleValidate(args, ctx) {
13452
13485
  }
13453
13486
 
13454
13487
  // ../paradigm-mcp/src/tools/pm.ts
13455
- import * as fs22 from "fs";
13456
- import * as path25 from "path";
13488
+ import * as fs23 from "fs";
13489
+ import * as path26 from "path";
13457
13490
 
13458
13491
  // ../paradigm-mcp/src/utils/habits-loader.ts
13459
- import * as fs21 from "fs";
13460
- import * as path23 from "path";
13492
+ import * as fs22 from "fs";
13493
+ import * as path24 from "path";
13461
13494
  import * as yaml13 from "js-yaml";
13462
13495
  var SEED_HABITS = [
13463
13496
  {
@@ -13564,7 +13597,7 @@ var SEED_HABITS = [
13564
13597
  var HABITS_CACHE_TTL_MS = 30 * 1e3;
13565
13598
  var habitsCache = /* @__PURE__ */ new Map();
13566
13599
  function loadHabits(rootDir) {
13567
- const absoluteRoot = path23.resolve(rootDir);
13600
+ const absoluteRoot = path24.resolve(rootDir);
13568
13601
  const cached = habitsCache.get(absoluteRoot);
13569
13602
  if (cached && Date.now() - cached.loadedAt < HABITS_CACHE_TTL_MS) {
13570
13603
  return cached.habits;
@@ -13579,16 +13612,16 @@ function loadHabitsFresh(rootDir) {
13579
13612
  habitsById.set(seed.id, { ...seed });
13580
13613
  }
13581
13614
  const home = process.env.HOME || process.env.USERPROFILE || "~";
13582
- const globalConfig = loadHabitsYaml(path23.join(home, ".paradigm", "habits.yaml"));
13615
+ const globalConfig = loadHabitsYaml(path24.join(home, ".paradigm", "habits.yaml"));
13583
13616
  if (globalConfig) mergeHabits(habitsById, globalConfig);
13584
- const projectConfig = loadHabitsYaml(path23.join(rootDir, ".paradigm", "habits.yaml"));
13617
+ const projectConfig = loadHabitsYaml(path24.join(rootDir, ".paradigm", "habits.yaml"));
13585
13618
  if (projectConfig) mergeHabits(habitsById, projectConfig);
13586
13619
  return Array.from(habitsById.values());
13587
13620
  }
13588
13621
  function loadHabitsYaml(filePath) {
13589
- if (!fs21.existsSync(filePath)) return null;
13622
+ if (!fs22.existsSync(filePath)) return null;
13590
13623
  try {
13591
- const content = fs21.readFileSync(filePath, "utf8");
13624
+ const content = fs22.readFileSync(filePath, "utf8");
13592
13625
  return yaml13.load(content);
13593
13626
  } catch {
13594
13627
  return null;
@@ -13613,8 +13646,11 @@ function mergeHabits(habitsById, config) {
13613
13646
  function getHabitsByTrigger(habits, trigger) {
13614
13647
  return habits.filter((h) => h.enabled && h.trigger === trigger);
13615
13648
  }
13616
- function evaluateHabits(habits, trigger, context2) {
13617
- const activeHabits = getHabitsByTrigger(habits, trigger);
13649
+ function evaluateHabits(habits, trigger, context2, platform2) {
13650
+ let activeHabits = getHabitsByTrigger(habits, trigger);
13651
+ if (platform2) {
13652
+ activeHabits = activeHabits.filter((h) => !h.platforms || h.platforms.includes(platform2));
13653
+ }
13618
13654
  const evaluations = activeHabits.map((h) => evaluateHabit(h, context2));
13619
13655
  const followed = evaluations.filter((e) => e.result === "followed").length;
13620
13656
  const skipped = evaluations.filter((e) => e.result === "skipped").length;
@@ -13637,7 +13673,8 @@ function buildEvaluationContext(params) {
13637
13673
  loreRecorded: params.loreRecorded || false,
13638
13674
  hasPortalRoutes: params.hasPortalRoutes || false,
13639
13675
  taskAddsRoutes: params.taskAddsRoutes || false,
13640
- taskDescription: params.taskDescription
13676
+ taskDescription: params.taskDescription,
13677
+ gitClean: params.gitClean
13641
13678
  };
13642
13679
  }
13643
13680
  function evaluateHabit(habit, ctx) {
@@ -13646,6 +13683,8 @@ function evaluateHabit(habit, ctx) {
13646
13683
  return evalToolCalled(habit, ctx);
13647
13684
  case "file-exists":
13648
13685
  return evalFileExists(habit, ctx);
13686
+ case "file-modified":
13687
+ return evalFileModified(habit, ctx);
13649
13688
  case "lore-recorded":
13650
13689
  return evalLoreRecorded(habit, ctx);
13651
13690
  case "symbols-registered":
@@ -13654,6 +13693,8 @@ function evaluateHabit(habit, ctx) {
13654
13693
  return evalGatesDeclared(habit, ctx);
13655
13694
  case "tests-exist":
13656
13695
  return evalTestsExist(habit, ctx);
13696
+ case "git-clean":
13697
+ return evalGitClean(habit, ctx);
13657
13698
  default:
13658
13699
  return { habit, result: "partial", reason: `Unknown check: ${habit.check.type}` };
13659
13700
  }
@@ -13722,14 +13763,36 @@ function evalTestsExist(habit, ctx) {
13722
13763
  if (src.length === 0) return { habit, result: "followed", reason: "No source files to test" };
13723
13764
  return { habit, result: "partial", reason: `${src.length} source file(s), no test files updated`, evidence: src.slice(0, 5) };
13724
13765
  }
13766
+ function evalFileModified(habit, ctx) {
13767
+ if (ctx.filesModified.length === 0) return { habit, result: "followed", reason: "No files modified" };
13768
+ const patterns = habit.check.params.patterns || [];
13769
+ if (patterns.length === 0) return { habit, result: "followed", reason: "No patterns specified" };
13770
+ const matched = ctx.filesModified.filter(
13771
+ (f) => patterns.some((p) => f.includes(p) || path24.basename(f) === p)
13772
+ );
13773
+ if (matched.length > 0) {
13774
+ return { habit, result: "followed", reason: `Matching files: ${matched.join(", ")}`, evidence: matched };
13775
+ }
13776
+ return { habit, result: "skipped", reason: `None of [${patterns.join(", ")}] found in modified files` };
13777
+ }
13778
+ function evalGitClean(habit, ctx) {
13779
+ if (ctx.filesModified.length === 0) return { habit, result: "followed", reason: "No files modified" };
13780
+ if (ctx.gitClean === void 0) {
13781
+ return { habit, result: "partial", reason: "Git status not available" };
13782
+ }
13783
+ if (ctx.gitClean) {
13784
+ return { habit, result: "followed", reason: "Working tree is clean \u2014 changes committed" };
13785
+ }
13786
+ return { habit, result: "skipped", reason: "Uncommitted changes in working tree" };
13787
+ }
13725
13788
 
13726
13789
  // ../paradigm-mcp/src/utils/practice-store.ts
13727
- import * as path24 from "path";
13790
+ import * as path25 from "path";
13728
13791
  var storageInstance = null;
13729
13792
  var storageInitialized2 = false;
13730
13793
  async function getStorage2(rootDir) {
13731
13794
  if (!storageInstance) {
13732
- const dbPath = path24.join(rootDir, ".paradigm", "sentinel", "sentinel.db");
13795
+ const dbPath = path25.join(rootDir, ".paradigm", "sentinel", "sentinel.db");
13733
13796
  storageInstance = new SentinelStorage(dbPath);
13734
13797
  await storageInstance.ensureReady();
13735
13798
  storageInitialized2 = true;
@@ -13781,6 +13844,7 @@ async function recordEvaluationResults(rootDir, evaluations, context2) {
13781
13844
  result: eval_.result,
13782
13845
  engineer: context2.engineer,
13783
13846
  sessionId: context2.sessionId,
13847
+ loreEntryId: context2.loreEntryId,
13784
13848
  taskDescription: context2.taskDescription,
13785
13849
  symbolsTouched: context2.symbolsTouched,
13786
13850
  filesModified: context2.filesModified,
@@ -13792,6 +13856,7 @@ async function recordEvaluationResults(rootDir, evaluations, context2) {
13792
13856
  }
13793
13857
 
13794
13858
  // ../paradigm-mcp/src/tools/pm.ts
13859
+ import { execSync } from "child_process";
13795
13860
  var SYMBOL_PATTERN2 = /[@#$%^!?&~][a-zA-Z][a-zA-Z0-9_-]*/g;
13796
13861
  var ROUTE_KEYWORDS = [
13797
13862
  "endpoint",
@@ -13942,17 +14007,36 @@ async function runPreflightCheck(task, ctx) {
13942
14007
  requiredChecks.push("signal-registration");
13943
14008
  }
13944
14009
  requiredChecks.push("purpose-coverage");
13945
- let activeHabits = [];
14010
+ let habitsEvaluation = null;
13946
14011
  let recentCompliance = null;
13947
14012
  try {
13948
14013
  const habits = loadHabits(ctx.rootDir);
13949
- const preflightHabits = getHabitsByTrigger(habits, "preflight");
13950
- activeHabits = preflightHabits.map((h) => ({
13951
- id: h.id,
13952
- name: h.name,
13953
- category: h.category,
13954
- severity: h.severity
13955
- }));
14014
+ const tracker2 = getSessionTracker();
14015
+ const stats = tracker2.getStats();
14016
+ const toolsCalled = [...new Set(stats.toolCalls.map((tc) => tc.toolName))];
14017
+ const evalContext = buildEvaluationContext({
14018
+ toolsCalled,
14019
+ filesModified: [],
14020
+ symbolsTouched: uniqueSymbols,
14021
+ loreRecorded: false,
14022
+ hasPortalRoutes: portalStatus.exists && portalStatus.routeCount > 0,
14023
+ taskAddsRoutes,
14024
+ taskDescription: task
14025
+ });
14026
+ const evalResult = evaluateHabits(habits, "preflight", evalContext);
14027
+ habitsEvaluation = {
14028
+ total: evalResult.summary.total,
14029
+ followed: evalResult.summary.followed,
14030
+ skipped: evalResult.summary.skipped,
14031
+ partial: evalResult.summary.partial,
14032
+ results: evalResult.evaluations.map((e) => ({
14033
+ id: e.habit.id,
14034
+ name: e.habit.name,
14035
+ severity: e.habit.severity,
14036
+ result: e.result,
14037
+ reason: e.reason
14038
+ }))
14039
+ };
13956
14040
  const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString();
13957
14041
  recentCompliance = await getComplianceRate(ctx.rootDir, { dateFrom: thirtyDaysAgo });
13958
14042
  } catch {
@@ -13966,12 +14050,11 @@ async function runPreflightCheck(task, ctx) {
13966
14050
  requiredChecks,
13967
14051
  recommendations: buildPreflightRecommendations(affectedSymbols, rippleAnalysis, portalStatus, taskAddsRoutes),
13968
14052
  habits: {
13969
- active: activeHabits,
14053
+ evaluation: habitsEvaluation,
13970
14054
  recentCompliance: recentCompliance ? {
13971
14055
  rate: recentCompliance.rate,
13972
14056
  totalEvents: recentCompliance.total
13973
- } : null,
13974
- note: activeHabits.length > 0 ? `${activeHabits.length} preflight habit(s) active. Call paradigm_habits_check with trigger="preflight" to evaluate.` : "No habits configured. Run paradigm habits init to set up."
14057
+ } : null
13975
14058
  }
13976
14059
  };
13977
14060
  }
@@ -14000,11 +14083,11 @@ function runPostflightCheck(filesModified, symbolsTouched, ctx) {
14000
14083
  const violations = [];
14001
14084
  const declaredRoutes = ctx.gateConfig?.routes ? Object.keys(ctx.gateConfig.routes) : [];
14002
14085
  for (const file of filesModified) {
14003
- const absPath = path25.isAbsolute(file) ? file : path25.join(ctx.rootDir, file);
14004
- if (!fs22.existsSync(absPath)) continue;
14086
+ const absPath = path26.isAbsolute(file) ? file : path26.join(ctx.rootDir, file);
14087
+ if (!fs23.existsSync(absPath)) continue;
14005
14088
  let content;
14006
14089
  try {
14007
- content = fs22.readFileSync(absPath, "utf-8");
14090
+ content = fs23.readFileSync(absPath, "utf-8");
14008
14091
  } catch {
14009
14092
  continue;
14010
14093
  }
@@ -14022,8 +14105,8 @@ function runPostflightCheck(filesModified, symbolsTouched, ctx) {
14022
14105
  violations.push({
14023
14106
  type: "missing-portal-gate",
14024
14107
  severity: "warning",
14025
- message: `Route "${routePath}" in ${path25.relative(ctx.rootDir, absPath)} not in portal.yaml`,
14026
- file: path25.relative(ctx.rootDir, absPath),
14108
+ message: `Route "${routePath}" in ${path26.relative(ctx.rootDir, absPath)} not in portal.yaml`,
14109
+ file: path26.relative(ctx.rootDir, absPath),
14027
14110
  suggestion: "Add route to portal.yaml with ^gates. Use paradigm_gates_for_route for suggestions."
14028
14111
  });
14029
14112
  } else if (!ctx.gateConfig && routePath.startsWith("/api/")) {
@@ -14031,7 +14114,7 @@ function runPostflightCheck(filesModified, symbolsTouched, ctx) {
14031
14114
  type: "missing-portal-gate",
14032
14115
  severity: "warning",
14033
14116
  message: `API route "${routePath}" found but no portal.yaml exists`,
14034
- file: path25.relative(ctx.rootDir, absPath),
14117
+ file: path26.relative(ctx.rootDir, absPath),
14035
14118
  suggestion: "Create portal.yaml to declare gates for API routes."
14036
14119
  });
14037
14120
  }
@@ -14089,8 +14172,8 @@ function runPostflightCheck(filesModified, symbolsTouched, ctx) {
14089
14172
  });
14090
14173
  } else {
14091
14174
  for (const anchor of anchors) {
14092
- const filePath = path25.isAbsolute(anchor.path) ? anchor.path : path25.join(ctx.rootDir, anchor.path);
14093
- if (!fs22.existsSync(filePath)) {
14175
+ const filePath = path26.isAbsolute(anchor.path) ? anchor.path : path26.join(ctx.rootDir, anchor.path);
14176
+ if (!fs23.existsSync(filePath)) {
14094
14177
  violations.push({
14095
14178
  type: "stale-aspect",
14096
14179
  severity: "warning",
@@ -14129,14 +14212,54 @@ function runPostflightCheck(filesModified, symbolsTouched, ctx) {
14129
14212
  let status = "pass";
14130
14213
  if (errors > 0) status = "violations";
14131
14214
  else if (warnings > 0) status = "warnings";
14132
- let habitReminder = null;
14215
+ let habitsEvaluation = null;
14133
14216
  try {
14134
14217
  const habits = loadHabits(ctx.rootDir);
14135
- const postflightHabits = getHabitsByTrigger(habits, "postflight");
14136
- const stopHabits = getHabitsByTrigger(habits, "on-stop");
14137
- const totalActive = postflightHabits.length + stopHabits.length;
14138
- if (totalActive > 0) {
14139
- habitReminder = `${totalActive} habit(s) should be checked. Call paradigm_habits_check with trigger="postflight" to evaluate and record practice events.`;
14218
+ const tracker2 = getSessionTracker();
14219
+ const stats = tracker2.getStats();
14220
+ const toolsCalled = [...new Set(stats.toolCalls.map((tc) => tc.toolName))];
14221
+ const loreRecorded = toolsCalled.includes("paradigm_lore_record");
14222
+ let gitClean;
14223
+ try {
14224
+ const gitStatus = execSync("git status --porcelain", {
14225
+ cwd: ctx.rootDir,
14226
+ encoding: "utf8",
14227
+ timeout: 5e3
14228
+ });
14229
+ gitClean = gitStatus.trim() === "";
14230
+ } catch {
14231
+ }
14232
+ const evalContext = buildEvaluationContext({
14233
+ toolsCalled,
14234
+ filesModified,
14235
+ symbolsTouched,
14236
+ loreRecorded,
14237
+ hasPortalRoutes: ctx.gateConfig !== null && ctx.gateConfig.routes != null,
14238
+ taskAddsRoutes: false,
14239
+ gitClean
14240
+ });
14241
+ const evalResult = evaluateHabits(habits, "postflight", evalContext);
14242
+ habitsEvaluation = {
14243
+ trigger: "postflight",
14244
+ total: evalResult.summary.total,
14245
+ followed: evalResult.summary.followed,
14246
+ skipped: evalResult.summary.skipped,
14247
+ partial: evalResult.summary.partial,
14248
+ blockingViolations: evalResult.summary.blockingViolations,
14249
+ results: evalResult.evaluations.map((e) => ({
14250
+ id: e.habit.id,
14251
+ name: e.habit.name,
14252
+ severity: e.habit.severity,
14253
+ result: e.result,
14254
+ reason: e.reason
14255
+ }))
14256
+ };
14257
+ const markerPath = path26.join(ctx.rootDir, ".paradigm", ".habits-blocking");
14258
+ if (evalResult.blocksCompletion) {
14259
+ const blocking = evalResult.evaluations.filter((e) => e.result === "skipped" && e.habit.severity === "block").map((e) => `${e.habit.name}: ${e.reason}`);
14260
+ fs23.writeFileSync(markerPath, blocking.join("\n"), "utf8");
14261
+ } else if (fs23.existsSync(markerPath)) {
14262
+ fs23.unlinkSync(markerPath);
14140
14263
  }
14141
14264
  } catch {
14142
14265
  }
@@ -14150,13 +14273,13 @@ function runPostflightCheck(filesModified, symbolsTouched, ctx) {
14150
14273
  errors
14151
14274
  },
14152
14275
  blocksCompletion: errors > 0,
14153
- habitReminder
14276
+ habitsEvaluation
14154
14277
  };
14155
14278
  }
14156
14279
 
14157
14280
  // ../paradigm-mcp/src/tools/reindex.ts
14158
- import * as fs23 from "fs";
14159
- import * as path26 from "path";
14281
+ import * as fs24 from "fs";
14282
+ import * as path27 from "path";
14160
14283
  import * as yaml14 from "js-yaml";
14161
14284
 
14162
14285
  // ../probe/core/dist/generator.js
@@ -14495,10 +14618,10 @@ async function rebuildStaticFiles(rootDir, ctx) {
14495
14618
  } else {
14496
14619
  aggregation = await aggregateFromDirectory(rootDir);
14497
14620
  }
14498
- const projectName = ctx?.projectName || path26.basename(rootDir);
14499
- const paradigmDir = path26.join(rootDir, ".paradigm");
14500
- if (!fs23.existsSync(paradigmDir)) {
14501
- fs23.mkdirSync(paradigmDir, { recursive: true });
14621
+ const projectName = ctx?.projectName || path27.basename(rootDir);
14622
+ const paradigmDir = path27.join(rootDir, ".paradigm");
14623
+ if (!fs24.existsSync(paradigmDir)) {
14624
+ fs24.mkdirSync(paradigmDir, { recursive: true });
14502
14625
  }
14503
14626
  const scanIndex = generateScanIndex(
14504
14627
  {
@@ -14508,12 +14631,12 @@ async function rebuildStaticFiles(rootDir, ctx) {
14508
14631
  },
14509
14632
  { projectName }
14510
14633
  );
14511
- const scanIndexPath = path26.join(paradigmDir, "scan-index.json");
14512
- fs23.writeFileSync(scanIndexPath, serializeScanIndex(scanIndex), "utf8");
14634
+ const scanIndexPath = path27.join(paradigmDir, "scan-index.json");
14635
+ fs24.writeFileSync(scanIndexPath, serializeScanIndex(scanIndex), "utf8");
14513
14636
  filesWritten.push(".paradigm/scan-index.json");
14514
14637
  const navigatorData = buildNavigatorData(rootDir, aggregation);
14515
- const navigatorPath = path26.join(paradigmDir, "navigator.yaml");
14516
- fs23.writeFileSync(
14638
+ const navigatorPath = path27.join(paradigmDir, "navigator.yaml");
14639
+ fs24.writeFileSync(
14517
14640
  navigatorPath,
14518
14641
  yaml14.dump(navigatorData, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }),
14519
14642
  "utf8"
@@ -14522,8 +14645,8 @@ async function rebuildStaticFiles(rootDir, ctx) {
14522
14645
  const flowIndex = generateFlowIndex(rootDir, aggregation.purposeFiles);
14523
14646
  let flowCount = 0;
14524
14647
  if (flowIndex && Object.keys(flowIndex.flows).length > 0) {
14525
- const flowIndexPath = path26.join(paradigmDir, "flow-index.json");
14526
- fs23.writeFileSync(flowIndexPath, JSON.stringify(flowIndex, null, 2), "utf8");
14648
+ const flowIndexPath = path27.join(paradigmDir, "flow-index.json");
14649
+ fs24.writeFileSync(flowIndexPath, JSON.stringify(flowIndex, null, 2), "utf8");
14527
14650
  filesWritten.push(".paradigm/flow-index.json");
14528
14651
  flowCount = Object.keys(flowIndex.flows).length;
14529
14652
  }
@@ -14552,7 +14675,7 @@ function buildNavigatorData(rootDir, aggregation) {
14552
14675
  function buildStructure(rootDir) {
14553
14676
  const structure = {};
14554
14677
  for (const [category, patterns] of Object.entries(DIRECTORY_PATTERNS)) {
14555
- const existingPaths = patterns.filter((p) => fs23.existsSync(path26.join(rootDir, p)));
14678
+ const existingPaths = patterns.filter((p) => fs24.existsSync(path27.join(rootDir, p)));
14556
14679
  if (existingPaths.length > 0) {
14557
14680
  const symbolInfo = Object.values(SYMBOL_CATEGORIES).find((s) => s.category === category);
14558
14681
  structure[category] = { paths: existingPaths, symbol: symbolInfo?.prefix || "@" };
@@ -14563,7 +14686,7 @@ function buildStructure(rootDir) {
14563
14686
  function buildKeyFiles(rootDir) {
14564
14687
  const keyFiles = {};
14565
14688
  for (const [category, patterns] of Object.entries(KEY_FILE_PATTERNS)) {
14566
- const existingPaths = patterns.filter((p) => fs23.existsSync(path26.join(rootDir, p)));
14689
+ const existingPaths = patterns.filter((p) => fs24.existsSync(path27.join(rootDir, p)));
14567
14690
  if (existingPaths.length > 0) {
14568
14691
  keyFiles[category] = existingPaths;
14569
14692
  }
@@ -14579,10 +14702,10 @@ function buildSkipPatterns(rootDir) {
14579
14702
  unless_testing: [...DEFAULT_SKIP_PATTERNS.unless_testing],
14580
14703
  unless_docs: [...DEFAULT_SKIP_PATTERNS.unless_docs]
14581
14704
  };
14582
- const gitignorePath = path26.join(rootDir, ".gitignore");
14583
- if (fs23.existsSync(gitignorePath)) {
14705
+ const gitignorePath = path27.join(rootDir, ".gitignore");
14706
+ if (fs24.existsSync(gitignorePath)) {
14584
14707
  try {
14585
- const content = fs23.readFileSync(gitignorePath, "utf8");
14708
+ const content = fs24.readFileSync(gitignorePath, "utf8");
14586
14709
  const gitignorePatterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).filter(
14587
14710
  (line) => line.endsWith("/") || line.includes("*") || ["node_modules", "dist", "build", ".cache"].some((p) => line.includes(p))
14588
14711
  ).slice(0, 20);
@@ -14631,11 +14754,11 @@ function buildSymbolMap(symbols, purposeFiles, _rootDir) {
14631
14754
  symbolMap[symbolId] = symbol.filePath;
14632
14755
  } else {
14633
14756
  const matchingPurpose = purposeFiles.find((pf) => {
14634
- const dir = path26.dirname(pf);
14757
+ const dir = path27.dirname(pf);
14635
14758
  return dir.toLowerCase().includes(symbol.id.toLowerCase());
14636
14759
  });
14637
14760
  if (matchingPurpose) {
14638
- symbolMap[symbolId] = path26.dirname(matchingPurpose) + "/";
14761
+ symbolMap[symbolId] = path27.dirname(matchingPurpose) + "/";
14639
14762
  }
14640
14763
  }
14641
14764
  }
@@ -14646,7 +14769,7 @@ function generateFlowIndex(rootDir, purposeFiles) {
14646
14769
  const symbolToFlows = {};
14647
14770
  for (const filePath of purposeFiles) {
14648
14771
  try {
14649
- const content = fs23.readFileSync(filePath, "utf8");
14772
+ const content = fs24.readFileSync(filePath, "utf8");
14650
14773
  const data = yaml14.load(content);
14651
14774
  if (!data?.flows) continue;
14652
14775
  if (Array.isArray(data.flows)) {
@@ -14660,7 +14783,7 @@ function generateFlowIndex(rootDir, purposeFiles) {
14660
14783
  id: flowId,
14661
14784
  description: flow.description || "",
14662
14785
  steps,
14663
- definedIn: path26.relative(rootDir, filePath)
14786
+ definedIn: path27.relative(rootDir, filePath)
14664
14787
  };
14665
14788
  indexFlowSymbols(flowId, steps, symbolToFlows);
14666
14789
  }
@@ -14676,7 +14799,7 @@ function generateFlowIndex(rootDir, purposeFiles) {
14676
14799
  trigger: flowDef.trigger,
14677
14800
  steps,
14678
14801
  validation: flowDef.validation,
14679
- definedIn: path26.relative(rootDir, filePath)
14802
+ definedIn: path27.relative(rootDir, filePath)
14680
14803
  };
14681
14804
  indexFlowSymbols(flowId, steps, symbolToFlows);
14682
14805
  }
@@ -14729,28 +14852,28 @@ function indexFlowSymbols(flowId, steps, symbolToFlows) {
14729
14852
  }
14730
14853
 
14731
14854
  // ../paradigm-mcp/src/utils/lore-loader.ts
14732
- import * as fs24 from "fs";
14733
- import * as path27 from "path";
14855
+ import * as fs25 from "fs";
14856
+ import * as path28 from "path";
14734
14857
  import * as yaml15 from "js-yaml";
14735
14858
  var LORE_DIR = ".paradigm/lore";
14736
14859
  var ENTRIES_DIR = "entries";
14737
14860
  var TIMELINE_FILE = "timeline.yaml";
14738
14861
  async function loadLoreEntries(rootDir, filter) {
14739
- const entriesPath = path27.join(rootDir, LORE_DIR, ENTRIES_DIR);
14740
- if (!fs24.existsSync(entriesPath)) {
14862
+ const entriesPath = path28.join(rootDir, LORE_DIR, ENTRIES_DIR);
14863
+ if (!fs25.existsSync(entriesPath)) {
14741
14864
  return [];
14742
14865
  }
14743
14866
  migrateLegacyEntries(rootDir);
14744
14867
  const entries = [];
14745
- const dateDirs = fs24.readdirSync(entriesPath).filter((d) => /^\d{4}-\d{2}-\d{2}$/.test(d)).sort().reverse();
14868
+ const dateDirs = fs25.readdirSync(entriesPath).filter((d) => /^\d{4}-\d{2}-\d{2}$/.test(d)).sort().reverse();
14746
14869
  for (const dateDir of dateDirs) {
14747
14870
  if (filter?.dateFrom && dateDir < filter.dateFrom.slice(0, 10)) continue;
14748
14871
  if (filter?.dateTo && dateDir > filter.dateTo.slice(0, 10)) continue;
14749
- const dirPath = path27.join(entriesPath, dateDir);
14750
- const files = fs24.readdirSync(dirPath).filter((f) => f.endsWith(".yaml")).sort();
14872
+ const dirPath = path28.join(entriesPath, dateDir);
14873
+ const files = fs25.readdirSync(dirPath).filter((f) => f.endsWith(".yaml")).sort();
14751
14874
  for (const file of files) {
14752
14875
  try {
14753
- const content = fs24.readFileSync(path27.join(dirPath, file), "utf8");
14876
+ const content = fs25.readFileSync(path28.join(dirPath, file), "utf8");
14754
14877
  const entry = yaml15.load(content);
14755
14878
  entries.push(entry);
14756
14879
  } catch {
@@ -14759,48 +14882,65 @@ async function loadLoreEntries(rootDir, filter) {
14759
14882
  }
14760
14883
  return filter ? applyFilter(entries, filter) : entries;
14761
14884
  }
14885
+ async function loadLoreEntry(rootDir, entryId) {
14886
+ const dateMatch = entryId.match(/^L-(\d{4}-\d{2}-\d{2})-/);
14887
+ if (dateMatch) {
14888
+ const dateStr = dateMatch[1];
14889
+ const entryPath = path28.join(rootDir, LORE_DIR, ENTRIES_DIR, dateStr, `${entryId}.yaml`);
14890
+ if (fs25.existsSync(entryPath)) {
14891
+ try {
14892
+ const content = fs25.readFileSync(entryPath, "utf8");
14893
+ return yaml15.load(content);
14894
+ } catch {
14895
+ return null;
14896
+ }
14897
+ }
14898
+ }
14899
+ const entries = await loadLoreEntries(rootDir);
14900
+ return entries.find((e) => e.id === entryId) || null;
14901
+ }
14762
14902
  async function loadLoreTimeline(rootDir) {
14763
- const timelinePath = path27.join(rootDir, LORE_DIR, TIMELINE_FILE);
14764
- if (!fs24.existsSync(timelinePath)) {
14903
+ const timelinePath = path28.join(rootDir, LORE_DIR, TIMELINE_FILE);
14904
+ if (!fs25.existsSync(timelinePath)) {
14765
14905
  return null;
14766
14906
  }
14767
14907
  try {
14768
- const content = fs24.readFileSync(timelinePath, "utf8");
14908
+ const content = fs25.readFileSync(timelinePath, "utf8");
14769
14909
  return yaml15.load(content);
14770
14910
  } catch {
14771
14911
  return null;
14772
14912
  }
14773
14913
  }
14774
14914
  async function recordLoreEntry(rootDir, entry) {
14775
- const lorePath = path27.join(rootDir, LORE_DIR);
14915
+ const lorePath = path28.join(rootDir, LORE_DIR);
14776
14916
  const dateStr = entry.timestamp.slice(0, 10);
14777
- const datePath = path27.join(lorePath, ENTRIES_DIR, dateStr);
14778
- if (!fs24.existsSync(datePath)) {
14779
- fs24.mkdirSync(datePath, { recursive: true });
14917
+ const datePath = path28.join(lorePath, ENTRIES_DIR, dateStr);
14918
+ if (!fs25.existsSync(datePath)) {
14919
+ fs25.mkdirSync(datePath, { recursive: true });
14780
14920
  }
14781
14921
  if (!entry.id) {
14782
14922
  entry.id = generateLoreId(rootDir, dateStr);
14783
14923
  }
14784
- const entryPath = path27.join(datePath, `${entry.id}.yaml`);
14785
- fs24.writeFileSync(entryPath, yaml15.dump(entry, { lineWidth: -1, noRefs: true }));
14924
+ const entryPath = path28.join(datePath, `${entry.id}.yaml`);
14925
+ fs25.writeFileSync(entryPath, yaml15.dump(entry, { lineWidth: -1, noRefs: true }));
14786
14926
  await rebuildTimeline(rootDir);
14787
14927
  return entry.id;
14788
14928
  }
14789
14929
  async function rebuildTimeline(rootDir) {
14790
- const lorePath = path27.join(rootDir, LORE_DIR);
14791
- const entriesPath = path27.join(lorePath, ENTRIES_DIR);
14792
- if (!fs24.existsSync(entriesPath)) return;
14930
+ const lorePath = path28.join(rootDir, LORE_DIR);
14931
+ const entriesPath = path28.join(lorePath, ENTRIES_DIR);
14932
+ if (!fs25.existsSync(entriesPath)) return;
14793
14933
  migrateLegacyEntries(rootDir);
14794
14934
  const authors = /* @__PURE__ */ new Set();
14795
14935
  let entryCount = 0;
14796
14936
  let lastUpdated = "";
14797
- const dateDirs = fs24.readdirSync(entriesPath).filter((d) => /^\d{4}-\d{2}-\d{2}$/.test(d));
14937
+ const dateDirs = fs25.readdirSync(entriesPath).filter((d) => /^\d{4}-\d{2}-\d{2}$/.test(d));
14798
14938
  for (const dateDir of dateDirs) {
14799
- const dirPath = path27.join(entriesPath, dateDir);
14800
- const files = fs24.readdirSync(dirPath).filter((f) => f.endsWith(".yaml"));
14939
+ const dirPath = path28.join(entriesPath, dateDir);
14940
+ const files = fs25.readdirSync(dirPath).filter((f) => f.endsWith(".yaml"));
14801
14941
  for (const file of files) {
14802
14942
  try {
14803
- const content = fs24.readFileSync(path27.join(dirPath, file), "utf8");
14943
+ const content = fs25.readFileSync(path28.join(dirPath, file), "utf8");
14804
14944
  const entry = yaml15.load(content);
14805
14945
  authors.add(entry.author.id);
14806
14946
  entryCount++;
@@ -14812,10 +14952,10 @@ async function rebuildTimeline(rootDir) {
14812
14952
  }
14813
14953
  }
14814
14954
  let project = "unknown";
14815
- const configPath = path27.join(rootDir, ".paradigm", "config.yaml");
14816
- if (fs24.existsSync(configPath)) {
14955
+ const configPath = path28.join(rootDir, ".paradigm", "config.yaml");
14956
+ if (fs25.existsSync(configPath)) {
14817
14957
  try {
14818
- const config = yaml15.load(fs24.readFileSync(configPath, "utf8"));
14958
+ const config = yaml15.load(fs25.readFileSync(configPath, "utf8"));
14819
14959
  project = config.project || config.name || "unknown";
14820
14960
  } catch {
14821
14961
  }
@@ -14827,31 +14967,31 @@ async function rebuildTimeline(rootDir) {
14827
14967
  last_updated: lastUpdated || (/* @__PURE__ */ new Date()).toISOString(),
14828
14968
  authors: Array.from(authors)
14829
14969
  };
14830
- if (!fs24.existsSync(lorePath)) {
14831
- fs24.mkdirSync(lorePath, { recursive: true });
14970
+ if (!fs25.existsSync(lorePath)) {
14971
+ fs25.mkdirSync(lorePath, { recursive: true });
14832
14972
  }
14833
- fs24.writeFileSync(
14834
- path27.join(lorePath, TIMELINE_FILE),
14973
+ fs25.writeFileSync(
14974
+ path28.join(lorePath, TIMELINE_FILE),
14835
14975
  yaml15.dump(timeline, { lineWidth: -1, noRefs: true })
14836
14976
  );
14837
14977
  }
14838
14978
  function migrateLegacyEntries(rootDir) {
14839
- const entriesPath = path27.join(rootDir, LORE_DIR, ENTRIES_DIR);
14840
- if (!fs24.existsSync(entriesPath)) return 0;
14841
- const rootFiles = fs24.readdirSync(entriesPath).filter((f) => f.endsWith(".yaml") && !f.startsWith("."));
14979
+ const entriesPath = path28.join(rootDir, LORE_DIR, ENTRIES_DIR);
14980
+ if (!fs25.existsSync(entriesPath)) return 0;
14981
+ const rootFiles = fs25.readdirSync(entriesPath).filter((f) => f.endsWith(".yaml") && !f.startsWith("."));
14842
14982
  let migrated = 0;
14843
14983
  for (const file of rootFiles) {
14844
- const filePath = path27.join(entriesPath, file);
14845
- const stat = fs24.statSync(filePath);
14984
+ const filePath = path28.join(entriesPath, file);
14985
+ const stat = fs25.statSync(filePath);
14846
14986
  if (!stat.isFile()) continue;
14847
14987
  try {
14848
- const content = fs24.readFileSync(filePath, "utf8");
14988
+ const content = fs25.readFileSync(filePath, "utf8");
14849
14989
  const raw = yaml15.load(content);
14850
14990
  if (raw.author && typeof raw.author === "object") continue;
14851
14991
  const dateStr = typeof raw.date === "string" ? raw.date.slice(0, 10) : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
14852
- const datePath = path27.join(entriesPath, dateStr);
14853
- if (!fs24.existsSync(datePath)) {
14854
- fs24.mkdirSync(datePath, { recursive: true });
14992
+ const datePath = path28.join(entriesPath, dateStr);
14993
+ if (!fs25.existsSync(datePath)) {
14994
+ fs25.mkdirSync(datePath, { recursive: true });
14855
14995
  }
14856
14996
  const id = generateLoreId(rootDir, dateStr);
14857
14997
  const oldType = String(raw.type || "agent-session");
@@ -14876,17 +15016,58 @@ function migrateLegacyEntries(rootDir) {
14876
15016
  ...verification ? { verification } : {},
14877
15017
  tags: ["migrated", oldType]
14878
15018
  };
14879
- fs24.writeFileSync(
14880
- path27.join(datePath, `${id}.yaml`),
15019
+ fs25.writeFileSync(
15020
+ path28.join(datePath, `${id}.yaml`),
14881
15021
  yaml15.dump(v2Entry, { lineWidth: -1, noRefs: true })
14882
15022
  );
14883
- fs24.unlinkSync(filePath);
15023
+ fs25.unlinkSync(filePath);
14884
15024
  migrated++;
14885
15025
  } catch {
14886
15026
  }
14887
15027
  }
14888
15028
  return migrated;
14889
15029
  }
15030
+ async function updateLoreEntry(rootDir, entryId, partial) {
15031
+ const entry = await loadLoreEntry(rootDir, entryId);
15032
+ if (!entry) return false;
15033
+ const dateStr = entry.timestamp.slice(0, 10);
15034
+ const entryPath = path28.join(rootDir, LORE_DIR, ENTRIES_DIR, dateStr, `${entryId}.yaml`);
15035
+ if (!fs25.existsSync(entryPath)) return false;
15036
+ if (partial.title !== void 0) entry.title = partial.title;
15037
+ if (partial.summary !== void 0) entry.summary = partial.summary;
15038
+ if (partial.type !== void 0) entry.type = partial.type;
15039
+ if (partial.duration_minutes !== void 0) entry.duration_minutes = partial.duration_minutes;
15040
+ if (partial.symbols_touched !== void 0) entry.symbols_touched = partial.symbols_touched;
15041
+ if (partial.symbols_created !== void 0) entry.symbols_created = partial.symbols_created;
15042
+ if (partial.files_created !== void 0) entry.files_created = partial.files_created;
15043
+ if (partial.files_modified !== void 0) entry.files_modified = partial.files_modified;
15044
+ if (partial.lines_added !== void 0) entry.lines_added = partial.lines_added;
15045
+ if (partial.lines_removed !== void 0) entry.lines_removed = partial.lines_removed;
15046
+ if (partial.commit !== void 0) entry.commit = partial.commit;
15047
+ if (partial.decisions !== void 0) entry.decisions = partial.decisions;
15048
+ if (partial.errors_encountered !== void 0) entry.errors_encountered = partial.errors_encountered;
15049
+ if (partial.learnings !== void 0) entry.learnings = partial.learnings;
15050
+ if (partial.verification !== void 0) entry.verification = partial.verification;
15051
+ if (partial.tags !== void 0) entry.tags = partial.tags;
15052
+ fs25.writeFileSync(entryPath, yaml15.dump(entry, { lineWidth: -1, noRefs: true }));
15053
+ await rebuildTimeline(rootDir);
15054
+ return true;
15055
+ }
15056
+ async function deleteLoreEntry(rootDir, entryId) {
15057
+ const entry = await loadLoreEntry(rootDir, entryId);
15058
+ if (!entry) return false;
15059
+ const dateStr = entry.timestamp.slice(0, 10);
15060
+ const entryPath = path28.join(rootDir, LORE_DIR, ENTRIES_DIR, dateStr, `${entryId}.yaml`);
15061
+ if (!fs25.existsSync(entryPath)) return false;
15062
+ fs25.unlinkSync(entryPath);
15063
+ const dateDir = path28.dirname(entryPath);
15064
+ const remaining = fs25.readdirSync(dateDir).filter((f) => f.endsWith(".yaml"));
15065
+ if (remaining.length === 0) {
15066
+ fs25.rmdirSync(dateDir);
15067
+ }
15068
+ await rebuildTimeline(rootDir);
15069
+ return true;
15070
+ }
14890
15071
  function applyFilter(entries, filter) {
14891
15072
  let result = entries;
14892
15073
  if (filter.author) {
@@ -14923,11 +15104,11 @@ function applyFilter(entries, filter) {
14923
15104
  return result;
14924
15105
  }
14925
15106
  function generateLoreId(rootDir, dateStr) {
14926
- const datePath = path27.join(rootDir, LORE_DIR, ENTRIES_DIR, dateStr);
14927
- if (!fs24.existsSync(datePath)) {
15107
+ const datePath = path28.join(rootDir, LORE_DIR, ENTRIES_DIR, dateStr);
15108
+ if (!fs25.existsSync(datePath)) {
14928
15109
  return `L-${dateStr}-001`;
14929
15110
  }
14930
- const existing = fs24.readdirSync(datePath).filter((f) => f.startsWith("L-") && f.endsWith(".yaml")).map((f) => {
15111
+ const existing = fs25.readdirSync(datePath).filter((f) => f.startsWith("L-") && f.endsWith(".yaml")).map((f) => {
14931
15112
  const match = f.match(/L-\d{4}-\d{2}-\d{2}-(\d+)\.yaml/);
14932
15113
  return match ? parseInt(match[1], 10) : 0;
14933
15114
  });
@@ -15109,6 +15290,109 @@ function getLoreToolsList() {
15109
15290
  readOnlyHint: true,
15110
15291
  destructiveHint: false
15111
15292
  }
15293
+ },
15294
+ {
15295
+ name: "paradigm_lore_get",
15296
+ description: "Fetch a single lore entry by ID. Returns the full entry with all fields.",
15297
+ inputSchema: {
15298
+ type: "object",
15299
+ properties: {
15300
+ id: {
15301
+ type: "string",
15302
+ description: 'Lore entry ID (e.g., "L-2026-02-23-001")'
15303
+ }
15304
+ },
15305
+ required: ["id"]
15306
+ },
15307
+ annotations: {
15308
+ readOnlyHint: true,
15309
+ destructiveHint: false
15310
+ }
15311
+ },
15312
+ {
15313
+ name: "paradigm_lore_update",
15314
+ description: "Update an existing lore entry. Merges provided fields into the existing entry.",
15315
+ inputSchema: {
15316
+ type: "object",
15317
+ properties: {
15318
+ id: {
15319
+ type: "string",
15320
+ description: "Lore entry ID to update"
15321
+ },
15322
+ title: { type: "string", description: "New title" },
15323
+ summary: { type: "string", description: "New summary" },
15324
+ type: {
15325
+ type: "string",
15326
+ enum: ["agent-session", "human-note", "decision", "review", "incident", "milestone"],
15327
+ description: "New entry type"
15328
+ },
15329
+ symbols_touched: {
15330
+ type: "array",
15331
+ items: { type: "string" },
15332
+ description: "Updated symbols list"
15333
+ },
15334
+ symbols_created: {
15335
+ type: "array",
15336
+ items: { type: "string" },
15337
+ description: "Updated created symbols"
15338
+ },
15339
+ files_created: {
15340
+ type: "array",
15341
+ items: { type: "string" }
15342
+ },
15343
+ files_modified: {
15344
+ type: "array",
15345
+ items: { type: "string" }
15346
+ },
15347
+ lines_added: { type: "number" },
15348
+ lines_removed: { type: "number" },
15349
+ commit: { type: "string" },
15350
+ duration_minutes: { type: "number" },
15351
+ learnings: {
15352
+ type: "array",
15353
+ items: { type: "string" },
15354
+ description: "Updated learnings"
15355
+ },
15356
+ verification: {
15357
+ type: "object",
15358
+ properties: {
15359
+ status: { type: "string", enum: ["pass", "fail", "partial", "untested"] },
15360
+ details: { type: "object" }
15361
+ }
15362
+ },
15363
+ tags: {
15364
+ type: "array",
15365
+ items: { type: "string" }
15366
+ }
15367
+ },
15368
+ required: ["id"]
15369
+ },
15370
+ annotations: {
15371
+ readOnlyHint: false,
15372
+ destructiveHint: false
15373
+ }
15374
+ },
15375
+ {
15376
+ name: "paradigm_lore_delete",
15377
+ description: "Delete a lore entry. Requires explicit confirmation to prevent accidental deletion.",
15378
+ inputSchema: {
15379
+ type: "object",
15380
+ properties: {
15381
+ id: {
15382
+ type: "string",
15383
+ description: "Lore entry ID to delete"
15384
+ },
15385
+ confirm: {
15386
+ type: "boolean",
15387
+ description: "Must be true to proceed with deletion"
15388
+ }
15389
+ },
15390
+ required: ["id", "confirm"]
15391
+ },
15392
+ annotations: {
15393
+ readOnlyHint: false,
15394
+ destructiveHint: true
15395
+ }
15112
15396
  }
15113
15397
  ];
15114
15398
  }
@@ -15199,6 +15483,7 @@ async function handleLoreTool(name, args, ctx) {
15199
15483
  habit_compliance
15200
15484
  };
15201
15485
  const id = await recordLoreEntry(ctx.rootDir, entry);
15486
+ getSessionTracker().setLastLoreEntryId(id);
15202
15487
  return {
15203
15488
  handled: true,
15204
15489
  text: JSON.stringify({
@@ -15247,6 +15532,61 @@ async function handleLoreTool(name, args, ctx) {
15247
15532
  }, null, 2)
15248
15533
  };
15249
15534
  }
15535
+ case "paradigm_lore_get": {
15536
+ const id = args.id;
15537
+ const entry = await loadLoreEntry(ctx.rootDir, id);
15538
+ if (!entry) {
15539
+ return {
15540
+ handled: true,
15541
+ text: JSON.stringify({ error: `Lore entry not found: ${id}` })
15542
+ };
15543
+ }
15544
+ return {
15545
+ handled: true,
15546
+ text: JSON.stringify(entry, null, 2)
15547
+ };
15548
+ }
15549
+ case "paradigm_lore_update": {
15550
+ const id = args.id;
15551
+ const { id: _, ...rest } = args;
15552
+ const partial = {};
15553
+ for (const [key, value] of Object.entries(rest)) {
15554
+ if (value !== void 0) {
15555
+ partial[key] = value;
15556
+ }
15557
+ }
15558
+ const success = await updateLoreEntry(ctx.rootDir, id, partial);
15559
+ return {
15560
+ handled: true,
15561
+ text: JSON.stringify({
15562
+ success,
15563
+ id,
15564
+ message: success ? "Lore entry updated" : `Lore entry not found: ${id}`
15565
+ })
15566
+ };
15567
+ }
15568
+ case "paradigm_lore_delete": {
15569
+ const id = args.id;
15570
+ const confirm = args.confirm;
15571
+ if (!confirm) {
15572
+ return {
15573
+ handled: true,
15574
+ text: JSON.stringify({
15575
+ success: false,
15576
+ message: "Deletion requires confirm: true"
15577
+ })
15578
+ };
15579
+ }
15580
+ const success = await deleteLoreEntry(ctx.rootDir, id);
15581
+ return {
15582
+ handled: true,
15583
+ text: JSON.stringify({
15584
+ success,
15585
+ id,
15586
+ message: success ? "Lore entry deleted" : `Lore entry not found: ${id}`
15587
+ })
15588
+ };
15589
+ }
15250
15590
  default:
15251
15591
  return { handled: false, text: "" };
15252
15592
  }
@@ -15271,10 +15611,38 @@ function summarizeEntry(entry) {
15271
15611
  }
15272
15612
 
15273
15613
  // ../paradigm-mcp/src/tools/habits.ts
15274
- import * as fs25 from "fs";
15275
- import * as path28 from "path";
15614
+ import * as fs26 from "fs";
15615
+ import * as path29 from "path";
15616
+ import { execSync as execSync2 } from "child_process";
15276
15617
  function getHabitsToolsList() {
15277
15618
  return [
15619
+ {
15620
+ name: "paradigm_habits_list",
15621
+ description: "List all habit definitions: seed (built-in), global (~/.paradigm/habits.yaml), and project (.paradigm/habits.yaml). Shows what habits exist, their triggers, severity, and enabled state. Use to discover available habits before evaluating them.",
15622
+ inputSchema: {
15623
+ type: "object",
15624
+ properties: {
15625
+ trigger: {
15626
+ type: "string",
15627
+ enum: ["preflight", "postflight", "on-commit", "on-stop"],
15628
+ description: "Filter by trigger point"
15629
+ },
15630
+ category: {
15631
+ type: "string",
15632
+ enum: ["discovery", "verification", "testing", "documentation", "collaboration", "security"],
15633
+ description: "Filter by category"
15634
+ },
15635
+ enabled: {
15636
+ type: "boolean",
15637
+ description: "Filter by enabled state (default: show all)"
15638
+ }
15639
+ }
15640
+ },
15641
+ annotations: {
15642
+ readOnlyHint: true,
15643
+ destructiveHint: false
15644
+ }
15645
+ },
15278
15646
  {
15279
15647
  name: "paradigm_habits_check",
15280
15648
  description: "Evaluate habit compliance for the current session and record practice events. Call at preflight (before implementing), postflight (after implementing), or on-stop (session end). Returns which habits were followed, skipped, or partially met.",
@@ -15283,7 +15651,7 @@ function getHabitsToolsList() {
15283
15651
  properties: {
15284
15652
  trigger: {
15285
15653
  type: "string",
15286
- enum: ["preflight", "postflight", "on-stop"],
15654
+ enum: ["preflight", "postflight", "on-stop", "on-commit"],
15287
15655
  description: "When to evaluate: preflight (before task), postflight (after task), on-stop (session end)"
15288
15656
  },
15289
15657
  filesModified: {
@@ -15361,6 +15729,11 @@ function getHabitsToolsList() {
15361
15729
  }
15362
15730
  async function handleHabitsTool(name, args, ctx) {
15363
15731
  switch (name) {
15732
+ case "paradigm_habits_list": {
15733
+ const result = handleHabitsList(args, ctx);
15734
+ trackToolCall(result.length, name);
15735
+ return { text: result, handled: true };
15736
+ }
15364
15737
  case "paradigm_habits_check": {
15365
15738
  const result = await handleHabitsCheck(args, ctx);
15366
15739
  trackToolCall(result.length, name);
@@ -15380,6 +15753,45 @@ async function handleHabitsTool(name, args, ctx) {
15380
15753
  return { text: "", handled: false };
15381
15754
  }
15382
15755
  }
15756
+ function handleHabitsList(args, ctx) {
15757
+ const triggerFilter = args.trigger;
15758
+ const categoryFilter = args.category;
15759
+ const enabledFilter = args.enabled;
15760
+ let habits = loadHabits(ctx.rootDir);
15761
+ if (triggerFilter) habits = habits.filter((h) => h.trigger === triggerFilter);
15762
+ if (categoryFilter) habits = habits.filter((h) => h.category === categoryFilter);
15763
+ if (enabledFilter !== void 0) habits = habits.filter((h) => h.enabled === enabledFilter);
15764
+ const byTrigger = {};
15765
+ for (const h of habits) {
15766
+ if (!byTrigger[h.trigger]) byTrigger[h.trigger] = [];
15767
+ byTrigger[h.trigger].push(h);
15768
+ }
15769
+ return JSON.stringify(
15770
+ {
15771
+ total: habits.length,
15772
+ filters: Object.fromEntries(
15773
+ Object.entries({ trigger: triggerFilter, category: categoryFilter, enabled: enabledFilter }).filter(([, v]) => v !== void 0)
15774
+ ),
15775
+ byTrigger: Object.fromEntries(
15776
+ Object.entries(byTrigger).map(([trigger, list]) => [
15777
+ trigger,
15778
+ list.map((h) => ({
15779
+ id: h.id,
15780
+ name: h.name,
15781
+ description: h.description,
15782
+ category: h.category,
15783
+ severity: h.severity,
15784
+ enabled: h.enabled,
15785
+ check: { type: h.check.type, params: h.check.params },
15786
+ platforms: h.platforms || null
15787
+ }))
15788
+ ])
15789
+ )
15790
+ },
15791
+ null,
15792
+ 2
15793
+ );
15794
+ }
15383
15795
  async function handleHabitsCheck(args, ctx) {
15384
15796
  const trigger = args.trigger;
15385
15797
  const filesModified = args.filesModified || [];
@@ -15403,6 +15815,16 @@ async function handleHabitsCheck(args, ctx) {
15403
15815
  "patch",
15404
15816
  "delete"
15405
15817
  ].some((k) => taskLower.includes(k));
15818
+ let gitClean;
15819
+ try {
15820
+ const status = execSync2("git status --porcelain", {
15821
+ cwd: ctx.rootDir,
15822
+ encoding: "utf8",
15823
+ timeout: 5e3
15824
+ });
15825
+ gitClean = status.trim() === "";
15826
+ } catch {
15827
+ }
15406
15828
  const evalContext = buildEvaluationContext({
15407
15829
  toolsCalled,
15408
15830
  filesModified,
@@ -15410,12 +15832,15 @@ async function handleHabitsCheck(args, ctx) {
15410
15832
  loreRecorded,
15411
15833
  hasPortalRoutes: ctx.gateConfig !== null && ctx.gateConfig.routes != null,
15412
15834
  taskAddsRoutes,
15413
- taskDescription
15835
+ taskDescription,
15836
+ gitClean
15414
15837
  });
15415
- const evaluation = evaluateHabits(habits, trigger, evalContext);
15838
+ const platform2 = "claude";
15839
+ const evaluation = evaluateHabits(habits, trigger, evalContext, platform2);
15416
15840
  let recordedIds = [];
15417
15841
  if (shouldRecord && evaluation.evaluations.length > 0) {
15418
15842
  try {
15843
+ const loreEntryId = tracker2.getLastLoreEntryId() ?? void 0;
15419
15844
  recordedIds = await recordEvaluationResults(
15420
15845
  ctx.rootDir,
15421
15846
  evaluation.evaluations.map((e) => ({
@@ -15427,6 +15852,7 @@ async function handleHabitsCheck(args, ctx) {
15427
15852
  {
15428
15853
  engineer: "agent",
15429
15854
  sessionId: stats.sessionId,
15855
+ loreEntryId,
15430
15856
  taskDescription,
15431
15857
  symbolsTouched,
15432
15858
  filesModified
@@ -15435,13 +15861,13 @@ async function handleHabitsCheck(args, ctx) {
15435
15861
  } catch {
15436
15862
  }
15437
15863
  }
15438
- const markerPath = path28.join(ctx.rootDir, ".paradigm", ".habits-blocking");
15864
+ const markerPath = path29.join(ctx.rootDir, ".paradigm", ".habits-blocking");
15439
15865
  try {
15440
15866
  if (trigger === "on-stop" && evaluation.blocksCompletion) {
15441
15867
  const blocking = evaluation.evaluations.filter((e) => e.result === "skipped" && e.habit.severity === "block").map((e) => `${e.habit.name}: ${e.reason}`);
15442
- fs25.writeFileSync(markerPath, blocking.join("\n"), "utf8");
15868
+ fs26.writeFileSync(markerPath, blocking.join("\n"), "utf8");
15443
15869
  } else if (trigger === "on-stop") {
15444
- if (fs25.existsSync(markerPath)) fs25.unlinkSync(markerPath);
15870
+ if (fs26.existsSync(markerPath)) fs26.unlinkSync(markerPath);
15445
15871
  }
15446
15872
  } catch {
15447
15873
  }
@@ -15499,6 +15925,12 @@ function buildRecommendations(evaluation) {
15499
15925
  case "purpose-coverage":
15500
15926
  recs.push(`Update .purpose files using paradigm_purpose_add_component.`);
15501
15927
  break;
15928
+ case "changelog-updated":
15929
+ recs.push(`Update CHANGELOG.md with the changes made in this phase.`);
15930
+ break;
15931
+ case "changes-committed":
15932
+ recs.push(`Commit all changes to git before finishing this phase.`);
15933
+ break;
15502
15934
  default:
15503
15935
  recs.push(`${e.habit.name}: ${e.reason}`);
15504
15936
  }
@@ -15648,8 +16080,8 @@ async function handlePracticeContext(args, ctx) {
15648
16080
  }
15649
16081
 
15650
16082
  // ../paradigm-mcp/src/tools/fallback-grep.ts
15651
- import * as path29 from "path";
15652
- import { execSync } from "child_process";
16083
+ import * as path30 from "path";
16084
+ import { execSync as execSync3 } from "child_process";
15653
16085
  function grepForReferences(rootDir, symbol, options = {}) {
15654
16086
  const { maxResults = 20 } = options;
15655
16087
  const results = [];
@@ -15663,7 +16095,7 @@ function grepForReferences(rootDir, symbol, options = {}) {
15663
16095
  let output = "";
15664
16096
  for (const cmd of grepCommands) {
15665
16097
  try {
15666
- output = execSync(cmd, { encoding: "utf8", maxBuffer: 1024 * 1024 });
16098
+ output = execSync3(cmd, { encoding: "utf8", maxBuffer: 1024 * 1024 });
15667
16099
  if (output.trim()) break;
15668
16100
  } catch {
15669
16101
  continue;
@@ -15677,7 +16109,7 @@ function grepForReferences(rootDir, symbol, options = {}) {
15677
16109
  const match = line.match(/^(.+?):(\d+):(.*)$/);
15678
16110
  if (match) {
15679
16111
  const [, filePath, lineNum, content] = match;
15680
- const relativePath = path29.relative(rootDir, filePath);
16112
+ const relativePath = path30.relative(rootDir, filePath);
15681
16113
  let context2 = "unknown";
15682
16114
  if (relativePath.includes(".purpose") || relativePath.includes("portal.yaml")) {
15683
16115
  context2 = "purpose";