@a-company/paradigm 3.0.2 → 3.1.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.
@@ -0,0 +1,485 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-MO4EEYFW.js";
3
+
4
+ // src/commands/habits/index.ts
5
+ import * as fs2 from "fs";
6
+ import * as path2 from "path";
7
+ import chalk from "chalk";
8
+ import * as yaml2 from "js-yaml";
9
+
10
+ // src/core/habits/loader.ts
11
+ import * as fs from "fs";
12
+ import * as path from "path";
13
+ import * as yaml from "js-yaml";
14
+
15
+ // src/core/habits/seed-habits.json
16
+ var seed_habits_default = [
17
+ {
18
+ id: "explore-before-implement",
19
+ name: "Explore Before Implementing",
20
+ description: "Call ripple/navigate/search before modifying existing symbols to understand impact",
21
+ category: "discovery",
22
+ trigger: "preflight",
23
+ severity: "advisory",
24
+ check: {
25
+ type: "tool-called",
26
+ params: {
27
+ tools: ["paradigm_ripple", "paradigm_navigate", "paradigm_search", "paradigm_related"]
28
+ }
29
+ },
30
+ enabled: true
31
+ },
32
+ {
33
+ id: "ripple-before-modify",
34
+ name: "Ripple Before Modifying",
35
+ description: "Run ripple analysis before modifying symbols with dependents",
36
+ category: "discovery",
37
+ trigger: "preflight",
38
+ severity: "advisory",
39
+ check: {
40
+ type: "tool-called",
41
+ params: {
42
+ tools: ["paradigm_ripple"]
43
+ }
44
+ },
45
+ enabled: true
46
+ },
47
+ {
48
+ id: "check-fragility",
49
+ name: "Check Fragility",
50
+ description: "Check history fragility for symbols before modifying frequently-broken code",
51
+ category: "discovery",
52
+ trigger: "preflight",
53
+ severity: "advisory",
54
+ check: {
55
+ type: "tool-called",
56
+ params: {
57
+ tools: ["paradigm_history_fragility"]
58
+ }
59
+ },
60
+ enabled: true
61
+ },
62
+ {
63
+ id: "wisdom-before-implement",
64
+ name: "Check Team Wisdom",
65
+ description: "Check team wisdom (preferences, antipatterns, decisions) before implementing",
66
+ category: "collaboration",
67
+ trigger: "preflight",
68
+ severity: "advisory",
69
+ check: {
70
+ type: "tool-called",
71
+ params: {
72
+ tools: ["paradigm_wisdom_context", "paradigm_wisdom_expert"]
73
+ }
74
+ },
75
+ enabled: true
76
+ },
77
+ {
78
+ id: "verify-before-done",
79
+ name: "Verify Before Done",
80
+ description: "Run postflight compliance checks before finishing a session",
81
+ category: "verification",
82
+ trigger: "on-stop",
83
+ severity: "warn",
84
+ check: {
85
+ type: "tool-called",
86
+ params: {
87
+ tools: ["paradigm_pm_postflight"]
88
+ }
89
+ },
90
+ enabled: true
91
+ },
92
+ {
93
+ id: "postflight-compliance",
94
+ name: "Postflight Compliance",
95
+ description: "Ensure postflight checks pass without errors before finishing",
96
+ category: "verification",
97
+ trigger: "on-stop",
98
+ severity: "advisory",
99
+ check: {
100
+ type: "tool-called",
101
+ params: {
102
+ tools: ["paradigm_pm_postflight", "paradigm_reindex"]
103
+ }
104
+ },
105
+ enabled: true
106
+ },
107
+ {
108
+ id: "test-new-components",
109
+ name: "Test New Components",
110
+ description: "New components should have associated tests or test plan documented",
111
+ category: "testing",
112
+ trigger: "postflight",
113
+ severity: "advisory",
114
+ check: {
115
+ type: "tests-exist",
116
+ params: {
117
+ patterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**"]
118
+ }
119
+ },
120
+ enabled: true
121
+ },
122
+ {
123
+ id: "purpose-coverage",
124
+ name: "Purpose File Coverage",
125
+ description: "All modified source directories should have .purpose file coverage",
126
+ category: "documentation",
127
+ trigger: "postflight",
128
+ severity: "warn",
129
+ check: {
130
+ type: "file-exists",
131
+ params: {
132
+ patterns: ["**/.purpose"]
133
+ }
134
+ },
135
+ enabled: true
136
+ },
137
+ {
138
+ id: "record-lore-for-significant",
139
+ name: "Record Lore for Significant Changes",
140
+ description: "Sessions modifying 3+ files should record a lore entry",
141
+ category: "documentation",
142
+ trigger: "on-stop",
143
+ severity: "warn",
144
+ check: {
145
+ type: "lore-recorded",
146
+ params: {}
147
+ },
148
+ enabled: true
149
+ },
150
+ {
151
+ id: "gates-for-routes",
152
+ name: "Gates for Routes",
153
+ description: "API routes should have corresponding gate declarations in portal.yaml",
154
+ category: "security",
155
+ trigger: "postflight",
156
+ severity: "warn",
157
+ check: {
158
+ type: "gates-declared",
159
+ params: {
160
+ requireRoutes: true
161
+ }
162
+ },
163
+ enabled: true
164
+ }
165
+ ];
166
+
167
+ // src/core/habits/loader.ts
168
+ var SEED_HABITS = seed_habits_default;
169
+ var HABITS_CACHE_TTL_MS = 30 * 1e3;
170
+ var habitsCache = /* @__PURE__ */ new Map();
171
+ function loadHabits(rootDir) {
172
+ const absoluteRoot = path.resolve(rootDir);
173
+ const cached = habitsCache.get(absoluteRoot);
174
+ if (cached && Date.now() - cached.loadedAt < HABITS_CACHE_TTL_MS) {
175
+ return cached.habits;
176
+ }
177
+ const habits = loadHabitsFresh(absoluteRoot);
178
+ habitsCache.set(absoluteRoot, {
179
+ habits,
180
+ loadedAt: Date.now()
181
+ });
182
+ return habits;
183
+ }
184
+ function loadHabitsFresh(rootDir) {
185
+ const habitsById = /* @__PURE__ */ new Map();
186
+ for (const seed of SEED_HABITS) {
187
+ habitsById.set(seed.id, { ...seed });
188
+ }
189
+ const globalHabitsPath = path.join(
190
+ process.env.HOME || process.env.USERPROFILE || "~",
191
+ ".paradigm",
192
+ "habits.yaml"
193
+ );
194
+ const globalConfig = loadHabitsYaml(globalHabitsPath);
195
+ if (globalConfig) {
196
+ mergeHabits(habitsById, globalConfig);
197
+ }
198
+ const projectHabitsPath = path.join(rootDir, ".paradigm", "habits.yaml");
199
+ const projectConfig = loadHabitsYaml(projectHabitsPath);
200
+ if (projectConfig) {
201
+ mergeHabits(habitsById, projectConfig);
202
+ }
203
+ return Array.from(habitsById.values());
204
+ }
205
+ function loadHabitsYaml(filePath) {
206
+ if (!fs.existsSync(filePath)) {
207
+ return null;
208
+ }
209
+ try {
210
+ const content = fs.readFileSync(filePath, "utf8");
211
+ return yaml.load(content);
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+ function mergeHabits(habitsById, config) {
217
+ if (config.habits) {
218
+ for (const habit of config.habits) {
219
+ habitsById.set(habit.id, { ...habit });
220
+ }
221
+ }
222
+ if (config.overrides) {
223
+ for (const [habitId, override] of Object.entries(config.overrides)) {
224
+ const existing = habitsById.get(habitId);
225
+ if (existing) {
226
+ applyOverride(existing, override);
227
+ }
228
+ }
229
+ }
230
+ }
231
+ function applyOverride(habit, override) {
232
+ if (override.severity !== void 0) {
233
+ habit.severity = override.severity;
234
+ }
235
+ if (override.enabled !== void 0) {
236
+ habit.enabled = override.enabled;
237
+ }
238
+ }
239
+ function getEnabledHabits(habits) {
240
+ return habits.filter((h) => h.enabled);
241
+ }
242
+ function invalidateHabitsCache(rootDir) {
243
+ const absoluteRoot = path.resolve(rootDir);
244
+ habitsCache.delete(absoluteRoot);
245
+ }
246
+
247
+ // src/commands/habits/index.ts
248
+ var HABITS_FILE = ".paradigm/habits.yaml";
249
+ async function habitsListCommand(options) {
250
+ const rootDir = process.cwd();
251
+ let habits;
252
+ try {
253
+ habits = loadHabits(rootDir);
254
+ } catch (err) {
255
+ console.log(chalk.red("Failed to load habits:"), err.message);
256
+ return;
257
+ }
258
+ if (options.trigger) {
259
+ habits = habits.filter((h) => h.trigger === options.trigger);
260
+ }
261
+ if (options.category) {
262
+ habits = habits.filter((h) => h.category === options.category);
263
+ }
264
+ if (options.json) {
265
+ console.log(JSON.stringify(habits, null, 2));
266
+ return;
267
+ }
268
+ const enabled = habits.filter((h) => h.enabled);
269
+ const disabled = habits.filter((h) => !h.enabled);
270
+ console.log(chalk.magenta(`
271
+ Habits (${enabled.length} active, ${disabled.length} disabled)
272
+ `));
273
+ const triggers = ["preflight", "postflight", "on-stop", "on-commit"];
274
+ for (const trigger of triggers) {
275
+ const group = habits.filter((h) => h.trigger === trigger);
276
+ if (group.length === 0) continue;
277
+ console.log(chalk.cyan(` ${trigger}:`));
278
+ for (const h of group) {
279
+ const status = h.enabled ? chalk.green("ON") : chalk.gray("OFF");
280
+ const severity = h.severity === "block" ? chalk.red(h.severity) : h.severity === "warn" ? chalk.yellow(h.severity) : chalk.gray(h.severity);
281
+ console.log(` ${status} ${chalk.white(h.id)} [${severity}] - ${h.name}`);
282
+ console.log(chalk.gray(` ${h.description}`));
283
+ }
284
+ console.log();
285
+ }
286
+ const projectConfig = path2.join(rootDir, HABITS_FILE);
287
+ if (fs2.existsSync(projectConfig)) {
288
+ console.log(chalk.gray(` Config: ${HABITS_FILE}`));
289
+ } else {
290
+ console.log(chalk.gray(` Config: using seed habits only (run 'paradigm habits init' to customize)`));
291
+ }
292
+ console.log();
293
+ }
294
+ async function habitsStatusCommand(options) {
295
+ const rootDir = process.cwd();
296
+ let habits;
297
+ try {
298
+ habits = loadHabits(rootDir);
299
+ } catch (err) {
300
+ console.log(chalk.red("Failed to load habits:"), err.message);
301
+ return;
302
+ }
303
+ const enabled = getEnabledHabits(habits);
304
+ let practiceData = null;
305
+ try {
306
+ const { SentinelStorage } = await import("./dist-GPQ4LAY3.js");
307
+ const sentinelDir = path2.join(rootDir, ".paradigm", "sentinel");
308
+ if (fs2.existsSync(sentinelDir)) {
309
+ const storage = new SentinelStorage(sentinelDir);
310
+ const period = options.period || "30d";
311
+ const days = parseInt(period.replace("d", ""), 10) || 30;
312
+ const dateFrom = period === "all" ? void 0 : new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
313
+ const compliance = storage.getComplianceRate({ dateFrom });
314
+ const events = storage.getPracticeEvents({ dateFrom, limit: 500 });
315
+ const catStats = /* @__PURE__ */ new Map();
316
+ for (const event of events) {
317
+ const cat = event.habitCategory;
318
+ const existing = catStats.get(cat) || { followed: 0, skipped: 0, partial: 0 };
319
+ existing[event.result]++;
320
+ catStats.set(cat, existing);
321
+ }
322
+ const byCategory = Array.from(catStats.entries()).map(([category, stats]) => {
323
+ const total = stats.followed + stats.skipped + stats.partial;
324
+ const rate = total > 0 ? Math.round((stats.followed + stats.partial * 0.5) / total * 100) : 100;
325
+ return { category, rate, total };
326
+ }).sort((a, b) => a.rate - b.rate);
327
+ practiceData = {
328
+ total: compliance.total,
329
+ followed: compliance.followed,
330
+ skipped: compliance.skipped,
331
+ partial: compliance.partial,
332
+ rate: compliance.rate,
333
+ byCategory
334
+ };
335
+ }
336
+ } catch {
337
+ }
338
+ if (options.json) {
339
+ console.log(JSON.stringify({
340
+ habits: { total: habits.length, enabled: enabled.length },
341
+ practice: practiceData
342
+ }, null, 2));
343
+ return;
344
+ }
345
+ console.log(chalk.magenta("\n Habits Practice Profile\n"));
346
+ console.log(chalk.white(` Total habits: ${habits.length} (${enabled.length} active)`));
347
+ const byTrigger = /* @__PURE__ */ new Map();
348
+ for (const h of enabled) {
349
+ byTrigger.set(h.trigger, (byTrigger.get(h.trigger) || 0) + 1);
350
+ }
351
+ for (const [trigger, count] of byTrigger) {
352
+ console.log(chalk.gray(` ${trigger}: ${count} habit(s)`));
353
+ }
354
+ console.log();
355
+ if (practiceData && practiceData.total > 0) {
356
+ const rateColor = practiceData.rate >= 80 ? chalk.green : practiceData.rate >= 60 ? chalk.yellow : chalk.red;
357
+ console.log(chalk.white(` Compliance Rate: ${rateColor(`${practiceData.rate}%`)}`));
358
+ console.log(chalk.gray(` Followed: ${practiceData.followed} | Skipped: ${practiceData.skipped} | Partial: ${practiceData.partial}`));
359
+ console.log(chalk.gray(` Total events: ${practiceData.total}
360
+ `));
361
+ if (practiceData.byCategory.length > 0) {
362
+ console.log(chalk.white(" By Category:"));
363
+ for (const cat of practiceData.byCategory) {
364
+ const catColor = cat.rate >= 80 ? chalk.green : cat.rate >= 60 ? chalk.yellow : chalk.red;
365
+ const bar = "\u2588".repeat(Math.round(cat.rate / 5)) + "\u2591".repeat(20 - Math.round(cat.rate / 5));
366
+ console.log(` ${cat.category.padEnd(15)} ${catColor(bar)} ${catColor(`${cat.rate}%`)} (${cat.total})`);
367
+ }
368
+ }
369
+ } else {
370
+ console.log(chalk.gray(" No practice events recorded yet."));
371
+ console.log(chalk.gray(" Call paradigm_habits_check via MCP to start recording.\n"));
372
+ }
373
+ console.log();
374
+ }
375
+ async function habitsInitCommand(options) {
376
+ const rootDir = process.cwd();
377
+ const configPath = path2.join(rootDir, HABITS_FILE);
378
+ if (fs2.existsSync(configPath) && !options.force) {
379
+ console.log(chalk.yellow(`${HABITS_FILE} already exists. Use --force to overwrite.`));
380
+ return;
381
+ }
382
+ const dir = path2.dirname(configPath);
383
+ if (!fs2.existsSync(dir)) {
384
+ fs2.mkdirSync(dir, { recursive: true });
385
+ }
386
+ const defaultConfig = {
387
+ version: "1.0",
388
+ habits: [],
389
+ overrides: {
390
+ "verify-before-done": {
391
+ severity: "warn"
392
+ }
393
+ }
394
+ };
395
+ const content = `# Paradigm Habits Configuration
396
+ # See: paradigm habits list (for all seed habits)
397
+ #
398
+ # Seed habits are built-in and active by default.
399
+ # Use 'overrides' to tune severity or disable specific habits.
400
+ # Add custom habits in the 'habits' section.
401
+
402
+ ${yaml2.dump(defaultConfig, { lineWidth: 80, noRefs: true })}
403
+ # Example custom habit:
404
+ # habits:
405
+ # - id: my-custom-check
406
+ # name: "Custom Check"
407
+ # description: "Describe what this habit enforces"
408
+ # category: verification
409
+ # trigger: postflight
410
+ # severity: advisory
411
+ # check:
412
+ # type: tool-called
413
+ # params:
414
+ # tools: [paradigm_pm_postflight]
415
+ # enabled: true
416
+ #
417
+ # Override seed habits:
418
+ # overrides:
419
+ # verify-before-done:
420
+ # severity: block # Upgrade to blocking
421
+ # check-fragility:
422
+ # enabled: false # Disable this habit
423
+ `;
424
+ fs2.writeFileSync(configPath, content, "utf8");
425
+ invalidateHabitsCache(rootDir);
426
+ console.log(chalk.green(`Created ${HABITS_FILE}`));
427
+ console.log(chalk.gray(" 10 seed habits are active by default."));
428
+ console.log(chalk.gray(" Use overrides section to tune severity or disable habits."));
429
+ console.log(chalk.gray(" Run `paradigm habits list` to see all habits.\n"));
430
+ }
431
+ async function habitsAddCommand(options) {
432
+ const rootDir = process.cwd();
433
+ const configPath = path2.join(rootDir, HABITS_FILE);
434
+ if (!fs2.existsSync(configPath)) {
435
+ console.log(chalk.yellow(`No ${HABITS_FILE} found. Run 'paradigm habits init' first.`));
436
+ return;
437
+ }
438
+ let config;
439
+ try {
440
+ const content = fs2.readFileSync(configPath, "utf8");
441
+ config = yaml2.load(content);
442
+ if (!config.habits) config.habits = [];
443
+ } catch (err) {
444
+ console.log(chalk.red("Failed to parse habits.yaml:"), err.message);
445
+ return;
446
+ }
447
+ const existingIds = /* @__PURE__ */ new Set([
448
+ ...config.habits.map((h) => h.id),
449
+ ...loadHabits(rootDir).map((h) => h.id)
450
+ ]);
451
+ if (existingIds.has(options.id)) {
452
+ console.log(chalk.yellow(`Habit "${options.id}" already exists.`));
453
+ return;
454
+ }
455
+ const tools = options.tools ? options.tools.split(",").map((t) => t.trim()) : [];
456
+ const newHabit = {
457
+ id: options.id,
458
+ name: options.name,
459
+ description: options.description,
460
+ category: options.category,
461
+ trigger: options.trigger,
462
+ severity: options.severity || "advisory",
463
+ check: {
464
+ type: "tool-called",
465
+ params: { tools }
466
+ },
467
+ enabled: true
468
+ };
469
+ config.habits.push(newHabit);
470
+ fs2.writeFileSync(configPath, yaml2.dump(config, { lineWidth: 80, noRefs: true }), "utf8");
471
+ invalidateHabitsCache(rootDir);
472
+ console.log(chalk.green(`Added habit: ${options.id}`));
473
+ console.log(chalk.gray(` Name: ${options.name}`));
474
+ console.log(chalk.gray(` Category: ${options.category} | Trigger: ${options.trigger} | Severity: ${options.severity || "advisory"}`));
475
+ if (tools.length > 0) {
476
+ console.log(chalk.gray(` Tools: ${tools.join(", ")}`));
477
+ }
478
+ console.log();
479
+ }
480
+ export {
481
+ habitsAddCommand,
482
+ habitsInitCommand,
483
+ habitsListCommand,
484
+ habitsStatusCommand
485
+ };
@@ -3,7 +3,7 @@ import {
3
3
  hooksInstallCommand,
4
4
  hooksStatusCommand,
5
5
  hooksUninstallCommand
6
- } from "./chunk-4WR7X3FE.js";
6
+ } from "./chunk-LRSJNX7K.js";
7
7
  import "./chunk-MO4EEYFW.js";
8
8
  export {
9
9
  hooksInstallCommand,