@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.
- package/dist/{accept-orchestration-CWZNCGZX.js → accept-orchestration-DIGPJVUR.js} +6 -5
- package/dist/{aggregate-W7Q6VIM2.js → aggregate-V4KPR3RW.js} +2 -2
- package/dist/{beacon-B47XSTL7.js → beacon-XRXL5KZB.js} +2 -2
- package/dist/{chunk-4LGLU2LO.js → chunk-2E2RTBSM.js} +533 -182
- package/dist/{chunk-YCLN7WXV.js → chunk-2QNZ6PVD.js} +219 -35
- package/dist/{chunk-UM54F7G5.js → chunk-4N6AYEEA.js} +1 -1
- package/dist/{chunk-MVXJVRFI.js → chunk-5TUAVVIG.js} +65 -1
- package/dist/{chunk-5C4SGQKH.js → chunk-6P4IFIK2.js} +4 -2
- package/dist/{chunk-WS5KM7OL.js → chunk-6RNYVBSG.js} +1 -1
- package/dist/{chunk-N6PJAPDE.js → chunk-AK5M6KJB.js} +18 -0
- package/dist/{chunk-VZ7CXFRZ.js → chunk-CRICL4FQ.js} +1004 -17
- package/dist/{chunk-MC7XC7XQ.js → chunk-GZDFVP2N.js} +20 -13
- package/dist/chunk-HPC3JAUP.js +42 -0
- package/dist/chunk-IRVA7NKV.js +657 -0
- package/dist/{chunk-ZPN7MXRA.js → chunk-KFHK6EBI.js} +184 -1
- package/dist/{chunk-UUZ2DMG5.js → chunk-KWDTBXP2.js} +1 -1
- package/dist/{chunk-DRUDZKIT.js → chunk-M2XMTJHQ.js} +693 -70
- package/dist/{chunk-PW2EXJQT.js → chunk-MRENOFTR.js} +24 -1
- package/dist/{chunk-QS36NGWV.js → chunk-QHJGB5TV.js} +1 -1
- package/dist/chunk-UI3XXVJ6.js +449 -0
- package/dist/{chunk-AD2LSCHB.js → chunk-Y4XZWCHK.js} +40 -74
- package/dist/{constellation-K3CIQCHI.js → constellation-GNK5DIMH.js} +2 -2
- package/dist/{cost-AEK6R7HK.js → cost-AGO5N7DD.js} +1 -1
- package/dist/{cursorrules-KI5QWHIX.js → cursorrules-LQFA7M62.js} +2 -2
- package/dist/{delete-W67IVTLJ.js → delete-3YXAJ5AA.js} +12 -1
- package/dist/{diff-AJJ5H6HV.js → diff-J6C5IHPV.js} +6 -5
- package/dist/{dist-2F7NO4H4-KSL6SJIO.js → dist-AG5JNIZU-XSEZ2LLK.js} +28 -3
- package/dist/dist-JOHRYQUA.js +7294 -0
- package/dist/{dist-NHJQVVUW.js → dist-Q6SAZI7X.js} +2 -2
- package/dist/{dist-GPQ4LAY3.js → dist-YP2CO4TG.js} +24 -6
- package/dist/{doctor-JBIV5PMN.js → doctor-TQYRF7KK.js} +2 -2
- package/dist/{edit-Y7XPYSMK.js → edit-EOMPXOG5.js} +1 -1
- package/dist/flow-7JUH6D4H.js +185 -0
- package/dist/global-AXILUM5X.js +136 -0
- package/dist/{habits-FA65W77Y.js → habits-CHP4EW5H.js} +234 -5
- package/dist/{hooks-JKWO44WH.js → hooks-DLZEYHI3.js} +1 -1
- package/dist/index.js +125 -100
- package/dist/{lint-HXKTWRNO.js → lint-N4LMMEXH.js} +141 -1
- package/dist/{list-R3QWW4SC.js → list-JKBJ7ESH.js} +1 -1
- package/dist/mcp.js +9273 -6515
- package/dist/{orchestrate-4ZH5GUQH.js → orchestrate-FAV64G2R.js} +6 -5
- package/dist/{probe-OYCP4JYG.js → probe-X3J2JX62.js} +18 -3
- package/dist/{promote-E6NBZ3BK.js → promote-HZH5E5CO.js} +1 -1
- package/dist/{providers-4PGPZEWP.js → providers-NQ67LO2Z.js} +1 -1
- package/dist/{record-OHQNWOUP.js → record-EECZ3E4I.js} +1 -1
- package/dist/{remember-6VZ74B7E.js → remember-3KJZGDUG.js} +1 -1
- package/dist/{review-RUHX25A5.js → review-BF26ILZB.js} +1 -1
- package/dist/{ripple-SBQOSTZD.js → ripple-JIUAMBLA.js} +2 -2
- package/dist/sentinel-ZTL224IG.js +63 -0
- package/dist/{server-MV4HNFVF.js → server-MZBYDXJY.js} +4193 -9
- package/dist/{setup-DF4F3ICN.js → setup-363IB6MO.js} +1 -1
- package/dist/{setup-JHBPZAG7.js → setup-UKJ3VGHI.js} +4 -4
- package/dist/{shift-2LQFQP4P.js → shift-KDVYB6CR.js} +16 -13
- package/dist/{show-WTOJXUTN.js → show-SAMTXEHG.js} +1 -1
- package/dist/{snapshot-GTVPRYZG.js → snapshot-KCMONZAO.js} +2 -2
- package/dist/{spawn-BJRQA2NR.js → spawn-EO7B2UM3.js} +2 -2
- package/dist/{summary-5SBFO7QK.js → summary-E2PU4UN2.js} +3 -3
- package/dist/{switch-6EANJ7O6.js → switch-CC2KACXO.js} +1 -1
- package/dist/{sync-5KSTPJ4B.js → sync-5VJPZQNX.js} +2 -2
- package/dist/sync-llms-7QDA3ZWC.js +166 -0
- package/dist/{team-NWP2KJAB.js → team-6CCNANKE.js} +7 -6
- package/dist/{test-MA5TWJQV.js → test-DK2RWLTK.js} +91 -8
- package/dist/{thread-JCJVRUQR.js → thread-RNSLADXN.js} +18 -2
- package/dist/{timeline-P7BARFLI.js → timeline-TJDVVVA3.js} +1 -1
- package/dist/{triage-TBIWJA6R.js → triage-PXMU3RWV.js} +2 -2
- package/dist/university-content/courses/para-101.json +2 -1
- package/dist/university-content/courses/para-201.json +102 -3
- package/dist/university-content/courses/para-301.json +14 -11
- package/dist/university-content/courses/para-401.json +57 -3
- package/dist/university-content/courses/para-501.json +204 -6
- package/dist/university-content/plsat/v3.0.json +808 -3
- package/dist/university-content/reference.json +270 -0
- package/dist/{upgrade-TIYFQYPO.js → upgrade-RBSE4M6I.js} +1 -1
- package/dist/{validate-QEEY6KFS.js → validate-2LTHHORX.js} +1 -1
- package/dist/{watch-4LT4O6K7.js → watch-NBPOMOEX.js} +76 -0
- package/dist/{watch-2XEYUH43.js → watch-PAEH6MOG.js} +1 -1
- package/package.json +1 -1
- package/dist/chunk-GWM2WRXL.js +0 -1095
- package/dist/sentinel-WB7GIK4V.js +0 -43
- /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-
|
|
34
|
-
import "./chunk-
|
|
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
|
-
|
|
19
|
-
} from "./chunk-4LGLU2LO.js";
|
|
21
|
+
loadUniversalPatterns
|
|
22
|
+
} from "./chunk-2E2RTBSM.js";
|
|
20
23
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
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-
|
|
5
|
-
import "./chunk-
|
|
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";
|
|
@@ -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
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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({
|