@a-company/paradigm 3.1.5 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/{accept-orchestration-CWZNCGZX.js → accept-orchestration-DIGPJVUR.js} +6 -5
  2. package/dist/{aggregate-W7Q6VIM2.js → aggregate-V4KPR3RW.js} +2 -2
  3. package/dist/{beacon-B47XSTL7.js → beacon-XRXL5KZB.js} +2 -2
  4. package/dist/{chunk-4LGLU2LO.js → chunk-2E2RTBSM.js} +533 -182
  5. package/dist/{chunk-YCLN7WXV.js → chunk-2QNZ6PVD.js} +219 -35
  6. package/dist/{chunk-UM54F7G5.js → chunk-4N6AYEEA.js} +1 -1
  7. package/dist/{chunk-MVXJVRFI.js → chunk-5TUAVVIG.js} +65 -1
  8. package/dist/{chunk-5C4SGQKH.js → chunk-6P4IFIK2.js} +4 -2
  9. package/dist/{chunk-WS5KM7OL.js → chunk-6RNYVBSG.js} +1 -1
  10. package/dist/{chunk-N6PJAPDE.js → chunk-AK5M6KJB.js} +18 -0
  11. package/dist/{chunk-VZ7CXFRZ.js → chunk-CRICL4FQ.js} +1004 -17
  12. package/dist/{chunk-MC7XC7XQ.js → chunk-GZDFVP2N.js} +20 -13
  13. package/dist/chunk-HPC3JAUP.js +42 -0
  14. package/dist/chunk-IRVA7NKV.js +657 -0
  15. package/dist/{chunk-ZPN7MXRA.js → chunk-KFHK6EBI.js} +184 -1
  16. package/dist/{chunk-UUZ2DMG5.js → chunk-KWDTBXP2.js} +1 -1
  17. package/dist/{chunk-DRUDZKIT.js → chunk-M2XMTJHQ.js} +693 -70
  18. package/dist/{chunk-PW2EXJQT.js → chunk-MRENOFTR.js} +24 -1
  19. package/dist/{chunk-QS36NGWV.js → chunk-QHJGB5TV.js} +1 -1
  20. package/dist/chunk-UI3XXVJ6.js +449 -0
  21. package/dist/{chunk-AD2LSCHB.js → chunk-Y4XZWCHK.js} +40 -74
  22. package/dist/{constellation-K3CIQCHI.js → constellation-GNK5DIMH.js} +2 -2
  23. package/dist/{cost-AEK6R7HK.js → cost-AGO5N7DD.js} +1 -1
  24. package/dist/{cursorrules-KI5QWHIX.js → cursorrules-LQFA7M62.js} +2 -2
  25. package/dist/{delete-W67IVTLJ.js → delete-3YXAJ5AA.js} +12 -1
  26. package/dist/{diff-AJJ5H6HV.js → diff-J6C5IHPV.js} +6 -5
  27. package/dist/{dist-2F7NO4H4-KSL6SJIO.js → dist-AG5JNIZU-XSEZ2LLK.js} +28 -3
  28. package/dist/dist-JOHRYQUA.js +7294 -0
  29. package/dist/{dist-NHJQVVUW.js → dist-Q6SAZI7X.js} +2 -2
  30. package/dist/{dist-GPQ4LAY3.js → dist-YP2CO4TG.js} +24 -6
  31. package/dist/{doctor-JBIV5PMN.js → doctor-TQYRF7KK.js} +2 -2
  32. package/dist/{edit-Y7XPYSMK.js → edit-EOMPXOG5.js} +1 -1
  33. package/dist/flow-7JUH6D4H.js +185 -0
  34. package/dist/global-AXILUM5X.js +136 -0
  35. package/dist/{habits-FA65W77Y.js → habits-CHP4EW5H.js} +234 -5
  36. package/dist/{hooks-JKWO44WH.js → hooks-DLZEYHI3.js} +1 -1
  37. package/dist/index.js +125 -100
  38. package/dist/{lint-HXKTWRNO.js → lint-N4LMMEXH.js} +141 -1
  39. package/dist/{list-R3QWW4SC.js → list-JKBJ7ESH.js} +1 -1
  40. package/dist/mcp.js +9273 -6515
  41. package/dist/{orchestrate-4ZH5GUQH.js → orchestrate-FAV64G2R.js} +6 -5
  42. package/dist/{probe-OYCP4JYG.js → probe-X3J2JX62.js} +18 -3
  43. package/dist/{promote-E6NBZ3BK.js → promote-HZH5E5CO.js} +1 -1
  44. package/dist/{providers-4PGPZEWP.js → providers-NQ67LO2Z.js} +1 -1
  45. package/dist/{record-OHQNWOUP.js → record-EECZ3E4I.js} +1 -1
  46. package/dist/{remember-6VZ74B7E.js → remember-3KJZGDUG.js} +1 -1
  47. package/dist/{review-RUHX25A5.js → review-BF26ILZB.js} +1 -1
  48. package/dist/{ripple-SBQOSTZD.js → ripple-JIUAMBLA.js} +2 -2
  49. package/dist/sentinel-ZTL224IG.js +63 -0
  50. package/dist/{server-MV4HNFVF.js → server-MZBYDXJY.js} +4193 -9
  51. package/dist/{setup-DF4F3ICN.js → setup-363IB6MO.js} +1 -1
  52. package/dist/{setup-JHBPZAG7.js → setup-UKJ3VGHI.js} +4 -4
  53. package/dist/{shift-2LQFQP4P.js → shift-KDVYB6CR.js} +16 -13
  54. package/dist/{show-WTOJXUTN.js → show-SAMTXEHG.js} +1 -1
  55. package/dist/{snapshot-GTVPRYZG.js → snapshot-KCMONZAO.js} +2 -2
  56. package/dist/{spawn-BJRQA2NR.js → spawn-EO7B2UM3.js} +2 -2
  57. package/dist/{summary-5SBFO7QK.js → summary-E2PU4UN2.js} +3 -3
  58. package/dist/{switch-6EANJ7O6.js → switch-CC2KACXO.js} +1 -1
  59. package/dist/{sync-5KSTPJ4B.js → sync-5VJPZQNX.js} +2 -2
  60. package/dist/sync-llms-7QDA3ZWC.js +166 -0
  61. package/dist/{team-NWP2KJAB.js → team-6CCNANKE.js} +7 -6
  62. package/dist/{test-MA5TWJQV.js → test-DK2RWLTK.js} +91 -8
  63. package/dist/{thread-JCJVRUQR.js → thread-RNSLADXN.js} +18 -2
  64. package/dist/{timeline-P7BARFLI.js → timeline-TJDVVVA3.js} +1 -1
  65. package/dist/{triage-TBIWJA6R.js → triage-PXMU3RWV.js} +2 -2
  66. package/dist/university-content/courses/para-101.json +2 -1
  67. package/dist/university-content/courses/para-201.json +102 -3
  68. package/dist/university-content/courses/para-301.json +14 -11
  69. package/dist/university-content/courses/para-401.json +57 -3
  70. package/dist/university-content/courses/para-501.json +204 -6
  71. package/dist/university-content/plsat/v3.0.json +808 -3
  72. package/dist/university-content/reference.json +270 -0
  73. package/dist/{upgrade-TIYFQYPO.js → upgrade-RBSE4M6I.js} +1 -1
  74. package/dist/{validate-QEEY6KFS.js → validate-2LTHHORX.js} +1 -1
  75. package/dist/{watch-4LT4O6K7.js → watch-NBPOMOEX.js} +76 -0
  76. package/dist/{watch-2XEYUH43.js → watch-PAEH6MOG.js} +1 -1
  77. package/package.json +1 -1
  78. package/dist/chunk-GWM2WRXL.js +0 -1095
  79. package/dist/sentinel-WB7GIK4V.js +0 -43
  80. /package/dist/{chunk-TAP5N3HH.js → chunk-CCG6KYBT.js} +0 -0
@@ -30,8 +30,8 @@ import {
30
30
  searchSymbols,
31
31
  serializePremiseFile,
32
32
  updateNodePosition
33
- } from "./chunk-5C4SGQKH.js";
34
- import "./chunk-PW2EXJQT.js";
33
+ } from "./chunk-6P4IFIK2.js";
34
+ import "./chunk-MRENOFTR.js";
35
35
  import "./chunk-IRKUEJVW.js";
36
36
  import "./chunk-MO4EEYFW.js";
37
37
  export {
@@ -7,36 +7,54 @@ import {
7
7
  PatternMatcher,
8
8
  PatternSuggester,
9
9
  Sentinel,
10
+ SentinelClient,
11
+ SentinelTransport,
10
12
  StatsCalculator,
11
13
  TimelineBuilder,
14
+ createSentinelClient,
15
+ createSentinelTransport,
12
16
  detectSymbols,
17
+ enableSentinel,
13
18
  generateConfig,
14
19
  loadAllSeedPatterns,
15
- loadConfig,
16
20
  loadParadigmPatterns,
17
- loadUniversalPatterns,
18
- writeConfig
19
- } from "./chunk-4LGLU2LO.js";
21
+ loadUniversalPatterns
22
+ } from "./chunk-2E2RTBSM.js";
20
23
  import {
21
- SentinelStorage
22
- } from "./chunk-VZ7CXFRZ.js";
24
+ DEFAULT_AUTH_CONFIG,
25
+ DEFAULT_RATE_LIMIT_CONFIG,
26
+ DEFAULT_SERVER_CONFIG,
27
+ SentinelStorage,
28
+ loadConfig,
29
+ loadServerConfig,
30
+ writeConfig
31
+ } from "./chunk-CRICL4FQ.js";
23
32
  import "./chunk-MO4EEYFW.js";
24
33
  export {
25
34
  ContextEnricher,
35
+ DEFAULT_AUTH_CONFIG,
36
+ DEFAULT_RATE_LIMIT_CONFIG,
37
+ DEFAULT_SERVER_CONFIG,
26
38
  FlowTracker,
27
39
  IncidentGrouper,
28
40
  PatternImporter,
29
41
  PatternMatcher,
30
42
  PatternSuggester,
31
43
  Sentinel,
44
+ SentinelClient,
32
45
  SentinelStorage,
46
+ SentinelTransport,
33
47
  StatsCalculator,
34
48
  TimelineBuilder,
49
+ createSentinelClient,
50
+ createSentinelTransport,
35
51
  detectSymbols,
52
+ enableSentinel,
36
53
  generateConfig,
37
54
  loadAllSeedPatterns,
38
55
  loadConfig,
39
56
  loadParadigmPatterns,
57
+ loadServerConfig,
40
58
  loadUniversalPatterns,
41
59
  writeConfig
42
60
  };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  doctorCommand
4
- } from "./chunk-ZPN7MXRA.js";
5
- import "./chunk-YCLN7WXV.js";
4
+ } from "./chunk-KFHK6EBI.js";
5
+ import "./chunk-2QNZ6PVD.js";
6
6
  import "./chunk-4NCFWYGG.js";
7
7
  import "./chunk-YO6DVTL7.js";
8
8
  import "./chunk-MO4EEYFW.js";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  loadLoreEntry,
4
4
  updateLoreEntry
5
- } from "./chunk-MVXJVRFI.js";
5
+ } from "./chunk-5TUAVVIG.js";
6
6
  import "./chunk-MO4EEYFW.js";
7
7
 
8
8
  // src/commands/lore/edit.ts
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-HPC3JAUP.js";
3
+ import {
4
+ log
5
+ } from "./chunk-4NCFWYGG.js";
6
+ import "./chunk-MO4EEYFW.js";
7
+
8
+ // src/commands/flow.ts
9
+ import * as fs2 from "fs";
10
+ import * as path2 from "path";
11
+ import chalk from "chalk";
12
+
13
+ // src/core/flow-validator.ts
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ import * as yaml from "js-yaml";
17
+ import { execSync } from "child_process";
18
+
19
+ // src/core/flow-schema.ts
20
+ function configToFlowDefinitions(config) {
21
+ return Object.entries(config.flows).map(([id, flow]) => ({
22
+ id,
23
+ ...flow
24
+ }));
25
+ }
26
+
27
+ // src/core/flow-validator.ts
28
+ function loadFlowsConfig(rootDir) {
29
+ const flowsPath = path.join(rootDir, ".paradigm", "flows.yaml");
30
+ if (!fs.existsSync(flowsPath)) {
31
+ return null;
32
+ }
33
+ try {
34
+ const content = fs.readFileSync(flowsPath, "utf-8");
35
+ return yaml.load(content);
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+ function loadFlowsFromPurpose(rootDir) {
41
+ const flows = [];
42
+ try {
43
+ const result = execSync(
44
+ `find "${rootDir}" -name ".purpose" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null || true`,
45
+ { encoding: "utf-8" }
46
+ );
47
+ for (const purposePath of result.split("\n").filter(Boolean)) {
48
+ try {
49
+ const content = fs.readFileSync(purposePath, "utf-8");
50
+ const flowsMatch = content.match(/flows:\s*\n([\s\S]*?)(?=\n[a-z_]+:|$)/);
51
+ if (flowsMatch) {
52
+ const flowsYaml = yaml.load(`flows:
53
+ ${flowsMatch[1]}`);
54
+ if (flowsYaml && typeof flowsYaml === "object" && "flows" in flowsYaml) {
55
+ const flowsData = flowsYaml.flows;
56
+ for (const [id, flow] of Object.entries(flowsData)) {
57
+ flows.push({
58
+ id,
59
+ ...flow,
60
+ definedIn: path.relative(rootDir, purposePath)
61
+ });
62
+ }
63
+ }
64
+ }
65
+ } catch {
66
+ }
67
+ }
68
+ } catch {
69
+ }
70
+ return flows;
71
+ }
72
+ function getAllFlows(rootDir) {
73
+ const flows = [];
74
+ const config = loadFlowsConfig(rootDir);
75
+ if (config) {
76
+ const configFlows = configToFlowDefinitions(config);
77
+ for (const flow of configFlows) {
78
+ flow.definedIn = ".paradigm/flows.yaml";
79
+ flows.push(flow);
80
+ }
81
+ }
82
+ const purposeFlows = loadFlowsFromPurpose(rootDir);
83
+ for (const flow of purposeFlows) {
84
+ if (!flows.some((f) => f.id === flow.id)) {
85
+ flows.push(flow);
86
+ }
87
+ }
88
+ return flows;
89
+ }
90
+ function generateMermaidDiagram(flow) {
91
+ const lines = [];
92
+ lines.push("```mermaid");
93
+ lines.push("flowchart TD");
94
+ lines.push(` START([${escapeLabel(flow.trigger)}])`);
95
+ let prevId = "START";
96
+ for (let i = 0; i < flow.steps.length; i++) {
97
+ const step = flow.steps[i];
98
+ const nodeId = `S${i}`;
99
+ const label = escapeLabel(step.symbol);
100
+ switch (step.type) {
101
+ case "gate": {
102
+ const gateStep = step;
103
+ lines.push(` ${nodeId}{${label}}`);
104
+ lines.push(` ${prevId} --> ${nodeId}`);
105
+ if (gateStep.failResponse || step.errorSignal) {
106
+ const denyId = `DENY${i}`;
107
+ const denyLabel = gateStep.failResponse || step.errorSignal || "Denied";
108
+ lines.push(` ${denyId}[/${escapeLabel(denyLabel)}/]`);
109
+ lines.push(` ${nodeId} -->|deny| ${denyId}`);
110
+ }
111
+ break;
112
+ }
113
+ case "action":
114
+ lines.push(` ${nodeId}[${label}]`);
115
+ lines.push(` ${prevId} -->|${step.optional ? "optional" : "allow"}| ${nodeId}`);
116
+ break;
117
+ case "signal":
118
+ lines.push(` ${nodeId}([${label}])`);
119
+ lines.push(` ${prevId} --> ${nodeId}`);
120
+ break;
121
+ }
122
+ prevId = nodeId;
123
+ }
124
+ if (flow.successSignal) {
125
+ lines.push(` SUCCESS([${escapeLabel(flow.successSignal)}])`);
126
+ lines.push(` ${prevId} --> SUCCESS`);
127
+ }
128
+ lines.push("");
129
+ lines.push(" classDef gate fill:#f9d71c,stroke:#333,color:#000");
130
+ lines.push(" classDef action fill:#4a90d9,stroke:#333,color:#fff");
131
+ lines.push(" classDef signal fill:#50c878,stroke:#333,color:#fff");
132
+ for (let i = 0; i < flow.steps.length; i++) {
133
+ const step = flow.steps[i];
134
+ lines.push(` class S${i} ${step.type}`);
135
+ }
136
+ lines.push("```");
137
+ return lines.join("\n");
138
+ }
139
+ function escapeLabel(text) {
140
+ return text.replace(/"/g, '\\"').replace(/[[\]{}()]/g, "");
141
+ }
142
+
143
+ // src/commands/flow.ts
144
+ async function flowDiagramCommand(flowId, options) {
145
+ const rootDir = process.cwd();
146
+ const config = loadFlowsConfig(rootDir);
147
+ if (!config) {
148
+ console.log(chalk.red("\n\u274C No flows.yaml found"));
149
+ console.log(chalk.gray("Create .paradigm/flows.yaml to define flows.\n"));
150
+ process.exit(1);
151
+ }
152
+ const flows = getAllFlows(rootDir);
153
+ const normalizedId = flowId.startsWith("$") ? flowId : `$${flowId}`;
154
+ const flow = flows.find((f) => f.id === normalizedId || f.id === flowId);
155
+ if (!flow) {
156
+ console.log(chalk.red(`
157
+ \u274C Flow not found: ${flowId}`));
158
+ console.log(chalk.gray(`
159
+ Available flows:`));
160
+ for (const f of flows) {
161
+ console.log(chalk.gray(` ${f.id} \u2014 ${f.name}`));
162
+ }
163
+ console.log("");
164
+ process.exit(1);
165
+ }
166
+ log.command("flow-diagram").info("Generating Mermaid diagram", { flowId: flow.id });
167
+ const diagram = generateMermaidDiagram(flow);
168
+ if (options.output) {
169
+ const outputPath = path2.resolve(rootDir, options.output);
170
+ fs2.writeFileSync(outputPath, diagram, "utf-8");
171
+ console.log(chalk.green(`
172
+ \u2713 Diagram written to ${path2.relative(rootDir, outputPath)}
173
+ `));
174
+ } else {
175
+ console.log("");
176
+ console.log(chalk.blue(`Flow: ${flow.name} (${flow.id})`));
177
+ console.log(chalk.gray(`Trigger: ${flow.trigger}`));
178
+ console.log("");
179
+ console.log(diagram);
180
+ console.log("");
181
+ }
182
+ }
183
+ export {
184
+ flowDiagramCommand
185
+ };
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ log
4
+ } from "./chunk-4NCFWYGG.js";
5
+ import "./chunk-MO4EEYFW.js";
6
+
7
+ // src/commands/global.ts
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import chalk from "chalk";
11
+ import ora from "ora";
12
+ function parseDuration(duration) {
13
+ const match = duration.match(/^(\d+)(d|h|m)$/);
14
+ if (!match) return null;
15
+ const [, num, unit] = match;
16
+ const value = parseInt(num, 10);
17
+ switch (unit) {
18
+ case "d":
19
+ return value * 24 * 60 * 60 * 1e3;
20
+ case "h":
21
+ return value * 60 * 60 * 1e3;
22
+ case "m":
23
+ return value * 60 * 1e3;
24
+ default:
25
+ return null;
26
+ }
27
+ }
28
+ function findOldFiles(dir, cutoffMs) {
29
+ const results = [];
30
+ if (!fs.existsSync(dir)) return results;
31
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
32
+ for (const entry of entries) {
33
+ const fullPath = path.join(dir, entry.name);
34
+ if (entry.isDirectory()) {
35
+ results.push(...findOldFiles(fullPath, cutoffMs));
36
+ } else if (entry.isFile()) {
37
+ try {
38
+ const stat = fs.statSync(fullPath);
39
+ const age = Date.now() - stat.mtimeMs;
40
+ if (age > cutoffMs) {
41
+ const days = Math.floor(age / (24 * 60 * 60 * 1e3));
42
+ results.push({ path: fullPath, age: `${days}d` });
43
+ }
44
+ } catch {
45
+ }
46
+ }
47
+ }
48
+ return results;
49
+ }
50
+ async function globalCleanCommand(options) {
51
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "~";
52
+ const globalDir = path.join(homeDir, ".paradigm");
53
+ const spinner = ora();
54
+ if (!fs.existsSync(globalDir)) {
55
+ console.log(chalk.yellow("\nNo ~/.paradigm/ directory found.\n"));
56
+ return;
57
+ }
58
+ const durationStr = options.olderThan || "90d";
59
+ const durationMs = parseDuration(durationStr);
60
+ if (!durationMs) {
61
+ console.log(chalk.red(`
62
+ Invalid duration: ${durationStr}`));
63
+ console.log(chalk.gray("Use format: 90d, 30d, 7d, 24h, etc.\n"));
64
+ process.exit(1);
65
+ }
66
+ console.log(chalk.blue(`
67
+ Global Brain Rotation
68
+ `));
69
+ console.log(chalk.gray(` Scanning ~/.paradigm/ for files older than ${durationStr}...
70
+ `));
71
+ spinner.start("Scanning...");
72
+ const cleanableDirs = ["wisdom", "lore", "history", "cache"];
73
+ const allOldFiles = [];
74
+ for (const sub of cleanableDirs) {
75
+ const subDir = path.join(globalDir, sub);
76
+ const oldFiles = findOldFiles(subDir, durationMs);
77
+ allOldFiles.push(...oldFiles);
78
+ }
79
+ spinner.stop();
80
+ if (allOldFiles.length === 0) {
81
+ console.log(chalk.green(` No files older than ${durationStr} found.
82
+ `));
83
+ return;
84
+ }
85
+ console.log(chalk.gray(` Found ${allOldFiles.length} files older than ${durationStr}:
86
+ `));
87
+ for (const file of allOldFiles.slice(0, 20)) {
88
+ const relPath = path.relative(globalDir, file.path);
89
+ console.log(chalk.gray(` ${file.age} old - ${relPath}`));
90
+ }
91
+ if (allOldFiles.length > 20) {
92
+ console.log(chalk.gray(` ... and ${allOldFiles.length - 20} more`));
93
+ }
94
+ if (options.dryRun) {
95
+ console.log(chalk.yellow(`
96
+ Dry run - no files deleted.
97
+ `));
98
+ log.command("global-clean").info("Dry run completed", { count: allOldFiles.length, olderThan: durationStr });
99
+ return;
100
+ }
101
+ spinner.start(`Deleting ${allOldFiles.length} files...`);
102
+ let deleted = 0;
103
+ for (const file of allOldFiles) {
104
+ try {
105
+ fs.unlinkSync(file.path);
106
+ deleted++;
107
+ } catch {
108
+ }
109
+ }
110
+ spinner.succeed(`Deleted ${deleted} files older than ${durationStr}`);
111
+ for (const sub of cleanableDirs) {
112
+ const subDir = path.join(globalDir, sub);
113
+ cleanEmptyDirs(subDir);
114
+ }
115
+ console.log("");
116
+ log.command("global-clean").success("Global brain rotation completed", { deleted, olderThan: durationStr });
117
+ }
118
+ function cleanEmptyDirs(dir) {
119
+ if (!fs.existsSync(dir)) return;
120
+ const entries = fs.readdirSync(dir);
121
+ for (const entry of entries) {
122
+ const fullPath = path.join(dir, entry);
123
+ if (fs.statSync(fullPath).isDirectory()) {
124
+ cleanEmptyDirs(fullPath);
125
+ }
126
+ }
127
+ if (fs.readdirSync(dir).length === 0) {
128
+ try {
129
+ fs.rmdirSync(dir);
130
+ } catch {
131
+ }
132
+ }
133
+ }
134
+ export {
135
+ globalCleanCommand
136
+ };
@@ -162,17 +162,93 @@ var seed_habits_default = [
162
162
  }
163
163
  },
164
164
  enabled: true
165
+ },
166
+ {
167
+ id: "commit-message-symbols",
168
+ name: "Commit Message Format",
169
+ description: "Commit messages should follow type(#symbol): format and include a Symbols: trailer",
170
+ category: "documentation",
171
+ trigger: "on-commit",
172
+ severity: "advisory",
173
+ check: {
174
+ type: "commit-message-format",
175
+ params: {
176
+ messagePatterns: ["^(feat|fix|refactor|chore|docs|test|style|perf|ci|build)\\(", "Symbols:"]
177
+ }
178
+ },
179
+ enabled: true
180
+ },
181
+ {
182
+ id: "flow-coverage-for-multi-component",
183
+ name: "Flow Coverage",
184
+ description: "Changes spanning 3+ components should have a documented $flow",
185
+ category: "documentation",
186
+ trigger: "postflight",
187
+ severity: "advisory",
188
+ check: {
189
+ type: "flow-coverage",
190
+ params: {
191
+ minSteps: 3
192
+ }
193
+ },
194
+ enabled: true
195
+ },
196
+ {
197
+ id: "context-session-awareness",
198
+ name: "Context Awareness",
199
+ description: "Use session recovery or context check tools for session continuity",
200
+ category: "discovery",
201
+ trigger: "preflight",
202
+ severity: "advisory",
203
+ check: {
204
+ type: "context-checked",
205
+ params: {
206
+ contextTools: ["paradigm_context_check", "paradigm_session_recover", "paradigm_session_checkpoint"]
207
+ }
208
+ },
209
+ enabled: true
210
+ },
211
+ {
212
+ id: "aspect-anchors-valid",
213
+ name: "Aspect Anchors Valid",
214
+ description: "Aspects touched during the session should have their code anchors validated",
215
+ category: "verification",
216
+ trigger: "postflight",
217
+ severity: "advisory",
218
+ check: {
219
+ type: "aspect-anchored",
220
+ params: {
221
+ checkAnchors: true
222
+ }
223
+ },
224
+ enabled: true
165
225
  }
166
226
  ];
167
227
 
168
228
  // src/core/habits/loader.ts
169
229
  var SEED_HABITS = seed_habits_default;
170
- var HABITS_CACHE_TTL_MS = 30 * 1e3;
230
+ var DEFAULT_HABITS_CACHE_TTL_MS = 30 * 1e3;
231
+ function getHabitsCacheTtl(rootDir) {
232
+ try {
233
+ const configPath = path.join(rootDir, ".paradigm", "config.yaml");
234
+ if (fs.existsSync(configPath)) {
235
+ const content = fs.readFileSync(configPath, "utf8");
236
+ const config = yaml.load(content);
237
+ const limits = config?.limits;
238
+ if (limits?.habitsCacheTtlMs && typeof limits.habitsCacheTtlMs === "number") {
239
+ return limits.habitsCacheTtlMs;
240
+ }
241
+ }
242
+ } catch {
243
+ }
244
+ return DEFAULT_HABITS_CACHE_TTL_MS;
245
+ }
171
246
  var habitsCache = /* @__PURE__ */ new Map();
172
247
  function loadHabits(rootDir) {
173
248
  const absoluteRoot = path.resolve(rootDir);
174
249
  const cached = habitsCache.get(absoluteRoot);
175
- if (cached && Date.now() - cached.loadedAt < HABITS_CACHE_TTL_MS) {
250
+ const ttl = getHabitsCacheTtl(absoluteRoot);
251
+ if (cached && Date.now() - cached.loadedAt < ttl) {
176
252
  return cached.habits;
177
253
  }
178
254
  const habits = loadHabitsFresh(absoluteRoot);
@@ -294,6 +370,14 @@ function evaluateHabit(habit, context) {
294
370
  return evaluateFileModified(habit, context);
295
371
  case "git-clean":
296
372
  return evaluateGitClean(habit, context);
373
+ case "commit-message-format":
374
+ return evaluateCommitMessageFormat(habit, context);
375
+ case "flow-coverage":
376
+ return evaluateFlowCoverage(habit, context);
377
+ case "context-checked":
378
+ return evaluateContextChecked(habit, context);
379
+ case "aspect-anchored":
380
+ return evaluateAspectAnchored(habit, context);
297
381
  default:
298
382
  return {
299
383
  habit,
@@ -550,6 +634,148 @@ function evaluateGitClean(habit, context) {
550
634
  reason: "Uncommitted changes in working tree"
551
635
  };
552
636
  }
637
+ function evaluateCommitMessageFormat(habit, context) {
638
+ if (!context.commitMessage) {
639
+ return {
640
+ habit,
641
+ result: "followed",
642
+ reason: "No commit message to check (not a commit trigger)"
643
+ };
644
+ }
645
+ const patterns = habit.check.params.messagePatterns || [
646
+ "^(feat|fix|refactor|chore|docs|test|style|perf|ci|build)\\(",
647
+ "Symbols:"
648
+ ];
649
+ const matchedPatterns = patterns.filter(
650
+ (p) => new RegExp(p, "m").test(context.commitMessage)
651
+ );
652
+ if (matchedPatterns.length === patterns.length) {
653
+ return {
654
+ habit,
655
+ result: "followed",
656
+ reason: "Commit message matches all required patterns",
657
+ evidence: matchedPatterns
658
+ };
659
+ }
660
+ if (matchedPatterns.length > 0) {
661
+ const missing = patterns.filter(
662
+ (p) => !new RegExp(p, "m").test(context.commitMessage)
663
+ );
664
+ return {
665
+ habit,
666
+ result: "partial",
667
+ reason: `Commit message matches ${matchedPatterns.length}/${patterns.length} patterns. Missing: ${missing.join(", ")}`
668
+ };
669
+ }
670
+ return {
671
+ habit,
672
+ result: "skipped",
673
+ reason: "Commit message does not match required format patterns"
674
+ };
675
+ }
676
+ function evaluateFlowCoverage(habit, context) {
677
+ const componentSymbols = context.symbolsTouched.filter(
678
+ (s) => s.startsWith("#")
679
+ );
680
+ if (componentSymbols.length < 3) {
681
+ return {
682
+ habit,
683
+ result: "followed",
684
+ reason: "Fewer than 3 components touched \u2014 flow not required"
685
+ };
686
+ }
687
+ if (context.hasFlowCoverage) {
688
+ return {
689
+ habit,
690
+ result: "followed",
691
+ reason: "Flow coverage exists for multi-component changes"
692
+ };
693
+ }
694
+ const flowTools = [
695
+ "paradigm_flow_validate",
696
+ "paradigm_flows_affected",
697
+ "paradigm_purpose_add_flow"
698
+ ];
699
+ const calledFlowTools = flowTools.filter(
700
+ (t) => context.toolsCalled.includes(t)
701
+ );
702
+ if (calledFlowTools.length > 0) {
703
+ return {
704
+ habit,
705
+ result: "followed",
706
+ reason: `Flow tools called: ${calledFlowTools.join(", ")}`,
707
+ evidence: calledFlowTools
708
+ };
709
+ }
710
+ return {
711
+ habit,
712
+ result: "skipped",
713
+ reason: `${componentSymbols.length} components touched without flow coverage or flow tools called`,
714
+ evidence: componentSymbols.slice(0, 5)
715
+ };
716
+ }
717
+ function evaluateContextChecked(habit, context) {
718
+ const contextTools = habit.check.params.contextTools || [
719
+ "paradigm_context_check",
720
+ "paradigm_session_recover",
721
+ "paradigm_session_checkpoint"
722
+ ];
723
+ const calledTools = contextTools.filter(
724
+ (t) => context.toolsCalled.includes(t)
725
+ );
726
+ if (calledTools.length > 0) {
727
+ return {
728
+ habit,
729
+ result: "followed",
730
+ reason: `Context tools called: ${calledTools.join(", ")}`,
731
+ evidence: calledTools
732
+ };
733
+ }
734
+ if (context.filesModified.length === 0 && context.symbolsTouched.length === 0) {
735
+ return {
736
+ habit,
737
+ result: "followed",
738
+ reason: "No modifications made, context check not applicable"
739
+ };
740
+ }
741
+ return {
742
+ habit,
743
+ result: "skipped",
744
+ reason: "No context/session tools called during session"
745
+ };
746
+ }
747
+ function evaluateAspectAnchored(habit, context) {
748
+ const aspectSymbols = context.symbolsTouched.filter(
749
+ (s) => s.startsWith("~")
750
+ );
751
+ if (aspectSymbols.length === 0) {
752
+ return {
753
+ habit,
754
+ result: "followed",
755
+ reason: "No aspects touched"
756
+ };
757
+ }
758
+ if (context.aspectAnchorsValid === true) {
759
+ return {
760
+ habit,
761
+ result: "followed",
762
+ reason: "Aspect anchors validated and valid"
763
+ };
764
+ }
765
+ if (context.toolsCalled.includes("paradigm_aspect_check")) {
766
+ return {
767
+ habit,
768
+ result: "followed",
769
+ reason: "paradigm_aspect_check was called to validate anchors"
770
+ };
771
+ }
772
+ return {
773
+ habit,
774
+ result: "skipped",
775
+ reason: `${aspectSymbols.length} aspect(s) touched without anchor validation`,
776
+ evidence: aspectSymbols.slice(0, 5)
777
+ };
778
+ }
553
779
  function buildEvaluationContext(params) {
554
780
  return {
555
781
  toolsCalled: params.toolsCalled || [],
@@ -559,7 +785,10 @@ function buildEvaluationContext(params) {
559
785
  hasPortalRoutes: params.hasPortalRoutes || false,
560
786
  taskAddsRoutes: params.taskAddsRoutes || false,
561
787
  taskDescription: params.taskDescription,
562
- gitClean: params.gitClean
788
+ gitClean: params.gitClean,
789
+ commitMessage: params.commitMessage,
790
+ hasFlowCoverage: params.hasFlowCoverage,
791
+ aspectAnchorsValid: params.aspectAnchorsValid
563
792
  };
564
793
  }
565
794
 
@@ -682,7 +911,7 @@ async function habitsStatusCommand(options) {
682
911
  const enabled = getEnabledHabits(habits);
683
912
  let practiceData = null;
684
913
  try {
685
- const { SentinelStorage } = await import("./dist-GPQ4LAY3.js");
914
+ const { SentinelStorage } = await import("./dist-YP2CO4TG.js");
686
915
  const sentinelDir = path3.join(rootDir, ".paradigm", "sentinel");
687
916
  if (fs2.existsSync(sentinelDir)) {
688
917
  const storage = new SentinelStorage(sentinelDir);
@@ -1050,7 +1279,7 @@ async function habitsCheckCommand(options) {
1050
1279
  try {
1051
1280
  const sentinelDir = path3.join(rootDir, ".paradigm", "sentinel");
1052
1281
  if (fs2.existsSync(sentinelDir)) {
1053
- const { SentinelStorage } = await import("./dist-GPQ4LAY3.js");
1282
+ const { SentinelStorage } = await import("./dist-YP2CO4TG.js");
1054
1283
  const storage = new SentinelStorage(sentinelDir);
1055
1284
  for (const e of evaluation.evaluations) {
1056
1285
  storage.recordPracticeEvent({