notoken-core 1.0.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/config/file-hints.json +255 -0
- package/config/hosts.json +14 -0
- package/config/intents.json +3920 -0
- package/config/playbooks.json +112 -0
- package/config/rules.json +100 -0
- package/dist/agents/agentSpawner.d.ts +56 -0
- package/dist/agents/agentSpawner.js +180 -0
- package/dist/agents/planner.d.ts +40 -0
- package/dist/agents/planner.js +175 -0
- package/dist/agents/playbookRunner.d.ts +45 -0
- package/dist/agents/playbookRunner.js +120 -0
- package/dist/agents/taskRunner.d.ts +61 -0
- package/dist/agents/taskRunner.js +142 -0
- package/dist/context/history.d.ts +36 -0
- package/dist/context/history.js +115 -0
- package/dist/conversation/coreference.d.ts +27 -0
- package/dist/conversation/coreference.js +147 -0
- package/dist/conversation/secrets.d.ts +43 -0
- package/dist/conversation/secrets.js +129 -0
- package/dist/conversation/store.d.ts +94 -0
- package/dist/conversation/store.js +184 -0
- package/dist/execution/git.d.ts +11 -0
- package/dist/execution/git.js +146 -0
- package/dist/execution/ssh.d.ts +2 -0
- package/dist/execution/ssh.js +17 -0
- package/dist/handlers/executor.d.ts +8 -0
- package/dist/handlers/executor.js +216 -0
- package/dist/healing/claudeHealer.d.ts +17 -0
- package/dist/healing/claudeHealer.js +300 -0
- package/dist/healing/patchPromoter.d.ts +25 -0
- package/dist/healing/patchPromoter.js +118 -0
- package/dist/healing/ruleBuilder.d.ts +5 -0
- package/dist/healing/ruleBuilder.js +111 -0
- package/dist/healing/ruleRepairer.d.ts +8 -0
- package/dist/healing/ruleRepairer.js +29 -0
- package/dist/healing/ruleValidator.d.ts +22 -0
- package/dist/healing/ruleValidator.js +145 -0
- package/dist/healing/runHealer.d.ts +11 -0
- package/dist/healing/runHealer.js +74 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +62 -0
- package/dist/intents/catalog.d.ts +4 -0
- package/dist/intents/catalog.js +7 -0
- package/dist/nlp/disambiguate.d.ts +2 -0
- package/dist/nlp/disambiguate.js +46 -0
- package/dist/nlp/fuzzyResolver.d.ts +14 -0
- package/dist/nlp/fuzzyResolver.js +108 -0
- package/dist/nlp/llmFallback.d.ts +63 -0
- package/dist/nlp/llmFallback.js +338 -0
- package/dist/nlp/llmParser.d.ts +8 -0
- package/dist/nlp/llmParser.js +118 -0
- package/dist/nlp/multiClassifier.d.ts +39 -0
- package/dist/nlp/multiClassifier.js +181 -0
- package/dist/nlp/parseIntent.d.ts +2 -0
- package/dist/nlp/parseIntent.js +34 -0
- package/dist/nlp/ruleParser.d.ts +2 -0
- package/dist/nlp/ruleParser.js +234 -0
- package/dist/nlp/semantic.d.ts +104 -0
- package/dist/nlp/semantic.js +419 -0
- package/dist/nlp/uncertainty.d.ts +42 -0
- package/dist/nlp/uncertainty.js +103 -0
- package/dist/parsers/apacheParser.d.ts +50 -0
- package/dist/parsers/apacheParser.js +152 -0
- package/dist/parsers/bindParser.d.ts +40 -0
- package/dist/parsers/bindParser.js +189 -0
- package/dist/parsers/envFile.d.ts +39 -0
- package/dist/parsers/envFile.js +128 -0
- package/dist/parsers/fileFinder.d.ts +30 -0
- package/dist/parsers/fileFinder.js +226 -0
- package/dist/parsers/index.d.ts +27 -0
- package/dist/parsers/index.js +193 -0
- package/dist/parsers/jsonParser.d.ts +16 -0
- package/dist/parsers/jsonParser.js +57 -0
- package/dist/parsers/nginxParser.d.ts +47 -0
- package/dist/parsers/nginxParser.js +161 -0
- package/dist/parsers/passwd.d.ts +25 -0
- package/dist/parsers/passwd.js +41 -0
- package/dist/parsers/shadow.d.ts +23 -0
- package/dist/parsers/shadow.js +50 -0
- package/dist/parsers/yamlParser.d.ts +13 -0
- package/dist/parsers/yamlParser.js +54 -0
- package/dist/policy/confirm.d.ts +2 -0
- package/dist/policy/confirm.js +29 -0
- package/dist/policy/safety.d.ts +4 -0
- package/dist/policy/safety.js +32 -0
- package/dist/types/intent.d.ts +205 -0
- package/dist/types/intent.js +32 -0
- package/dist/types/rules.d.ts +237 -0
- package/dist/types/rules.js +50 -0
- package/dist/utils/analysis.d.ts +25 -0
- package/dist/utils/analysis.js +307 -0
- package/dist/utils/autoBackup.d.ts +43 -0
- package/dist/utils/autoBackup.js +144 -0
- package/dist/utils/config.d.ts +11 -0
- package/dist/utils/config.js +32 -0
- package/dist/utils/dirAnalysis.d.ts +23 -0
- package/dist/utils/dirAnalysis.js +192 -0
- package/dist/utils/explain.d.ts +8 -0
- package/dist/utils/explain.js +145 -0
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +29 -0
- package/dist/utils/output.d.ts +2 -0
- package/dist/utils/output.js +26 -0
- package/dist/utils/paths.d.ts +26 -0
- package/dist/utils/paths.js +47 -0
- package/dist/utils/permissions.d.ts +64 -0
- package/dist/utils/permissions.js +298 -0
- package/dist/utils/platform.d.ts +53 -0
- package/dist/utils/platform.js +253 -0
- package/dist/utils/smartFile.d.ts +29 -0
- package/dist/utils/smartFile.js +188 -0
- package/dist/utils/spinner.d.ts +53 -0
- package/dist/utils/spinner.js +140 -0
- package/dist/utils/verbose.d.ts +27 -0
- package/dist/utils/verbose.js +131 -0
- package/dist/utils/wslPaths.d.ts +31 -0
- package/dist/utils/wslPaths.js +145 -0
- package/package.json +39 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { loadRules, loadIntents } from "../utils/config.js";
|
|
2
|
+
import { RulePatch as RulePatchSchema } from "../types/rules.js";
|
|
3
|
+
/**
|
|
4
|
+
* RuleBuilder: asks an LLM to propose new rules from a set of example phrases.
|
|
5
|
+
*/
|
|
6
|
+
export async function buildRulesFromExamples(examples) {
|
|
7
|
+
const endpoint = process.env.MYCLI_LLM_ENDPOINT;
|
|
8
|
+
if (!endpoint) {
|
|
9
|
+
console.error("Set MYCLI_LLM_ENDPOINT to use the RuleBuilder.");
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const rules = loadRules();
|
|
13
|
+
const intents = loadIntents();
|
|
14
|
+
const apiKey = process.env.MYCLI_LLM_API_KEY ?? "";
|
|
15
|
+
const intentList = intents.map((i) => `- ${i.name}: ${i.description}`).join("\n");
|
|
16
|
+
const prompt = `You are a rule builder for a CLI command parser.
|
|
17
|
+
|
|
18
|
+
Current rules config:
|
|
19
|
+
${JSON.stringify(rules, null, 2)}
|
|
20
|
+
|
|
21
|
+
Supported intents (from intents.json):
|
|
22
|
+
${intentList}
|
|
23
|
+
|
|
24
|
+
The following user phrases were NOT understood by the current parser:
|
|
25
|
+
${examples.map((e) => `- "${e}"`).join("\n")}
|
|
26
|
+
|
|
27
|
+
Analyze each phrase and propose a structured patch to expand the rules.
|
|
28
|
+
|
|
29
|
+
Return a JSON object with this exact schema:
|
|
30
|
+
{
|
|
31
|
+
"summary": "what this patch does",
|
|
32
|
+
"confidence": 0.0-1.0,
|
|
33
|
+
"changes": [
|
|
34
|
+
{ "type": "add_intent_synonym", "intent": "...", "phrase": "..." },
|
|
35
|
+
{ "type": "add_env_alias", "canonical": "...", "alias": "..." },
|
|
36
|
+
{ "type": "add_service_alias", "canonical": "...", "alias": "..." }
|
|
37
|
+
],
|
|
38
|
+
"tests": [
|
|
39
|
+
{ "input": "...", "expectedIntent": "...", "expectedFields": {} },
|
|
40
|
+
{ "input": "...", "shouldReject": true }
|
|
41
|
+
],
|
|
42
|
+
"warnings": ["any overlap or risk concerns"]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Rules:
|
|
46
|
+
- Only add synonyms/aliases that clearly map to existing intents or entities.
|
|
47
|
+
- Include at least one positive and one negative test per change.
|
|
48
|
+
- Warn if a new synonym could overlap with another intent.
|
|
49
|
+
- Do NOT invent new intents.
|
|
50
|
+
- Return ONLY the JSON object.`;
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch(endpoint, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}`, "x-api-key": apiKey } : {}),
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
model: process.env.MYCLI_LLM_MODEL ?? "claude-sonnet-4-20250514",
|
|
60
|
+
max_tokens: 1024,
|
|
61
|
+
messages: [{ role: "user", content: prompt }],
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
if (!response.ok)
|
|
65
|
+
return null;
|
|
66
|
+
const data = (await response.json());
|
|
67
|
+
const content = extractContent(data);
|
|
68
|
+
if (!content)
|
|
69
|
+
return null;
|
|
70
|
+
const json = extractJSON(content);
|
|
71
|
+
if (!json)
|
|
72
|
+
return null;
|
|
73
|
+
const parsed = RulePatchSchema.safeParse(json);
|
|
74
|
+
if (!parsed.success) {
|
|
75
|
+
console.error("LLM returned invalid patch:", parsed.error.message);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return parsed.data;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.error("RuleBuilder error:", err);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function extractContent(data) {
|
|
86
|
+
if (data.choices && Array.isArray(data.choices)) {
|
|
87
|
+
const msg = data.choices[0]?.message;
|
|
88
|
+
if (msg?.content && typeof msg.content === "string")
|
|
89
|
+
return msg.content;
|
|
90
|
+
}
|
|
91
|
+
if (data.content && Array.isArray(data.content)) {
|
|
92
|
+
const block = data.content[0];
|
|
93
|
+
if (block?.text && typeof block.text === "string")
|
|
94
|
+
return block.text;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function extractJSON(text) {
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(text.trim());
|
|
101
|
+
}
|
|
102
|
+
catch { }
|
|
103
|
+
const match = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
104
|
+
if (match) {
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(match[1].trim());
|
|
107
|
+
}
|
|
108
|
+
catch { }
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RulePatch } from "../types/rules.js";
|
|
2
|
+
/**
|
|
3
|
+
* RuleRepairer: reads the failure log and proposes patches to fix unmatched inputs.
|
|
4
|
+
*
|
|
5
|
+
* It batches recent failures, deduplicates, and sends them to the RuleBuilder
|
|
6
|
+
* which uses an LLM to propose structured changes.
|
|
7
|
+
*/
|
|
8
|
+
export declare function repairFromFailures(maxFailures?: number): Promise<RulePatch | null>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { loadFailures } from "../utils/logger.js";
|
|
2
|
+
import { buildRulesFromExamples } from "./ruleBuilder.js";
|
|
3
|
+
/**
|
|
4
|
+
* RuleRepairer: reads the failure log and proposes patches to fix unmatched inputs.
|
|
5
|
+
*
|
|
6
|
+
* It batches recent failures, deduplicates, and sends them to the RuleBuilder
|
|
7
|
+
* which uses an LLM to propose structured changes.
|
|
8
|
+
*/
|
|
9
|
+
export async function repairFromFailures(maxFailures = 20) {
|
|
10
|
+
const failures = loadFailures();
|
|
11
|
+
if (failures.length === 0) {
|
|
12
|
+
console.log("No failures to repair.");
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
// Deduplicate by rawText, take the most recent N
|
|
16
|
+
const unique = deduplicateFailures(failures);
|
|
17
|
+
const batch = unique.slice(-maxFailures);
|
|
18
|
+
console.log(`Repairing from ${batch.length} unique failure(s)...`);
|
|
19
|
+
const examples = batch.map((f) => f.rawText);
|
|
20
|
+
return buildRulesFromExamples(examples);
|
|
21
|
+
}
|
|
22
|
+
function deduplicateFailures(failures) {
|
|
23
|
+
const seen = new Map();
|
|
24
|
+
for (const f of failures) {
|
|
25
|
+
const key = f.rawText.trim().toLowerCase();
|
|
26
|
+
seen.set(key, f); // keep latest
|
|
27
|
+
}
|
|
28
|
+
return Array.from(seen.values());
|
|
29
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RulePatch } from "../types/rules.js";
|
|
2
|
+
export interface ValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: string[];
|
|
5
|
+
warnings: string[];
|
|
6
|
+
testResults: Array<{
|
|
7
|
+
input: string;
|
|
8
|
+
passed: boolean;
|
|
9
|
+
reason?: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* RuleValidator: checks a proposed patch for safety before promotion.
|
|
14
|
+
*
|
|
15
|
+
* Validates:
|
|
16
|
+
* - No overlapping synonyms across different intents
|
|
17
|
+
* - All referenced intents exist
|
|
18
|
+
* - All referenced environments/services exist
|
|
19
|
+
* - Test cases pass against the patched rule set
|
|
20
|
+
* - No dangerous broadening of high-risk intents
|
|
21
|
+
*/
|
|
22
|
+
export declare function validatePatch(patch: RulePatch): ValidationResult;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { loadRules, loadIntents } from "../utils/config.js";
|
|
2
|
+
import { parseByRules } from "../nlp/ruleParser.js";
|
|
3
|
+
/**
|
|
4
|
+
* RuleValidator: checks a proposed patch for safety before promotion.
|
|
5
|
+
*
|
|
6
|
+
* Validates:
|
|
7
|
+
* - No overlapping synonyms across different intents
|
|
8
|
+
* - All referenced intents exist
|
|
9
|
+
* - All referenced environments/services exist
|
|
10
|
+
* - Test cases pass against the patched rule set
|
|
11
|
+
* - No dangerous broadening of high-risk intents
|
|
12
|
+
*/
|
|
13
|
+
export function validatePatch(patch) {
|
|
14
|
+
const rules = loadRules();
|
|
15
|
+
const errors = [];
|
|
16
|
+
const warnings = [...patch.warnings];
|
|
17
|
+
// Check each change
|
|
18
|
+
for (const change of patch.changes) {
|
|
19
|
+
switch (change.type) {
|
|
20
|
+
case "add_intent_synonym": {
|
|
21
|
+
const knownIntent = rules.intentSynonyms[change.intent] || loadIntents().some((i) => i.name === change.intent);
|
|
22
|
+
if (!knownIntent) {
|
|
23
|
+
errors.push(`Unknown intent: ${change.intent}`);
|
|
24
|
+
}
|
|
25
|
+
// Check for overlap with other intents
|
|
26
|
+
const overlap = findSynonymOverlap(change.phrase, change.intent, rules);
|
|
27
|
+
if (overlap) {
|
|
28
|
+
errors.push(`Synonym "${change.phrase}" overlaps with intent "${overlap}"`);
|
|
29
|
+
}
|
|
30
|
+
// Check for overly broad synonyms
|
|
31
|
+
if (change.phrase.length <= 2) {
|
|
32
|
+
errors.push(`Synonym "${change.phrase}" is too short / too broad`);
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case "add_env_alias": {
|
|
37
|
+
if (!rules.environmentAliases[change.canonical]) {
|
|
38
|
+
errors.push(`Unknown environment: ${change.canonical}`);
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case "add_service_alias": {
|
|
43
|
+
if (!rules.serviceAliases[change.canonical]) {
|
|
44
|
+
errors.push(`Unknown service: ${change.canonical}`);
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case "remove_intent_synonym": {
|
|
49
|
+
const existing = rules.intentSynonyms[change.intent];
|
|
50
|
+
if (!existing?.includes(change.phrase)) {
|
|
51
|
+
warnings.push(`Synonym "${change.phrase}" not found in intent "${change.intent}"`);
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Simulate applying the patch and run tests
|
|
58
|
+
const patchedRules = simulateApply(rules, patch);
|
|
59
|
+
const testResults = runTests(patch, patchedRules);
|
|
60
|
+
const failedTests = testResults.filter((t) => !t.passed);
|
|
61
|
+
if (failedTests.length > 0) {
|
|
62
|
+
errors.push(`${failedTests.length} test(s) failed`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
valid: errors.length === 0,
|
|
66
|
+
errors,
|
|
67
|
+
warnings,
|
|
68
|
+
testResults,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function findSynonymOverlap(phrase, excludeIntent, rules) {
|
|
72
|
+
// Check rules.json synonyms
|
|
73
|
+
for (const [intent, phrases] of Object.entries(rules.intentSynonyms)) {
|
|
74
|
+
if (intent === excludeIntent)
|
|
75
|
+
continue;
|
|
76
|
+
if (phrases.includes(phrase))
|
|
77
|
+
return intent;
|
|
78
|
+
}
|
|
79
|
+
// Also check intents.json synonyms (primary source now)
|
|
80
|
+
for (const def of loadIntents()) {
|
|
81
|
+
if (def.name === excludeIntent)
|
|
82
|
+
continue;
|
|
83
|
+
if (def.synonyms.includes(phrase))
|
|
84
|
+
return def.name;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
function simulateApply(rules, patch) {
|
|
89
|
+
const clone = JSON.parse(JSON.stringify(rules));
|
|
90
|
+
for (const change of patch.changes) {
|
|
91
|
+
switch (change.type) {
|
|
92
|
+
case "add_intent_synonym":
|
|
93
|
+
if (clone.intentSynonyms[change.intent]) {
|
|
94
|
+
clone.intentSynonyms[change.intent].push(change.phrase);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case "add_env_alias":
|
|
98
|
+
if (clone.environmentAliases[change.canonical]) {
|
|
99
|
+
clone.environmentAliases[change.canonical].push(change.alias);
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
case "add_service_alias":
|
|
103
|
+
if (clone.serviceAliases[change.canonical]) {
|
|
104
|
+
clone.serviceAliases[change.canonical].push(change.alias);
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
case "remove_intent_synonym":
|
|
108
|
+
if (clone.intentSynonyms[change.intent]) {
|
|
109
|
+
clone.intentSynonyms[change.intent] = clone.intentSynonyms[change.intent].filter((p) => p !== change.phrase);
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return clone;
|
|
115
|
+
}
|
|
116
|
+
function runTests(patch, _patchedRules) {
|
|
117
|
+
// We test against current loaded rules + patch applied
|
|
118
|
+
// For now, we use parseByRules which reads from loadRules()
|
|
119
|
+
// In a full implementation, you'd inject the patched rules
|
|
120
|
+
return patch.tests.map((test) => {
|
|
121
|
+
const result = parseByRules(test.input);
|
|
122
|
+
if (test.shouldReject) {
|
|
123
|
+
return {
|
|
124
|
+
input: test.input,
|
|
125
|
+
passed: result === null || result.intent === "unknown",
|
|
126
|
+
reason: result ? `Matched as ${result.intent} but should reject` : undefined,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (!result) {
|
|
130
|
+
return {
|
|
131
|
+
input: test.input,
|
|
132
|
+
passed: false,
|
|
133
|
+
reason: "No parse result",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (test.expectedIntent && result.intent !== test.expectedIntent) {
|
|
137
|
+
return {
|
|
138
|
+
input: test.input,
|
|
139
|
+
passed: false,
|
|
140
|
+
reason: `Expected ${test.expectedIntent}, got ${result.intent}`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { input: test.input, passed: true };
|
|
144
|
+
});
|
|
145
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Standalone healer script.
|
|
4
|
+
*
|
|
5
|
+
* Reads the failure log, asks the LLM to propose fixes, validates,
|
|
6
|
+
* and optionally promotes the patch.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx tsx src/healing/runHealer.ts [--promote] [--force] [--dry-run]
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Standalone healer script.
|
|
4
|
+
*
|
|
5
|
+
* Reads the failure log, asks the LLM to propose fixes, validates,
|
|
6
|
+
* and optionally promotes the patch.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx tsx src/healing/runHealer.ts [--promote] [--force] [--dry-run]
|
|
10
|
+
*/
|
|
11
|
+
import { repairFromFailures } from "./ruleRepairer.js";
|
|
12
|
+
import { validatePatch } from "./ruleValidator.js";
|
|
13
|
+
import { promotePatch } from "./patchPromoter.js";
|
|
14
|
+
import { clearFailures } from "../utils/logger.js";
|
|
15
|
+
async function main() {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const shouldPromote = args.includes("--promote");
|
|
18
|
+
const force = args.includes("--force");
|
|
19
|
+
const dryRun = args.includes("--dry-run");
|
|
20
|
+
console.log("=== Auto-Learning Rule Repairer ===\n");
|
|
21
|
+
const patch = await repairFromFailures();
|
|
22
|
+
if (!patch) {
|
|
23
|
+
console.log("No patch generated.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
console.log("\n--- Proposed Patch ---");
|
|
27
|
+
console.log(`Summary: ${patch.summary}`);
|
|
28
|
+
console.log(`Confidence: ${(patch.confidence * 100).toFixed(0)}%`);
|
|
29
|
+
console.log(`Changes: ${patch.changes.length}`);
|
|
30
|
+
for (const c of patch.changes) {
|
|
31
|
+
console.log(` [${c.type}] ${JSON.stringify(c)}`);
|
|
32
|
+
}
|
|
33
|
+
console.log(`\nTests: ${patch.tests.length}`);
|
|
34
|
+
for (const t of patch.tests) {
|
|
35
|
+
const label = t.shouldReject ? "REJECT" : t.expectedIntent ?? "?";
|
|
36
|
+
console.log(` "${t.input}" => ${label}`);
|
|
37
|
+
}
|
|
38
|
+
if (patch.warnings.length > 0) {
|
|
39
|
+
console.log(`\nWarnings:`);
|
|
40
|
+
for (const w of patch.warnings)
|
|
41
|
+
console.log(` - ${w}`);
|
|
42
|
+
}
|
|
43
|
+
console.log("\n--- Validation ---");
|
|
44
|
+
const validation = validatePatch(patch);
|
|
45
|
+
console.log(`Valid: ${validation.valid}`);
|
|
46
|
+
if (validation.errors.length > 0) {
|
|
47
|
+
console.log("Errors:");
|
|
48
|
+
for (const e of validation.errors)
|
|
49
|
+
console.log(` - ${e}`);
|
|
50
|
+
}
|
|
51
|
+
console.log("Test results:");
|
|
52
|
+
for (const t of validation.testResults) {
|
|
53
|
+
console.log(` ${t.passed ? "PASS" : "FAIL"} "${t.input}"${t.reason ? ` (${t.reason})` : ""}`);
|
|
54
|
+
}
|
|
55
|
+
if (shouldPromote) {
|
|
56
|
+
console.log("\n--- Promoting ---");
|
|
57
|
+
const result = promotePatch(patch, { force, dryRun });
|
|
58
|
+
if (result.promoted) {
|
|
59
|
+
console.log("Patch applied successfully.");
|
|
60
|
+
clearFailures();
|
|
61
|
+
console.log("Failure log cleared.");
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log("Patch not promoted.");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log("\nRun with --promote to apply this patch.");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
main().catch((err) => {
|
|
72
|
+
console.error("Healer error:", err);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* notoken-core — shared library.
|
|
3
|
+
*
|
|
4
|
+
* Exports the core engine that both CLI and Electron app use.
|
|
5
|
+
* This is the single source of truth for all notoken functionality.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { parse, execute, detect, install, doctor } from "notoken/core";
|
|
9
|
+
*/
|
|
10
|
+
export { parseIntent } from "./nlp/parseIntent.js";
|
|
11
|
+
export { parseByRules } from "./nlp/ruleParser.js";
|
|
12
|
+
export { disambiguate } from "./nlp/disambiguate.js";
|
|
13
|
+
export { classifyMulti } from "./nlp/multiClassifier.js";
|
|
14
|
+
export { semanticParse, tokenize, keyboardDistance, fuzzyMatch } from "./nlp/semantic.js";
|
|
15
|
+
export { analyzeUncertainty, getUncoveredSpans } from "./nlp/uncertainty.js";
|
|
16
|
+
export { llmFallback, isLLMConfigured, getLLMBackend, formatLLMFallback } from "./nlp/llmFallback.js";
|
|
17
|
+
export { executeIntent } from "./handlers/executor.js";
|
|
18
|
+
export { runRemoteCommand, runLocalCommand } from "./execution/ssh.js";
|
|
19
|
+
export { loadRules, loadIntents, getIntentDef, loadHosts, getConfigDir } from "./utils/config.js";
|
|
20
|
+
export { CONFIG_DIR, DATA_DIR, LOG_DIR, PACKAGE_ROOT, USER_HOME, isSEA, ensureUserDirs } from "./utils/paths.js";
|
|
21
|
+
export { detectLocalPlatform, getInstallCommand, getServiceCommand, getPackageForCommand, formatPlatform } from "./utils/platform.js";
|
|
22
|
+
export type { PlatformInfo } from "./utils/platform.js";
|
|
23
|
+
export { winToLinux, linuxToWin, normalizePath, getUserDirs, isWSL, isWindows } from "./utils/wslPaths.js";
|
|
24
|
+
export { getLocalPermissions, getRemotePermissions, checkAccessForIntent, parsePermissionRequest, formatPermissionsDisplay } from "./utils/permissions.js";
|
|
25
|
+
export { analyzeOutput, analyzeLoad, analyzeDisk, analyzeMemory } from "./utils/analysis.js";
|
|
26
|
+
export { analyzeDirectory } from "./utils/dirAnalysis.js";
|
|
27
|
+
export { smartRead, smartSearch, getFileInfo } from "./utils/smartFile.js";
|
|
28
|
+
export { parseFile, formatParsedFile, detectFileType } from "./parsers/index.js";
|
|
29
|
+
export { findKnownLocations, searchRemoteFile } from "./parsers/fileFinder.js";
|
|
30
|
+
export { getOrCreateConversation, addUserTurn, addSystemTurn, saveConversation, getLastEntity, getRecentEntities, listConversations } from "./conversation/store.js";
|
|
31
|
+
export type { Conversation, ConversationTurn, KnowledgeNode, UncertaintyReport } from "./conversation/store.js";
|
|
32
|
+
export { resolveCoreferences, extractEntitiesFromFields } from "./conversation/coreference.js";
|
|
33
|
+
export { redactSecrets, listSecrets, clearSecrets, saveSecretsToFile, resolvePlaceholders } from "./conversation/secrets.js";
|
|
34
|
+
export { taskRunner, type BackgroundTask } from "./agents/taskRunner.js";
|
|
35
|
+
export { agentSpawner, type AgentHandle } from "./agents/agentSpawner.js";
|
|
36
|
+
export { createPlan, formatPlan } from "./agents/planner.js";
|
|
37
|
+
export { loadPlaybooks, getPlaybook, runPlaybook, formatPlaybookList } from "./agents/playbookRunner.js";
|
|
38
|
+
export { validatePatch } from "./healing/ruleValidator.js";
|
|
39
|
+
export { promotePatch } from "./healing/patchPromoter.js";
|
|
40
|
+
export { validateIntent, isDangerous, getRiskLevel } from "./policy/safety.js";
|
|
41
|
+
export { askForConfirmation, askForChoice } from "./policy/confirm.js";
|
|
42
|
+
export { formatVerbose, formatTaskNotification, formatJobsList } from "./utils/verbose.js";
|
|
43
|
+
export { formatExplain } from "./utils/explain.js";
|
|
44
|
+
export { formatParsedCommand } from "./utils/output.js";
|
|
45
|
+
export { Spinner, withSpinner, progressBar } from "./utils/spinner.js";
|
|
46
|
+
export { createBackup, rollback, listBackups, cleanExpiredBackups, formatBackupList } from "./utils/autoBackup.js";
|
|
47
|
+
export { logFailure, loadFailures, clearFailures } from "./utils/logger.js";
|
|
48
|
+
export { logUncertainty, loadUncertaintyLog, getUncertaintySummary } from "./nlp/uncertainty.js";
|
|
49
|
+
export { recordHistory, loadHistory, getRecentHistory, searchHistory } from "./context/history.js";
|
|
50
|
+
export type { DynamicIntent, ParsedCommand, IntentDef, FieldDef, EnvironmentName } from "./types/intent.js";
|
|
51
|
+
export type { RulePatch, RulePatchChange, FailureLog, RulesConfig } from "./types/rules.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* notoken-core — shared library.
|
|
3
|
+
*
|
|
4
|
+
* Exports the core engine that both CLI and Electron app use.
|
|
5
|
+
* This is the single source of truth for all notoken functionality.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { parse, execute, detect, install, doctor } from "notoken/core";
|
|
9
|
+
*/
|
|
10
|
+
// ── NLP & Parsing ──
|
|
11
|
+
export { parseIntent } from "./nlp/parseIntent.js";
|
|
12
|
+
export { parseByRules } from "./nlp/ruleParser.js";
|
|
13
|
+
export { disambiguate } from "./nlp/disambiguate.js";
|
|
14
|
+
export { classifyMulti } from "./nlp/multiClassifier.js";
|
|
15
|
+
export { semanticParse, tokenize, keyboardDistance, fuzzyMatch } from "./nlp/semantic.js";
|
|
16
|
+
export { analyzeUncertainty, getUncoveredSpans } from "./nlp/uncertainty.js";
|
|
17
|
+
export { llmFallback, isLLMConfigured, getLLMBackend, formatLLMFallback } from "./nlp/llmFallback.js";
|
|
18
|
+
// ── Execution ──
|
|
19
|
+
export { executeIntent } from "./handlers/executor.js";
|
|
20
|
+
export { runRemoteCommand, runLocalCommand } from "./execution/ssh.js";
|
|
21
|
+
// ── Config ──
|
|
22
|
+
export { loadRules, loadIntents, getIntentDef, loadHosts, getConfigDir } from "./utils/config.js";
|
|
23
|
+
export { CONFIG_DIR, DATA_DIR, LOG_DIR, PACKAGE_ROOT, USER_HOME, isSEA, ensureUserDirs } from "./utils/paths.js";
|
|
24
|
+
// ── Platform & Detection ──
|
|
25
|
+
export { detectLocalPlatform, getInstallCommand, getServiceCommand, getPackageForCommand, formatPlatform } from "./utils/platform.js";
|
|
26
|
+
// ── Paths ──
|
|
27
|
+
export { winToLinux, linuxToWin, normalizePath, getUserDirs, isWSL, isWindows } from "./utils/wslPaths.js";
|
|
28
|
+
// ── Permissions ──
|
|
29
|
+
export { getLocalPermissions, getRemotePermissions, checkAccessForIntent, parsePermissionRequest, formatPermissionsDisplay } from "./utils/permissions.js";
|
|
30
|
+
// ── Analysis ──
|
|
31
|
+
export { analyzeOutput, analyzeLoad, analyzeDisk, analyzeMemory } from "./utils/analysis.js";
|
|
32
|
+
export { analyzeDirectory } from "./utils/dirAnalysis.js";
|
|
33
|
+
// ── File Operations ──
|
|
34
|
+
export { smartRead, smartSearch, getFileInfo } from "./utils/smartFile.js";
|
|
35
|
+
export { parseFile, formatParsedFile, detectFileType } from "./parsers/index.js";
|
|
36
|
+
export { findKnownLocations, searchRemoteFile } from "./parsers/fileFinder.js";
|
|
37
|
+
// ── Conversation ──
|
|
38
|
+
export { getOrCreateConversation, addUserTurn, addSystemTurn, saveConversation, getLastEntity, getRecentEntities, listConversations } from "./conversation/store.js";
|
|
39
|
+
export { resolveCoreferences, extractEntitiesFromFields } from "./conversation/coreference.js";
|
|
40
|
+
export { redactSecrets, listSecrets, clearSecrets, saveSecretsToFile, resolvePlaceholders } from "./conversation/secrets.js";
|
|
41
|
+
// ── Agents & Background ──
|
|
42
|
+
export { taskRunner } from "./agents/taskRunner.js";
|
|
43
|
+
export { agentSpawner } from "./agents/agentSpawner.js";
|
|
44
|
+
export { createPlan, formatPlan } from "./agents/planner.js";
|
|
45
|
+
export { loadPlaybooks, getPlaybook, runPlaybook, formatPlaybookList } from "./agents/playbookRunner.js";
|
|
46
|
+
// ── Healing / Learning ──
|
|
47
|
+
export { validatePatch } from "./healing/ruleValidator.js";
|
|
48
|
+
export { promotePatch } from "./healing/patchPromoter.js";
|
|
49
|
+
// ── Safety ──
|
|
50
|
+
export { validateIntent, isDangerous, getRiskLevel } from "./policy/safety.js";
|
|
51
|
+
export { askForConfirmation, askForChoice } from "./policy/confirm.js";
|
|
52
|
+
// ── UI Helpers ──
|
|
53
|
+
export { formatVerbose, formatTaskNotification, formatJobsList } from "./utils/verbose.js";
|
|
54
|
+
export { formatExplain } from "./utils/explain.js";
|
|
55
|
+
export { formatParsedCommand } from "./utils/output.js";
|
|
56
|
+
export { Spinner, withSpinner, progressBar } from "./utils/spinner.js";
|
|
57
|
+
// ── Auto-backup ──
|
|
58
|
+
export { createBackup, rollback, listBackups, cleanExpiredBackups, formatBackupList } from "./utils/autoBackup.js";
|
|
59
|
+
// ── Logging ──
|
|
60
|
+
export { logFailure, loadFailures, clearFailures } from "./utils/logger.js";
|
|
61
|
+
export { logUncertainty, loadUncertaintyLog, getUncertaintySummary } from "./nlp/uncertainty.js";
|
|
62
|
+
export { recordHistory, loadHistory, getRecentHistory, searchHistory } from "./context/history.js";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getIntentDef } from "../utils/config.js";
|
|
2
|
+
import { loadRules } from "../utils/config.js";
|
|
3
|
+
const CONFIDENCE_THRESHOLD = 0.6;
|
|
4
|
+
export function disambiguate(intent) {
|
|
5
|
+
const def = getIntentDef(intent.intent);
|
|
6
|
+
const missingFields = [];
|
|
7
|
+
const ambiguousFields = [];
|
|
8
|
+
if (def) {
|
|
9
|
+
for (const [fieldName, fieldDef] of Object.entries(def.fields)) {
|
|
10
|
+
if (fieldDef.required && intent.fields[fieldName] === undefined) {
|
|
11
|
+
missingFields.push(fieldName);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Check for ambiguous service references
|
|
15
|
+
for (const [fieldName, fieldDef] of Object.entries(def.fields)) {
|
|
16
|
+
if (fieldDef.type === "service" && intent.fields[fieldName]) {
|
|
17
|
+
const candidates = findServiceCandidates(intent.fields[fieldName]);
|
|
18
|
+
if (candidates.length > 1) {
|
|
19
|
+
ambiguousFields.push({ field: fieldName, candidates });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const needsClarification = missingFields.length > 0 ||
|
|
25
|
+
ambiguousFields.length > 0 ||
|
|
26
|
+
intent.confidence < CONFIDENCE_THRESHOLD;
|
|
27
|
+
return {
|
|
28
|
+
intent,
|
|
29
|
+
missingFields,
|
|
30
|
+
ambiguousFields,
|
|
31
|
+
needsClarification,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function findServiceCandidates(input) {
|
|
35
|
+
const rules = loadRules();
|
|
36
|
+
const matches = [];
|
|
37
|
+
for (const [canonical, aliases] of Object.entries(rules.serviceAliases)) {
|
|
38
|
+
for (const alias of aliases) {
|
|
39
|
+
if (alias.includes(input) || input.includes(alias)) {
|
|
40
|
+
matches.push(canonical);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return matches;
|
|
46
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DynamicIntent } from "../types/intent.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fuzzy file resolver.
|
|
4
|
+
*
|
|
5
|
+
* When an intent has fuzzyResolve fields, this attempts to find the actual
|
|
6
|
+
* file path on the target server using a combination of:
|
|
7
|
+
* - exact match
|
|
8
|
+
* - find by filename
|
|
9
|
+
* - find by partial name (fuzzy)
|
|
10
|
+
* - locate database
|
|
11
|
+
*
|
|
12
|
+
* Returns the intent with resolved file paths in the fields.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveFuzzyFields(intent: DynamicIntent): Promise<DynamicIntent>;
|