@a-company/paradigm 3.20.2 → 3.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{habits-KD4RLIN2.js → habits-7BORPC2F.js} +31 -8
- package/dist/index.js +10 -10
- package/dist/mcp.js +416 -14
- package/package.json +1 -1
|
@@ -263,22 +263,45 @@ function loadHabitsFresh(rootDir) {
|
|
|
263
263
|
for (const seed of SEED_HABITS) {
|
|
264
264
|
habitsById.set(seed.id, { ...seed });
|
|
265
265
|
}
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
".paradigm",
|
|
269
|
-
"habits.yaml"
|
|
270
|
-
);
|
|
271
|
-
const globalConfig = loadHabitsYaml(globalHabitsPath);
|
|
266
|
+
const home = process.env.HOME || process.env.USERPROFILE || "~";
|
|
267
|
+
const globalConfig = loadHabitsYaml(path.join(home, ".paradigm", "habits.yaml"));
|
|
272
268
|
if (globalConfig) {
|
|
273
269
|
mergeHabits(habitsById, globalConfig);
|
|
274
270
|
}
|
|
275
|
-
const
|
|
276
|
-
const
|
|
271
|
+
const globalHabitFiles = loadHabitFiles(path.join(home, ".paradigm", "habits"));
|
|
272
|
+
for (const habit of globalHabitFiles) {
|
|
273
|
+
habitsById.set(habit.id, habit);
|
|
274
|
+
}
|
|
275
|
+
const projectConfig = loadHabitsYaml(path.join(rootDir, ".paradigm", "habits.yaml"));
|
|
277
276
|
if (projectConfig) {
|
|
278
277
|
mergeHabits(habitsById, projectConfig);
|
|
279
278
|
}
|
|
279
|
+
const projectHabitFiles = loadHabitFiles(path.join(rootDir, ".paradigm", "habits"));
|
|
280
|
+
for (const habit of projectHabitFiles) {
|
|
281
|
+
habitsById.set(habit.id, habit);
|
|
282
|
+
}
|
|
280
283
|
return Array.from(habitsById.values());
|
|
281
284
|
}
|
|
285
|
+
function loadHabitFiles(dir) {
|
|
286
|
+
if (!fs.existsSync(dir)) return [];
|
|
287
|
+
try {
|
|
288
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".habit")).sort();
|
|
289
|
+
const habits = [];
|
|
290
|
+
for (const file of files) {
|
|
291
|
+
try {
|
|
292
|
+
const content = fs.readFileSync(path.join(dir, file), "utf8");
|
|
293
|
+
const habit = yaml.load(content);
|
|
294
|
+
if (habit?.id && habit?.name) {
|
|
295
|
+
habits.push(habit);
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return habits;
|
|
301
|
+
} catch {
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
282
305
|
function loadHabitsYaml(filePath) {
|
|
283
306
|
if (!fs.existsSync(filePath)) {
|
|
284
307
|
return null;
|
package/dist/index.js
CHANGED
|
@@ -662,43 +662,43 @@ loreCmd.option("-p, --port <port>", "Port to run on", "3840").option("--no-open"
|
|
|
662
662
|
});
|
|
663
663
|
var habitsCmd = program.command("habits").description("Behavioral habits - practice tracking and compliance");
|
|
664
664
|
habitsCmd.command("list").alias("ls").description("List all configured habits").option("--trigger <trigger>", "Filter by trigger: preflight, postflight, on-stop, on-commit").option("--category <category>", "Filter by category: discovery, verification, testing, documentation, collaboration, security").option("--json", "Output as JSON").action(async (options) => {
|
|
665
|
-
const { habitsListCommand } = await import("./habits-
|
|
665
|
+
const { habitsListCommand } = await import("./habits-7BORPC2F.js");
|
|
666
666
|
await habitsListCommand(options);
|
|
667
667
|
});
|
|
668
668
|
habitsCmd.command("status").description("Show practice profile with compliance rates").option("-p, --period <period>", "Time period: 7d, 30d, 90d, all", "30d").option("--json", "Output as JSON").action(async (options) => {
|
|
669
|
-
const { habitsStatusCommand } = await import("./habits-
|
|
669
|
+
const { habitsStatusCommand } = await import("./habits-7BORPC2F.js");
|
|
670
670
|
await habitsStatusCommand(options);
|
|
671
671
|
});
|
|
672
672
|
habitsCmd.command("init").description("Initialize habits.yaml with seed habits").option("-f, --force", "Overwrite existing file").action(async (options) => {
|
|
673
|
-
const { habitsInitCommand } = await import("./habits-
|
|
673
|
+
const { habitsInitCommand } = await import("./habits-7BORPC2F.js");
|
|
674
674
|
await habitsInitCommand(options);
|
|
675
675
|
});
|
|
676
676
|
habitsCmd.command("check").description("Evaluate habit compliance for a trigger point").requiredOption("-t, --trigger <trigger>", "Trigger: preflight, postflight, on-stop, on-commit").option("--record", "Record practice events to Sentinel").option("--json", "Output as JSON").option("--files <files>", "Comma-separated files modified (default: git diff)").option("--symbols <symbols>", "Comma-separated symbols touched").action(async (options) => {
|
|
677
|
-
const { habitsCheckCommand } = await import("./habits-
|
|
677
|
+
const { habitsCheckCommand } = await import("./habits-7BORPC2F.js");
|
|
678
678
|
await habitsCheckCommand(options);
|
|
679
679
|
});
|
|
680
680
|
habitsCmd.command("add").description("Add a custom habit").requiredOption("--id <id>", "Habit ID (kebab-case)").requiredOption("--name <name>", "Human-readable name").requiredOption("--description <desc>", "What this habit enforces").requiredOption("--category <category>", "Category: discovery, verification, testing, documentation, collaboration, security").requiredOption("--trigger <trigger>", "Trigger: preflight, postflight, on-stop, on-commit").option("--severity <severity>", "Severity: advisory, warn, block", "advisory").option("--tools <tools>", "Comma-separated tools to check (for tool-called check type)").option("--check-type <type>", "Check type: tool-called, file-exists, file-modified, lore-recorded, symbols-registered, gates-declared, tests-exist, git-clean", "tool-called").option("--patterns <patterns>", "Comma-separated patterns (for file-exists, file-modified, tests-exist check types)").action(async (options) => {
|
|
681
|
-
const { habitsAddCommand } = await import("./habits-
|
|
681
|
+
const { habitsAddCommand } = await import("./habits-7BORPC2F.js");
|
|
682
682
|
await habitsAddCommand({ ...options, checkType: options.checkType });
|
|
683
683
|
});
|
|
684
684
|
habitsCmd.command("edit <id>").description("Edit a habit (seed habits: only severity/enabled; custom: all fields)").option("--name <name>", "New name").option("--description <desc>", "New description").option("--category <category>", "New category").option("--trigger <trigger>", "New trigger").option("--severity <severity>", "New severity: advisory, warn, block").option("--enabled <bool>", "Enable or disable: true, false").option("--check-type <type>", "New check type").option("--patterns <patterns>", "Comma-separated patterns").option("--tools <tools>", "Comma-separated tools").action(async (id, options) => {
|
|
685
|
-
const { habitsEditCommand } = await import("./habits-
|
|
685
|
+
const { habitsEditCommand } = await import("./habits-7BORPC2F.js");
|
|
686
686
|
await habitsEditCommand(id, { ...options, checkType: options.checkType });
|
|
687
687
|
});
|
|
688
688
|
habitsCmd.command("remove <id>").description("Remove a custom habit (seed habits cannot be removed, only disabled)").option("-y, --yes", "Skip confirmation").action(async (id, options) => {
|
|
689
|
-
const { habitsRemoveCommand } = await import("./habits-
|
|
689
|
+
const { habitsRemoveCommand } = await import("./habits-7BORPC2F.js");
|
|
690
690
|
await habitsRemoveCommand(id, options);
|
|
691
691
|
});
|
|
692
692
|
habitsCmd.command("enable <id>").description("Enable a habit").action(async (id) => {
|
|
693
|
-
const { habitsToggleCommand } = await import("./habits-
|
|
693
|
+
const { habitsToggleCommand } = await import("./habits-7BORPC2F.js");
|
|
694
694
|
await habitsToggleCommand(id, "enable");
|
|
695
695
|
});
|
|
696
696
|
habitsCmd.command("disable <id>").description("Disable a habit").action(async (id) => {
|
|
697
|
-
const { habitsToggleCommand } = await import("./habits-
|
|
697
|
+
const { habitsToggleCommand } = await import("./habits-7BORPC2F.js");
|
|
698
698
|
await habitsToggleCommand(id, "disable");
|
|
699
699
|
});
|
|
700
700
|
habitsCmd.action(async () => {
|
|
701
|
-
const { habitsListCommand } = await import("./habits-
|
|
701
|
+
const { habitsListCommand } = await import("./habits-7BORPC2F.js");
|
|
702
702
|
await habitsListCommand({});
|
|
703
703
|
});
|
|
704
704
|
var sentinelCmd = program.command("sentinel").description("Sentinel \u2014 semantic error monitoring");
|
package/dist/mcp.js
CHANGED
|
@@ -7543,8 +7543,16 @@ function loadHabitsFresh(rootDir) {
|
|
|
7543
7543
|
const home = process.env.HOME || process.env.USERPROFILE || "~";
|
|
7544
7544
|
const globalConfig = loadHabitsYaml(path18.join(home, ".paradigm", "habits.yaml"));
|
|
7545
7545
|
if (globalConfig) mergeHabits(habitsById, globalConfig);
|
|
7546
|
+
const globalHabitFiles = loadHabitFiles(path18.join(home, ".paradigm", "habits"));
|
|
7547
|
+
for (const habit of globalHabitFiles) {
|
|
7548
|
+
habitsById.set(habit.id, habit);
|
|
7549
|
+
}
|
|
7546
7550
|
const projectConfig = loadHabitsYaml(path18.join(rootDir, ".paradigm", "habits.yaml"));
|
|
7547
7551
|
if (projectConfig) mergeHabits(habitsById, projectConfig);
|
|
7552
|
+
const projectHabitFiles = loadHabitFiles(path18.join(rootDir, ".paradigm", "habits"));
|
|
7553
|
+
for (const habit of projectHabitFiles) {
|
|
7554
|
+
habitsById.set(habit.id, habit);
|
|
7555
|
+
}
|
|
7548
7556
|
return Array.from(habitsById.values());
|
|
7549
7557
|
}
|
|
7550
7558
|
function loadHabitsYaml(filePath) {
|
|
@@ -7575,6 +7583,141 @@ function mergeHabits(habitsById, config) {
|
|
|
7575
7583
|
function getHabitsByTrigger(habits, trigger) {
|
|
7576
7584
|
return habits.filter((h) => h.enabled && h.trigger === trigger);
|
|
7577
7585
|
}
|
|
7586
|
+
function invalidateHabitsCache(rootDir) {
|
|
7587
|
+
habitsCache.delete(path18.resolve(rootDir));
|
|
7588
|
+
}
|
|
7589
|
+
function loadHabitFiles(dir) {
|
|
7590
|
+
if (!fs17.existsSync(dir)) return [];
|
|
7591
|
+
try {
|
|
7592
|
+
const files = fs17.readdirSync(dir).filter((f) => f.endsWith(".habit")).sort();
|
|
7593
|
+
const habits = [];
|
|
7594
|
+
for (const file of files) {
|
|
7595
|
+
try {
|
|
7596
|
+
const content = fs17.readFileSync(path18.join(dir, file), "utf8");
|
|
7597
|
+
const habit = yaml11.load(content);
|
|
7598
|
+
if (habit?.id && habit?.name) {
|
|
7599
|
+
habits.push(habit);
|
|
7600
|
+
}
|
|
7601
|
+
} catch {
|
|
7602
|
+
}
|
|
7603
|
+
}
|
|
7604
|
+
return habits;
|
|
7605
|
+
} catch {
|
|
7606
|
+
return [];
|
|
7607
|
+
}
|
|
7608
|
+
}
|
|
7609
|
+
var VALID_CATEGORIES = [
|
|
7610
|
+
"discovery",
|
|
7611
|
+
"verification",
|
|
7612
|
+
"testing",
|
|
7613
|
+
"documentation",
|
|
7614
|
+
"collaboration",
|
|
7615
|
+
"security"
|
|
7616
|
+
];
|
|
7617
|
+
var VALID_TRIGGERS = [
|
|
7618
|
+
"preflight",
|
|
7619
|
+
"postflight",
|
|
7620
|
+
"on-commit",
|
|
7621
|
+
"on-stop"
|
|
7622
|
+
];
|
|
7623
|
+
var VALID_SEVERITIES = ["advisory", "warn", "block"];
|
|
7624
|
+
var VALID_CHECK_TYPES = [
|
|
7625
|
+
"tool-called",
|
|
7626
|
+
"file-exists",
|
|
7627
|
+
"file-modified",
|
|
7628
|
+
"lore-recorded",
|
|
7629
|
+
"symbols-registered",
|
|
7630
|
+
"gates-declared",
|
|
7631
|
+
"tests-exist",
|
|
7632
|
+
"git-clean",
|
|
7633
|
+
"commit-message-format",
|
|
7634
|
+
"flow-coverage",
|
|
7635
|
+
"context-checked",
|
|
7636
|
+
"aspect-anchored"
|
|
7637
|
+
];
|
|
7638
|
+
var KEBAB_CASE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
7639
|
+
function validateHabitDefinition(habit) {
|
|
7640
|
+
const errors = [];
|
|
7641
|
+
if (!habit.id) errors.push("Missing required field: id");
|
|
7642
|
+
if (!habit.name) errors.push("Missing required field: name");
|
|
7643
|
+
if (!habit.description) errors.push("Missing required field: description");
|
|
7644
|
+
if (!habit.category) errors.push("Missing required field: category");
|
|
7645
|
+
if (!habit.trigger) errors.push("Missing required field: trigger");
|
|
7646
|
+
if (!habit.severity) errors.push("Missing required field: severity");
|
|
7647
|
+
if (!habit.check) errors.push("Missing required field: check");
|
|
7648
|
+
if (habit.enabled === void 0 || habit.enabled === null) errors.push("Missing required field: enabled");
|
|
7649
|
+
if (habit.id && !KEBAB_CASE_RE.test(habit.id)) {
|
|
7650
|
+
errors.push(`Invalid id format: "${habit.id}" \u2014 must be kebab-case (lowercase, hyphens only)`);
|
|
7651
|
+
}
|
|
7652
|
+
if (habit.category && !VALID_CATEGORIES.includes(habit.category)) {
|
|
7653
|
+
errors.push(`Invalid category: "${habit.category}" \u2014 must be one of: ${VALID_CATEGORIES.join(", ")}`);
|
|
7654
|
+
}
|
|
7655
|
+
if (habit.trigger && !VALID_TRIGGERS.includes(habit.trigger)) {
|
|
7656
|
+
errors.push(`Invalid trigger: "${habit.trigger}" \u2014 must be one of: ${VALID_TRIGGERS.join(", ")}`);
|
|
7657
|
+
}
|
|
7658
|
+
if (habit.severity && !VALID_SEVERITIES.includes(habit.severity)) {
|
|
7659
|
+
errors.push(`Invalid severity: "${habit.severity}" \u2014 must be one of: ${VALID_SEVERITIES.join(", ")}`);
|
|
7660
|
+
}
|
|
7661
|
+
if (habit.check) {
|
|
7662
|
+
if (!VALID_CHECK_TYPES.includes(habit.check.type)) {
|
|
7663
|
+
errors.push(`Invalid check.type: "${habit.check.type}" \u2014 must be one of: ${VALID_CHECK_TYPES.join(", ")}`);
|
|
7664
|
+
}
|
|
7665
|
+
const params = habit.check.params || {};
|
|
7666
|
+
switch (habit.check.type) {
|
|
7667
|
+
case "tool-called":
|
|
7668
|
+
if (!params.tools || !Array.isArray(params.tools) || params.tools.length === 0) {
|
|
7669
|
+
errors.push('check.type "tool-called" requires check.params.tools[] (non-empty array)');
|
|
7670
|
+
}
|
|
7671
|
+
break;
|
|
7672
|
+
case "file-exists":
|
|
7673
|
+
case "file-modified":
|
|
7674
|
+
if (!params.patterns || !Array.isArray(params.patterns) || params.patterns.length === 0) {
|
|
7675
|
+
errors.push(`check.type "${habit.check.type}" requires check.params.patterns[] (non-empty array)`);
|
|
7676
|
+
}
|
|
7677
|
+
break;
|
|
7678
|
+
case "commit-message-format":
|
|
7679
|
+
if (!params.messagePatterns || !Array.isArray(params.messagePatterns) || params.messagePatterns.length === 0) {
|
|
7680
|
+
errors.push('check.type "commit-message-format" requires check.params.messagePatterns[] (non-empty array)');
|
|
7681
|
+
}
|
|
7682
|
+
break;
|
|
7683
|
+
}
|
|
7684
|
+
}
|
|
7685
|
+
return { valid: errors.length === 0, errors };
|
|
7686
|
+
}
|
|
7687
|
+
var SEED_HABIT_IDS = new Set(SEED_HABITS.map((h) => h.id));
|
|
7688
|
+
function isSeedHabit(id) {
|
|
7689
|
+
return SEED_HABIT_IDS.has(id);
|
|
7690
|
+
}
|
|
7691
|
+
function saveHabit(rootDir, habit, scope = "project") {
|
|
7692
|
+
const baseDir = scope === "global" ? path18.join(process.env.HOME || process.env.USERPROFILE || "~", ".paradigm", "habits") : path18.join(rootDir, ".paradigm", "habits");
|
|
7693
|
+
if (!fs17.existsSync(baseDir)) {
|
|
7694
|
+
fs17.mkdirSync(baseDir, { recursive: true });
|
|
7695
|
+
}
|
|
7696
|
+
const filePath = path18.join(baseDir, `${habit.id}.habit`);
|
|
7697
|
+
const content = yaml11.dump(habit, { lineWidth: 120, noRefs: true });
|
|
7698
|
+
fs17.writeFileSync(filePath, content, "utf8");
|
|
7699
|
+
invalidateHabitsCache(rootDir);
|
|
7700
|
+
return filePath;
|
|
7701
|
+
}
|
|
7702
|
+
function removeHabit(rootDir, id) {
|
|
7703
|
+
if (isSeedHabit(id)) {
|
|
7704
|
+
return { removed: false, reason: `"${id}" is a seed habit and cannot be removed. Use overrides in habits.yaml to disable it.` };
|
|
7705
|
+
}
|
|
7706
|
+
const projectPath = path18.join(rootDir, ".paradigm", "habits", `${id}.habit`);
|
|
7707
|
+
if (fs17.existsSync(projectPath)) {
|
|
7708
|
+
fs17.unlinkSync(projectPath);
|
|
7709
|
+
invalidateHabitsCache(rootDir);
|
|
7710
|
+
return { removed: true };
|
|
7711
|
+
}
|
|
7712
|
+
const home = process.env.HOME || process.env.USERPROFILE || "~";
|
|
7713
|
+
const globalPath = path18.join(home, ".paradigm", "habits", `${id}.habit`);
|
|
7714
|
+
if (fs17.existsSync(globalPath)) {
|
|
7715
|
+
fs17.unlinkSync(globalPath);
|
|
7716
|
+
invalidateHabitsCache(rootDir);
|
|
7717
|
+
return { removed: true };
|
|
7718
|
+
}
|
|
7719
|
+
return { removed: false, reason: `No .habit file found for "${id}". It may be defined in habits.yaml \u2014 edit that file directly.` };
|
|
7720
|
+
}
|
|
7578
7721
|
function evaluateHabits(habits, trigger, context2, platform2) {
|
|
7579
7722
|
let activeHabits = getHabitsByTrigger(habits, trigger);
|
|
7580
7723
|
if (platform2) {
|
|
@@ -8786,6 +8929,7 @@ function summarizeEntry(entry) {
|
|
|
8786
8929
|
// ../paradigm-mcp/src/tools/habits.ts
|
|
8787
8930
|
import * as fs19 from "fs";
|
|
8788
8931
|
import * as path21 from "path";
|
|
8932
|
+
import * as yaml12 from "js-yaml";
|
|
8789
8933
|
import { execSync as execSync3 } from "child_process";
|
|
8790
8934
|
function getHabitsToolsList() {
|
|
8791
8935
|
return [
|
|
@@ -8897,6 +9041,128 @@ function getHabitsToolsList() {
|
|
|
8897
9041
|
readOnlyHint: true,
|
|
8898
9042
|
destructiveHint: false
|
|
8899
9043
|
}
|
|
9044
|
+
},
|
|
9045
|
+
{
|
|
9046
|
+
name: "paradigm_habits_add",
|
|
9047
|
+
description: 'Create a new custom habit as an individual .habit file. Validates all fields and checks for ID collisions with seed habits. Use scope "global" for ~/.paradigm/habits/ or "project" (default) for .paradigm/habits/. ~150 tokens.',
|
|
9048
|
+
inputSchema: {
|
|
9049
|
+
type: "object",
|
|
9050
|
+
properties: {
|
|
9051
|
+
id: { type: "string", description: 'Unique habit ID in kebab-case (e.g. "check-changelog")' },
|
|
9052
|
+
name: { type: "string", description: "Human-readable habit name" },
|
|
9053
|
+
description: { type: "string", description: "What this habit checks and why" },
|
|
9054
|
+
category: {
|
|
9055
|
+
type: "string",
|
|
9056
|
+
enum: ["discovery", "verification", "testing", "documentation", "collaboration", "security"],
|
|
9057
|
+
description: "Habit category"
|
|
9058
|
+
},
|
|
9059
|
+
trigger: {
|
|
9060
|
+
type: "string",
|
|
9061
|
+
enum: ["preflight", "postflight", "on-commit", "on-stop"],
|
|
9062
|
+
description: "When the habit is evaluated"
|
|
9063
|
+
},
|
|
9064
|
+
severity: {
|
|
9065
|
+
type: "string",
|
|
9066
|
+
enum: ["advisory", "warn", "block"],
|
|
9067
|
+
description: "How strictly to enforce (block prevents session completion)"
|
|
9068
|
+
},
|
|
9069
|
+
check: {
|
|
9070
|
+
type: "object",
|
|
9071
|
+
description: "Check definition with type and params",
|
|
9072
|
+
properties: {
|
|
9073
|
+
type: {
|
|
9074
|
+
type: "string",
|
|
9075
|
+
enum: [
|
|
9076
|
+
"tool-called",
|
|
9077
|
+
"file-exists",
|
|
9078
|
+
"file-modified",
|
|
9079
|
+
"lore-recorded",
|
|
9080
|
+
"symbols-registered",
|
|
9081
|
+
"gates-declared",
|
|
9082
|
+
"tests-exist",
|
|
9083
|
+
"git-clean",
|
|
9084
|
+
"commit-message-format",
|
|
9085
|
+
"flow-coverage",
|
|
9086
|
+
"context-checked",
|
|
9087
|
+
"aspect-anchored"
|
|
9088
|
+
]
|
|
9089
|
+
},
|
|
9090
|
+
params: { type: "object", description: "Check-specific parameters (tools[], patterns[], etc.)" }
|
|
9091
|
+
},
|
|
9092
|
+
required: ["type", "params"]
|
|
9093
|
+
},
|
|
9094
|
+
enabled: { type: "boolean", description: "Whether the habit is active (default: true)" },
|
|
9095
|
+
platforms: {
|
|
9096
|
+
type: "array",
|
|
9097
|
+
items: { type: "string" },
|
|
9098
|
+
description: 'Platforms this habit applies to (e.g. ["claude", "cursor"]). Omit for all.'
|
|
9099
|
+
},
|
|
9100
|
+
scope: {
|
|
9101
|
+
type: "string",
|
|
9102
|
+
enum: ["project", "global"],
|
|
9103
|
+
description: 'Where to save: "project" (default) or "global" (~/.paradigm/habits/)'
|
|
9104
|
+
}
|
|
9105
|
+
},
|
|
9106
|
+
required: ["id", "name", "description", "category", "trigger", "severity", "check"]
|
|
9107
|
+
},
|
|
9108
|
+
annotations: {
|
|
9109
|
+
readOnlyHint: false,
|
|
9110
|
+
destructiveHint: false
|
|
9111
|
+
}
|
|
9112
|
+
},
|
|
9113
|
+
{
|
|
9114
|
+
name: "paradigm_habits_edit",
|
|
9115
|
+
description: "Update fields on an existing custom .habit file. Cannot edit seed habits \u2014 use overrides in habits.yaml instead. Merges provided fields with existing definition and re-validates. ~150 tokens.",
|
|
9116
|
+
inputSchema: {
|
|
9117
|
+
type: "object",
|
|
9118
|
+
properties: {
|
|
9119
|
+
id: { type: "string", description: "ID of the habit to edit" },
|
|
9120
|
+
name: { type: "string", description: "Updated name" },
|
|
9121
|
+
description: { type: "string", description: "Updated description" },
|
|
9122
|
+
category: {
|
|
9123
|
+
type: "string",
|
|
9124
|
+
enum: ["discovery", "verification", "testing", "documentation", "collaboration", "security"]
|
|
9125
|
+
},
|
|
9126
|
+
trigger: {
|
|
9127
|
+
type: "string",
|
|
9128
|
+
enum: ["preflight", "postflight", "on-commit", "on-stop"]
|
|
9129
|
+
},
|
|
9130
|
+
severity: {
|
|
9131
|
+
type: "string",
|
|
9132
|
+
enum: ["advisory", "warn", "block"]
|
|
9133
|
+
},
|
|
9134
|
+
check: {
|
|
9135
|
+
type: "object",
|
|
9136
|
+
properties: {
|
|
9137
|
+
type: { type: "string" },
|
|
9138
|
+
params: { type: "object" }
|
|
9139
|
+
},
|
|
9140
|
+
required: ["type", "params"]
|
|
9141
|
+
},
|
|
9142
|
+
enabled: { type: "boolean" },
|
|
9143
|
+
platforms: { type: "array", items: { type: "string" } }
|
|
9144
|
+
},
|
|
9145
|
+
required: ["id"]
|
|
9146
|
+
},
|
|
9147
|
+
annotations: {
|
|
9148
|
+
readOnlyHint: false,
|
|
9149
|
+
destructiveHint: false
|
|
9150
|
+
}
|
|
9151
|
+
},
|
|
9152
|
+
{
|
|
9153
|
+
name: "paradigm_habits_remove",
|
|
9154
|
+
description: "Delete a custom .habit file. Cannot remove seed habits \u2014 use overrides to disable them instead. Searches both project and global habit directories. ~100 tokens.",
|
|
9155
|
+
inputSchema: {
|
|
9156
|
+
type: "object",
|
|
9157
|
+
properties: {
|
|
9158
|
+
id: { type: "string", description: "ID of the habit to remove" }
|
|
9159
|
+
},
|
|
9160
|
+
required: ["id"]
|
|
9161
|
+
},
|
|
9162
|
+
annotations: {
|
|
9163
|
+
readOnlyHint: false,
|
|
9164
|
+
destructiveHint: true
|
|
9165
|
+
}
|
|
8900
9166
|
}
|
|
8901
9167
|
];
|
|
8902
9168
|
}
|
|
@@ -8922,6 +9188,21 @@ async function handleHabitsTool(name, args, ctx) {
|
|
|
8922
9188
|
trackToolCall(result.length, name);
|
|
8923
9189
|
return { text: result, handled: true };
|
|
8924
9190
|
}
|
|
9191
|
+
case "paradigm_habits_add": {
|
|
9192
|
+
const result = handleHabitsAdd(args, ctx);
|
|
9193
|
+
trackToolCall(result.length, name);
|
|
9194
|
+
return { text: result, handled: true };
|
|
9195
|
+
}
|
|
9196
|
+
case "paradigm_habits_edit": {
|
|
9197
|
+
const result = handleHabitsEdit(args, ctx);
|
|
9198
|
+
trackToolCall(result.length, name);
|
|
9199
|
+
return { text: result, handled: true };
|
|
9200
|
+
}
|
|
9201
|
+
case "paradigm_habits_remove": {
|
|
9202
|
+
const result = handleHabitsRemove(args, ctx);
|
|
9203
|
+
trackToolCall(result.length, name);
|
|
9204
|
+
return { text: result, handled: true };
|
|
9205
|
+
}
|
|
8925
9206
|
default:
|
|
8926
9207
|
return { text: "", handled: false };
|
|
8927
9208
|
}
|
|
@@ -9111,6 +9392,127 @@ function buildRecommendations(evaluation) {
|
|
|
9111
9392
|
}
|
|
9112
9393
|
return [...new Set(recs)];
|
|
9113
9394
|
}
|
|
9395
|
+
function handleHabitsAdd(args, ctx) {
|
|
9396
|
+
const id = args.id;
|
|
9397
|
+
const scope = args.scope || "project";
|
|
9398
|
+
if (isSeedHabit(id)) {
|
|
9399
|
+
return JSON.stringify({
|
|
9400
|
+
error: true,
|
|
9401
|
+
message: `Cannot create habit "${id}" \u2014 it collides with a seed habit. Choose a different ID or use overrides in habits.yaml to customize the seed habit.`
|
|
9402
|
+
}, null, 2);
|
|
9403
|
+
}
|
|
9404
|
+
const existingHabits = loadHabits(ctx.rootDir);
|
|
9405
|
+
const existing = existingHabits.find((h) => h.id === id);
|
|
9406
|
+
if (existing) {
|
|
9407
|
+
return JSON.stringify({
|
|
9408
|
+
error: true,
|
|
9409
|
+
message: `Habit "${id}" already exists. Use paradigm_habits_edit to update it, or choose a different ID.`
|
|
9410
|
+
}, null, 2);
|
|
9411
|
+
}
|
|
9412
|
+
const habit = {
|
|
9413
|
+
id,
|
|
9414
|
+
name: args.name,
|
|
9415
|
+
description: args.description,
|
|
9416
|
+
category: args.category,
|
|
9417
|
+
trigger: args.trigger,
|
|
9418
|
+
severity: args.severity,
|
|
9419
|
+
check: args.check,
|
|
9420
|
+
enabled: args.enabled !== void 0 ? args.enabled : true
|
|
9421
|
+
};
|
|
9422
|
+
if (args.platforms) habit.platforms = args.platforms;
|
|
9423
|
+
const validation = validateHabitDefinition(habit);
|
|
9424
|
+
if (!validation.valid) {
|
|
9425
|
+
return JSON.stringify({
|
|
9426
|
+
error: true,
|
|
9427
|
+
message: "Validation failed",
|
|
9428
|
+
errors: validation.errors
|
|
9429
|
+
}, null, 2);
|
|
9430
|
+
}
|
|
9431
|
+
const filePath = saveHabit(ctx.rootDir, habit, scope);
|
|
9432
|
+
return JSON.stringify({
|
|
9433
|
+
created: true,
|
|
9434
|
+
id: habit.id,
|
|
9435
|
+
filePath,
|
|
9436
|
+
scope,
|
|
9437
|
+
message: `Habit "${habit.name}" created at ${filePath}`
|
|
9438
|
+
}, null, 2);
|
|
9439
|
+
}
|
|
9440
|
+
function handleHabitsEdit(args, ctx) {
|
|
9441
|
+
const id = args.id;
|
|
9442
|
+
if (isSeedHabit(id)) {
|
|
9443
|
+
return JSON.stringify({
|
|
9444
|
+
error: true,
|
|
9445
|
+
message: `Cannot edit "${id}" \u2014 it is a seed habit. To customize it, add an override in .paradigm/habits.yaml under the "overrides:" key.`
|
|
9446
|
+
}, null, 2);
|
|
9447
|
+
}
|
|
9448
|
+
const projectPath = path21.join(ctx.rootDir, ".paradigm", "habits", `${id}.habit`);
|
|
9449
|
+
const home = process.env.HOME || process.env.USERPROFILE || "~";
|
|
9450
|
+
const globalPath = path21.join(home, ".paradigm", "habits", `${id}.habit`);
|
|
9451
|
+
let filePath = null;
|
|
9452
|
+
let scope = "project";
|
|
9453
|
+
if (fs19.existsSync(projectPath)) {
|
|
9454
|
+
filePath = projectPath;
|
|
9455
|
+
scope = "project";
|
|
9456
|
+
} else if (fs19.existsSync(globalPath)) {
|
|
9457
|
+
filePath = globalPath;
|
|
9458
|
+
scope = "global";
|
|
9459
|
+
}
|
|
9460
|
+
if (!filePath) {
|
|
9461
|
+
return JSON.stringify({
|
|
9462
|
+
error: true,
|
|
9463
|
+
message: `No .habit file found for "${id}". It may be defined in habits.yaml \u2014 edit that file directly.`
|
|
9464
|
+
}, null, 2);
|
|
9465
|
+
}
|
|
9466
|
+
let existing;
|
|
9467
|
+
try {
|
|
9468
|
+
const content = fs19.readFileSync(filePath, "utf8");
|
|
9469
|
+
existing = yaml12.load(content);
|
|
9470
|
+
} catch {
|
|
9471
|
+
return JSON.stringify({
|
|
9472
|
+
error: true,
|
|
9473
|
+
message: `Failed to read ${filePath}`
|
|
9474
|
+
}, null, 2);
|
|
9475
|
+
}
|
|
9476
|
+
const updated = { ...existing };
|
|
9477
|
+
if (args.name !== void 0) updated.name = args.name;
|
|
9478
|
+
if (args.description !== void 0) updated.description = args.description;
|
|
9479
|
+
if (args.category !== void 0) updated.category = args.category;
|
|
9480
|
+
if (args.trigger !== void 0) updated.trigger = args.trigger;
|
|
9481
|
+
if (args.severity !== void 0) updated.severity = args.severity;
|
|
9482
|
+
if (args.check !== void 0) updated.check = args.check;
|
|
9483
|
+
if (args.enabled !== void 0) updated.enabled = args.enabled;
|
|
9484
|
+
if (args.platforms !== void 0) updated.platforms = args.platforms;
|
|
9485
|
+
const validation = validateHabitDefinition(updated);
|
|
9486
|
+
if (!validation.valid) {
|
|
9487
|
+
return JSON.stringify({
|
|
9488
|
+
error: true,
|
|
9489
|
+
message: "Validation failed after merge",
|
|
9490
|
+
errors: validation.errors
|
|
9491
|
+
}, null, 2);
|
|
9492
|
+
}
|
|
9493
|
+
saveHabit(ctx.rootDir, updated, scope);
|
|
9494
|
+
return JSON.stringify({
|
|
9495
|
+
updated: true,
|
|
9496
|
+
id: updated.id,
|
|
9497
|
+
filePath,
|
|
9498
|
+
message: `Habit "${updated.name}" updated`
|
|
9499
|
+
}, null, 2);
|
|
9500
|
+
}
|
|
9501
|
+
function handleHabitsRemove(args, ctx) {
|
|
9502
|
+
const id = args.id;
|
|
9503
|
+
const result = removeHabit(ctx.rootDir, id);
|
|
9504
|
+
if (result.removed) {
|
|
9505
|
+
return JSON.stringify({
|
|
9506
|
+
removed: true,
|
|
9507
|
+
id,
|
|
9508
|
+
message: `Habit "${id}" removed`
|
|
9509
|
+
}, null, 2);
|
|
9510
|
+
}
|
|
9511
|
+
return JSON.stringify({
|
|
9512
|
+
error: true,
|
|
9513
|
+
message: result.reason
|
|
9514
|
+
}, null, 2);
|
|
9515
|
+
}
|
|
9114
9516
|
async function handleHabitsStatus(args, ctx) {
|
|
9115
9517
|
const engineer = args.engineer;
|
|
9116
9518
|
const period = args.period || "30d";
|
|
@@ -10366,7 +10768,7 @@ async function handleTasksTool(name, args, ctx) {
|
|
|
10366
10768
|
// ../paradigm-mcp/src/utils/assessment-loader.ts
|
|
10367
10769
|
import * as fs21 from "fs";
|
|
10368
10770
|
import * as path23 from "path";
|
|
10369
|
-
import * as
|
|
10771
|
+
import * as yaml13 from "js-yaml";
|
|
10370
10772
|
var ASSESSMENTS_DIR = ".paradigm/assessments";
|
|
10371
10773
|
var ARCS_DIR = "arcs";
|
|
10372
10774
|
var INDEX_FILE = "index.yaml";
|
|
@@ -10379,7 +10781,7 @@ function computeArcStats(rootDir, arc) {
|
|
|
10379
10781
|
const files = fs21.readdirSync(entriesPath).filter((f) => f.endsWith(".yaml"));
|
|
10380
10782
|
for (const file of files) {
|
|
10381
10783
|
try {
|
|
10382
|
-
const entry =
|
|
10784
|
+
const entry = yaml13.load(fs21.readFileSync(path23.join(entriesPath, file), "utf8"));
|
|
10383
10785
|
entryCount++;
|
|
10384
10786
|
if (entry.symbols) entry.symbols.forEach((s) => symbolSet.add(s));
|
|
10385
10787
|
if (!latestDate || entry.date > latestDate) latestDate = entry.date;
|
|
@@ -10404,7 +10806,7 @@ async function loadArcs(rootDir, status) {
|
|
|
10404
10806
|
const arcFile = path23.join(arcsPath, arcDir, "arc.yaml");
|
|
10405
10807
|
if (!fs21.existsSync(arcFile)) continue;
|
|
10406
10808
|
try {
|
|
10407
|
-
const arc =
|
|
10809
|
+
const arc = yaml13.load(fs21.readFileSync(arcFile, "utf8"));
|
|
10408
10810
|
if (status && status !== "all" && arc.status !== status) continue;
|
|
10409
10811
|
arcs.push(computeArcStats(rootDir, arc));
|
|
10410
10812
|
} catch {
|
|
@@ -10421,7 +10823,7 @@ async function loadArc(rootDir, arcId) {
|
|
|
10421
10823
|
const arcFile = path23.join(rootDir, ASSESSMENTS_DIR, ARCS_DIR, arcId, "arc.yaml");
|
|
10422
10824
|
if (!fs21.existsSync(arcFile)) return null;
|
|
10423
10825
|
try {
|
|
10424
|
-
const arc =
|
|
10826
|
+
const arc = yaml13.load(fs21.readFileSync(arcFile, "utf8"));
|
|
10425
10827
|
return computeArcStats(rootDir, arc);
|
|
10426
10828
|
} catch {
|
|
10427
10829
|
return null;
|
|
@@ -10431,9 +10833,9 @@ async function closeArc(rootDir, arcId, status) {
|
|
|
10431
10833
|
const arcFile = path23.join(rootDir, ASSESSMENTS_DIR, ARCS_DIR, arcId, "arc.yaml");
|
|
10432
10834
|
if (!fs21.existsSync(arcFile)) return false;
|
|
10433
10835
|
try {
|
|
10434
|
-
const arc =
|
|
10836
|
+
const arc = yaml13.load(fs21.readFileSync(arcFile, "utf8"));
|
|
10435
10837
|
arc.status = status;
|
|
10436
|
-
fs21.writeFileSync(arcFile,
|
|
10838
|
+
fs21.writeFileSync(arcFile, yaml13.dump(arc, { lineWidth: -1, noRefs: true }));
|
|
10437
10839
|
await rebuildAssessmentIndex(rootDir);
|
|
10438
10840
|
return true;
|
|
10439
10841
|
} catch {
|
|
@@ -10447,7 +10849,7 @@ async function loadEntries(rootDir, arcId) {
|
|
|
10447
10849
|
const files = fs21.readdirSync(entriesPath).filter((f) => f.endsWith(".yaml")).sort();
|
|
10448
10850
|
for (const file of files) {
|
|
10449
10851
|
try {
|
|
10450
|
-
const entry =
|
|
10852
|
+
const entry = yaml13.load(fs21.readFileSync(path23.join(entriesPath, file), "utf8"));
|
|
10451
10853
|
entries.push(entry);
|
|
10452
10854
|
} catch {
|
|
10453
10855
|
}
|
|
@@ -10469,9 +10871,9 @@ async function loadEntry(rootDir, entryId) {
|
|
|
10469
10871
|
const entryFile = path23.join(arcsPath, arcDir, "entries", `${entryId}.yaml`);
|
|
10470
10872
|
if (fs21.existsSync(entryFile)) {
|
|
10471
10873
|
try {
|
|
10472
|
-
const entry =
|
|
10874
|
+
const entry = yaml13.load(fs21.readFileSync(entryFile, "utf8"));
|
|
10473
10875
|
const arcFile = path23.join(arcsPath, arcDir, "arc.yaml");
|
|
10474
|
-
const arc =
|
|
10876
|
+
const arc = yaml13.load(fs21.readFileSync(arcFile, "utf8"));
|
|
10475
10877
|
return { entry, arc };
|
|
10476
10878
|
} catch {
|
|
10477
10879
|
return null;
|
|
@@ -10498,7 +10900,7 @@ async function searchEntries(rootDir, filter) {
|
|
|
10498
10900
|
const files = fs21.readdirSync(entriesPath).filter((f) => f.endsWith(".yaml"));
|
|
10499
10901
|
for (const file of files) {
|
|
10500
10902
|
try {
|
|
10501
|
-
const entry =
|
|
10903
|
+
const entry = yaml13.load(fs21.readFileSync(path23.join(entriesPath, file), "utf8"));
|
|
10502
10904
|
if (filter.symbol && !(entry.symbols || []).includes(filter.symbol)) continue;
|
|
10503
10905
|
if (filter.tag && !(entry.tags || []).includes(filter.tag)) continue;
|
|
10504
10906
|
if (filter.type && entry.type !== filter.type) continue;
|
|
@@ -10529,7 +10931,7 @@ async function rebuildAssessmentIndex(rootDir) {
|
|
|
10529
10931
|
const arcFile = path23.join(arcsPath, arcDir, "arc.yaml");
|
|
10530
10932
|
if (!fs21.existsSync(arcFile)) continue;
|
|
10531
10933
|
try {
|
|
10532
|
-
const arc =
|
|
10934
|
+
const arc = yaml13.load(fs21.readFileSync(arcFile, "utf8"));
|
|
10533
10935
|
const entriesPath = path23.join(arcsPath, arcDir, "entries");
|
|
10534
10936
|
const entryCount = fs21.existsSync(entriesPath) ? fs21.readdirSync(entriesPath).filter((f) => f.endsWith(".yaml")).length : 0;
|
|
10535
10937
|
totalArcs++;
|
|
@@ -10549,7 +10951,7 @@ async function rebuildAssessmentIndex(rootDir) {
|
|
|
10549
10951
|
arcs: arcSummaries
|
|
10550
10952
|
};
|
|
10551
10953
|
fs21.mkdirSync(assessmentsPath, { recursive: true });
|
|
10552
|
-
fs21.writeFileSync(path23.join(assessmentsPath, INDEX_FILE),
|
|
10954
|
+
fs21.writeFileSync(path23.join(assessmentsPath, INDEX_FILE), yaml13.dump(index, { lineWidth: -1, noRefs: true }));
|
|
10553
10955
|
return index;
|
|
10554
10956
|
}
|
|
10555
10957
|
|
|
@@ -11159,7 +11561,7 @@ async function runChain(rootDir, chainId, options) {
|
|
|
11159
11561
|
const start = Date.now();
|
|
11160
11562
|
const fs22 = await import("fs");
|
|
11161
11563
|
const path26 = await import("path");
|
|
11162
|
-
const
|
|
11564
|
+
const yaml14 = await import("js-yaml");
|
|
11163
11565
|
const chainPath = path26.join(rootDir, ".paradigm", "personas", "chains", `${chainId}.yaml`);
|
|
11164
11566
|
if (!fs22.existsSync(chainPath)) {
|
|
11165
11567
|
return {
|
|
@@ -11170,7 +11572,7 @@ async function runChain(rootDir, chainId, options) {
|
|
|
11170
11572
|
duration_ms: Date.now() - start
|
|
11171
11573
|
};
|
|
11172
11574
|
}
|
|
11173
|
-
const chain =
|
|
11575
|
+
const chain = yaml14.load(fs22.readFileSync(chainPath, "utf8"));
|
|
11174
11576
|
let permutation;
|
|
11175
11577
|
if (options.permutation && chain.permutations) {
|
|
11176
11578
|
permutation = chain.permutations.find((p) => p.id === options.permutation);
|