@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/{chunk-LRSJNX7K.js → chunk-6HZ7PZGG.js} +118 -100
- package/dist/{chunk-X3ROB27T.js → chunk-MVXJVRFI.js} +44 -1
- package/dist/delete-W67IVTLJ.js +45 -0
- package/dist/edit-Y7XPYSMK.js +63 -0
- package/dist/habits-FA65W77Y.js +1153 -0
- package/dist/{hooks-ZVGXLK6Z.js → hooks-QGUF77MB.js} +1 -1
- package/dist/index.js +51 -19
- package/dist/{list-SDYF6T7M.js → list-R3QWW4SC.js} +3 -1
- package/dist/{lore-server-3TAIUZ3Y.js → lore-server-RQH5REZV.js} +166 -41
- package/dist/mcp.js +559 -127
- package/dist/{record-5IY5RWTI.js → record-OHQNWOUP.js} +7 -2
- package/dist/{review-CSA223ZJ.js → review-RUHX25A5.js} +1 -1
- package/dist/{serve-WCIRW244.js → serve-H7ZBMODT.js} +1 -1
- package/dist/{shift-DLV5YLFJ.js → shift-OCNF4575.js} +1 -1
- package/dist/{show-QRV7CWKT.js → show-WTOJXUTN.js} +1 -1
- package/dist/timeline-P7BARFLI.js +110 -0
- package/dist/university-content/courses/para-401.json +1 -1
- package/dist/university-content/courses/para-501.json +4 -4
- package/lore-ui/dist/assets/index-BB3P4Cok.js +56 -0
- package/lore-ui/dist/assets/index-DI0Q6NmX.css +1 -0
- package/lore-ui/dist/index.html +2 -2
- package/package.json +3 -2
- package/dist/habits-YVCOZ2LC.js +0 -485
- package/lore-ui/dist/assets/index-DcT8TINz.js +0 -56
- package/lore-ui/dist/assets/index-DyJhpQ5w.css +0 -1
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
|
|
528
|
+
var fs27 = __require("fs");
|
|
529
529
|
za = __dirname + "/";
|
|
530
530
|
Ba = (a) => {
|
|
531
531
|
a = Ca(a) ? new URL(a) : a;
|
|
532
|
-
return
|
|
532
|
+
return fs27.readFileSync(a);
|
|
533
533
|
};
|
|
534
534
|
Aa = async (a) => {
|
|
535
535
|
a = Ca(a) ? new URL(a) : a;
|
|
536
|
-
return
|
|
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 =
|
|
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
|
|
4282
|
-
const firstPart =
|
|
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,
|
|
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
|
|
13456
|
-
import * as
|
|
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
|
|
13460
|
-
import * as
|
|
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 =
|
|
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(
|
|
13615
|
+
const globalConfig = loadHabitsYaml(path24.join(home, ".paradigm", "habits.yaml"));
|
|
13583
13616
|
if (globalConfig) mergeHabits(habitsById, globalConfig);
|
|
13584
|
-
const projectConfig = loadHabitsYaml(
|
|
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 (!
|
|
13622
|
+
if (!fs22.existsSync(filePath)) return null;
|
|
13590
13623
|
try {
|
|
13591
|
-
const content =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
14010
|
+
let habitsEvaluation = null;
|
|
13946
14011
|
let recentCompliance = null;
|
|
13947
14012
|
try {
|
|
13948
14013
|
const habits = loadHabits(ctx.rootDir);
|
|
13949
|
-
const
|
|
13950
|
-
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
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
|
-
|
|
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 =
|
|
14004
|
-
if (!
|
|
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 =
|
|
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 ${
|
|
14026
|
-
file:
|
|
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:
|
|
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 =
|
|
14093
|
-
if (!
|
|
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
|
|
14215
|
+
let habitsEvaluation = null;
|
|
14133
14216
|
try {
|
|
14134
14217
|
const habits = loadHabits(ctx.rootDir);
|
|
14135
|
-
const
|
|
14136
|
-
const
|
|
14137
|
-
const
|
|
14138
|
-
|
|
14139
|
-
|
|
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
|
-
|
|
14276
|
+
habitsEvaluation
|
|
14154
14277
|
};
|
|
14155
14278
|
}
|
|
14156
14279
|
|
|
14157
14280
|
// ../paradigm-mcp/src/tools/reindex.ts
|
|
14158
|
-
import * as
|
|
14159
|
-
import * as
|
|
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 ||
|
|
14499
|
-
const paradigmDir =
|
|
14500
|
-
if (!
|
|
14501
|
-
|
|
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 =
|
|
14512
|
-
|
|
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 =
|
|
14516
|
-
|
|
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 =
|
|
14526
|
-
|
|
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) =>
|
|
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) =>
|
|
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 =
|
|
14583
|
-
if (
|
|
14705
|
+
const gitignorePath = path27.join(rootDir, ".gitignore");
|
|
14706
|
+
if (fs24.existsSync(gitignorePath)) {
|
|
14584
14707
|
try {
|
|
14585
|
-
const content =
|
|
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 =
|
|
14757
|
+
const dir = path27.dirname(pf);
|
|
14635
14758
|
return dir.toLowerCase().includes(symbol.id.toLowerCase());
|
|
14636
14759
|
});
|
|
14637
14760
|
if (matchingPurpose) {
|
|
14638
|
-
symbolMap[symbolId] =
|
|
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 =
|
|
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:
|
|
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:
|
|
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
|
|
14733
|
-
import * as
|
|
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 =
|
|
14740
|
-
if (!
|
|
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 =
|
|
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 =
|
|
14750
|
-
const files =
|
|
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 =
|
|
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 =
|
|
14764
|
-
if (!
|
|
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 =
|
|
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 =
|
|
14915
|
+
const lorePath = path28.join(rootDir, LORE_DIR);
|
|
14776
14916
|
const dateStr = entry.timestamp.slice(0, 10);
|
|
14777
|
-
const datePath =
|
|
14778
|
-
if (!
|
|
14779
|
-
|
|
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 =
|
|
14785
|
-
|
|
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 =
|
|
14791
|
-
const entriesPath =
|
|
14792
|
-
if (!
|
|
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 =
|
|
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 =
|
|
14800
|
-
const files =
|
|
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 =
|
|
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 =
|
|
14816
|
-
if (
|
|
14955
|
+
const configPath = path28.join(rootDir, ".paradigm", "config.yaml");
|
|
14956
|
+
if (fs25.existsSync(configPath)) {
|
|
14817
14957
|
try {
|
|
14818
|
-
const config = yaml15.load(
|
|
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 (!
|
|
14831
|
-
|
|
14970
|
+
if (!fs25.existsSync(lorePath)) {
|
|
14971
|
+
fs25.mkdirSync(lorePath, { recursive: true });
|
|
14832
14972
|
}
|
|
14833
|
-
|
|
14834
|
-
|
|
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 =
|
|
14840
|
-
if (!
|
|
14841
|
-
const rootFiles =
|
|
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 =
|
|
14845
|
-
const stat =
|
|
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 =
|
|
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 =
|
|
14853
|
-
if (!
|
|
14854
|
-
|
|
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
|
-
|
|
14880
|
-
|
|
15019
|
+
fs25.writeFileSync(
|
|
15020
|
+
path28.join(datePath, `${id}.yaml`),
|
|
14881
15021
|
yaml15.dump(v2Entry, { lineWidth: -1, noRefs: true })
|
|
14882
15022
|
);
|
|
14883
|
-
|
|
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 =
|
|
14927
|
-
if (!
|
|
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 =
|
|
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
|
|
15275
|
-
import * as
|
|
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
|
|
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 =
|
|
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
|
-
|
|
15868
|
+
fs26.writeFileSync(markerPath, blocking.join("\n"), "utf8");
|
|
15443
15869
|
} else if (trigger === "on-stop") {
|
|
15444
|
-
if (
|
|
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
|
|
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 =
|
|
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 =
|
|
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";
|