aspectcode 0.4.1 → 1.0.1
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/README.md +2 -2
- package/dist/agentsMdRenderer.d.ts +16 -0
- package/dist/agentsMdRenderer.d.ts.map +1 -0
- package/dist/agentsMdRenderer.js +137 -0
- package/dist/agentsMdRenderer.js.map +1 -0
- package/dist/auth.d.ts +31 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +386 -0
- package/dist/auth.js.map +1 -0
- package/dist/autoResolve.d.ts +41 -0
- package/dist/autoResolve.d.ts.map +1 -0
- package/dist/autoResolve.js +196 -0
- package/dist/autoResolve.js.map +1 -0
- package/dist/changeEvaluator.d.ts +56 -0
- package/dist/changeEvaluator.d.ts.map +1 -0
- package/dist/changeEvaluator.js +674 -0
- package/dist/changeEvaluator.js.map +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +37 -17
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +50 -2
- package/dist/config.js.map +1 -1
- package/dist/dreamCycle.d.ts +57 -0
- package/dist/dreamCycle.d.ts.map +1 -0
- package/dist/dreamCycle.js +334 -0
- package/dist/dreamCycle.js.map +1 -0
- package/dist/kbBuilder.d.ts +1 -2
- package/dist/kbBuilder.d.ts.map +1 -1
- package/dist/kbBuilder.js +1 -2
- package/dist/kbBuilder.js.map +1 -1
- package/dist/main.d.ts +2 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +148 -7
- package/dist/main.js.map +1 -1
- package/dist/optimize.d.ts +13 -6
- package/dist/optimize.d.ts.map +1 -1
- package/dist/optimize.js +433 -142
- package/dist/optimize.js.map +1 -1
- package/dist/pipeline.d.ts +19 -21
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +1093 -160
- package/dist/pipeline.js.map +1 -1
- package/dist/preferences.d.ts +80 -0
- package/dist/preferences.d.ts.map +1 -0
- package/dist/preferences.js +238 -0
- package/dist/preferences.js.map +1 -0
- package/dist/runtimeState.d.ts +30 -0
- package/dist/runtimeState.d.ts.map +1 -0
- package/dist/runtimeState.js +39 -0
- package/dist/runtimeState.js.map +1 -0
- package/dist/scopedRules.d.ts +84 -0
- package/dist/scopedRules.d.ts.map +1 -0
- package/dist/scopedRules.js +449 -0
- package/dist/scopedRules.js.map +1 -0
- package/dist/ui/Dashboard.d.ts +4 -16
- package/dist/ui/Dashboard.d.ts.map +1 -1
- package/dist/ui/Dashboard.js +339 -140
- package/dist/ui/Dashboard.js.map +1 -1
- package/dist/ui/MemoryMap.d.ts +16 -0
- package/dist/ui/MemoryMap.d.ts.map +1 -0
- package/dist/ui/MemoryMap.js +266 -0
- package/dist/ui/MemoryMap.js.map +1 -0
- package/dist/ui/SettingsPanel.d.ts +18 -0
- package/dist/ui/SettingsPanel.d.ts.map +1 -0
- package/dist/ui/SettingsPanel.js +241 -0
- package/dist/ui/SettingsPanel.js.map +1 -0
- package/dist/ui/prompts.d.ts +7 -0
- package/dist/ui/prompts.d.ts.map +1 -1
- package/dist/ui/prompts.js +63 -0
- package/dist/ui/prompts.js.map +1 -1
- package/dist/ui/store.d.ts +154 -18
- package/dist/ui/store.d.ts.map +1 -1
- package/dist/ui/store.js +154 -24
- package/dist/ui/store.js.map +1 -1
- package/dist/ui/theme.d.ts +1 -8
- package/dist/ui/theme.d.ts.map +1 -1
- package/dist/ui/theme.js +2 -21
- package/dist/ui/theme.js.map +1 -1
- package/dist/updateChecker.d.ts +13 -0
- package/dist/updateChecker.d.ts.map +1 -0
- package/dist/updateChecker.js +66 -0
- package/dist/updateChecker.js.map +1 -0
- package/dist/usageTracker.d.ts +12 -0
- package/dist/usageTracker.d.ts.map +1 -0
- package/dist/usageTracker.js +89 -0
- package/dist/usageTracker.js.map +1 -0
- package/dist/writer.d.ts +1 -7
- package/dist/writer.d.ts.map +1 -1
- package/dist/writer.js +1 -11
- package/dist/writer.js.map +1 -1
- package/node_modules/@aspectcode/core/dist/analysis/repo.d.ts.map +1 -1
- package/node_modules/@aspectcode/core/dist/analysis/repo.js +13 -2
- package/node_modules/@aspectcode/core/dist/analysis/repo.js.map +1 -1
- package/node_modules/@aspectcode/core/dist/index.d.ts +1 -3
- package/node_modules/@aspectcode/core/dist/index.d.ts.map +1 -1
- package/node_modules/@aspectcode/core/dist/index.js +1 -3
- package/node_modules/@aspectcode/core/dist/index.js.map +1 -1
- package/node_modules/@aspectcode/core/dist/parsers/genericExtractors.d.ts +14 -0
- package/node_modules/@aspectcode/core/dist/parsers/genericExtractors.d.ts.map +1 -0
- package/node_modules/@aspectcode/core/dist/parsers/genericExtractors.js +191 -0
- package/node_modules/@aspectcode/core/dist/parsers/genericExtractors.js.map +1 -0
- package/node_modules/@aspectcode/core/dist/parsers/index.d.ts +1 -0
- package/node_modules/@aspectcode/core/dist/parsers/index.d.ts.map +1 -1
- package/node_modules/@aspectcode/core/dist/parsers/index.js +6 -1
- package/node_modules/@aspectcode/core/dist/parsers/index.js.map +1 -1
- package/node_modules/@aspectcode/core/dist/parsers/languages.d.ts +20 -0
- package/node_modules/@aspectcode/core/dist/parsers/languages.d.ts.map +1 -1
- package/node_modules/@aspectcode/core/dist/parsers/languages.js +25 -0
- package/node_modules/@aspectcode/core/dist/parsers/languages.js.map +1 -1
- package/node_modules/@aspectcode/core/dist/parsers/tsJsExtractors.d.ts.map +1 -1
- package/node_modules/@aspectcode/core/dist/parsers/tsJsExtractors.js +4 -1
- package/node_modules/@aspectcode/core/dist/parsers/tsJsExtractors.js.map +1 -1
- package/node_modules/@aspectcode/core/package.json +2 -2
- package/node_modules/@aspectcode/core/parsers/cpp.wasm +0 -0
- package/node_modules/@aspectcode/core/parsers/go.wasm +0 -0
- package/node_modules/@aspectcode/core/parsers/php.wasm +0 -0
- package/node_modules/@aspectcode/core/parsers/ruby.wasm +0 -0
- package/node_modules/@aspectcode/core/parsers/rust.wasm +0 -0
- package/node_modules/@aspectcode/emitters/dist/index.d.ts +1 -17
- package/node_modules/@aspectcode/emitters/dist/index.d.ts.map +1 -1
- package/node_modules/@aspectcode/emitters/dist/index.js +2 -90
- package/node_modules/@aspectcode/emitters/dist/index.js.map +1 -1
- package/node_modules/@aspectcode/emitters/dist/instructions/index.d.ts +0 -2
- package/node_modules/@aspectcode/emitters/dist/instructions/index.d.ts.map +1 -1
- package/node_modules/@aspectcode/emitters/dist/instructions/index.js +1 -7
- package/node_modules/@aspectcode/emitters/dist/instructions/index.js.map +1 -1
- package/node_modules/@aspectcode/emitters/dist/kb/analyzers.d.ts +0 -18
- package/node_modules/@aspectcode/emitters/dist/kb/analyzers.d.ts.map +1 -1
- package/node_modules/@aspectcode/emitters/dist/kb/analyzers.js +0 -57
- package/node_modules/@aspectcode/emitters/dist/kb/analyzers.js.map +1 -1
- package/node_modules/@aspectcode/emitters/dist/kb/conventions.d.ts +0 -18
- package/node_modules/@aspectcode/emitters/dist/kb/conventions.d.ts.map +1 -1
- package/node_modules/@aspectcode/emitters/dist/kb/conventions.js +0 -130
- package/node_modules/@aspectcode/emitters/dist/kb/conventions.js.map +1 -1
- package/node_modules/@aspectcode/emitters/dist/kb/index.d.ts +2 -4
- package/node_modules/@aspectcode/emitters/dist/kb/index.d.ts.map +1 -1
- package/node_modules/@aspectcode/emitters/dist/kb/index.js +1 -11
- package/node_modules/@aspectcode/emitters/dist/kb/index.js.map +1 -1
- package/node_modules/@aspectcode/emitters/package.json +3 -3
- package/node_modules/@aspectcode/evaluator/dist/apply.d.ts +55 -0
- package/node_modules/@aspectcode/evaluator/dist/apply.d.ts.map +1 -0
- package/node_modules/@aspectcode/evaluator/dist/apply.js +368 -0
- package/node_modules/@aspectcode/evaluator/dist/apply.js.map +1 -0
- package/node_modules/@aspectcode/evaluator/dist/diagnosis.d.ts +16 -25
- package/node_modules/@aspectcode/evaluator/dist/diagnosis.d.ts.map +1 -1
- package/node_modules/@aspectcode/evaluator/dist/diagnosis.js +115 -138
- package/node_modules/@aspectcode/evaluator/dist/diagnosis.js.map +1 -1
- package/node_modules/@aspectcode/evaluator/dist/index.d.ts +8 -43
- package/node_modules/@aspectcode/evaluator/dist/index.d.ts.map +1 -1
- package/node_modules/@aspectcode/evaluator/dist/index.js +15 -61
- package/node_modules/@aspectcode/evaluator/dist/index.js.map +1 -1
- package/node_modules/@aspectcode/evaluator/dist/judge.d.ts +32 -0
- package/node_modules/@aspectcode/evaluator/dist/judge.d.ts.map +1 -0
- package/node_modules/@aspectcode/evaluator/dist/judge.js +165 -0
- package/node_modules/@aspectcode/evaluator/dist/judge.js.map +1 -0
- package/node_modules/@aspectcode/evaluator/dist/llmUtil.d.ts +15 -0
- package/node_modules/@aspectcode/evaluator/dist/llmUtil.d.ts.map +1 -0
- package/node_modules/@aspectcode/evaluator/dist/llmUtil.js +41 -0
- package/node_modules/@aspectcode/evaluator/dist/llmUtil.js.map +1 -0
- package/node_modules/@aspectcode/evaluator/dist/probes.d.ts +20 -47
- package/node_modules/@aspectcode/evaluator/dist/probes.d.ts.map +1 -1
- package/node_modules/@aspectcode/evaluator/dist/probes.js +188 -278
- package/node_modules/@aspectcode/evaluator/dist/probes.js.map +1 -1
- package/node_modules/@aspectcode/evaluator/dist/runner.d.ts +7 -32
- package/node_modules/@aspectcode/evaluator/dist/runner.d.ts.map +1 -1
- package/node_modules/@aspectcode/evaluator/dist/runner.js +21 -146
- package/node_modules/@aspectcode/evaluator/dist/runner.js.map +1 -1
- package/node_modules/@aspectcode/evaluator/dist/types.d.ts +141 -99
- package/node_modules/@aspectcode/evaluator/dist/types.d.ts.map +1 -1
- package/node_modules/@aspectcode/evaluator/dist/types.js +10 -2
- package/node_modules/@aspectcode/evaluator/dist/types.js.map +1 -1
- package/node_modules/@aspectcode/evaluator/package.json +4 -4
- package/node_modules/@aspectcode/optimizer/dist/index.d.ts +3 -10
- package/node_modules/@aspectcode/optimizer/dist/index.d.ts.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/index.js +1 -19
- package/node_modules/@aspectcode/optimizer/dist/index.js.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/providers/anthropic.d.ts.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/providers/anthropic.js +40 -0
- package/node_modules/@aspectcode/optimizer/dist/providers/anthropic.js.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/providers/aspectcode.d.ts +9 -0
- package/node_modules/@aspectcode/optimizer/dist/providers/aspectcode.d.ts.map +1 -0
- package/node_modules/@aspectcode/optimizer/dist/providers/aspectcode.js +83 -0
- package/node_modules/@aspectcode/optimizer/dist/providers/aspectcode.js.map +1 -0
- package/node_modules/@aspectcode/optimizer/dist/providers/index.d.ts +4 -3
- package/node_modules/@aspectcode/optimizer/dist/providers/index.d.ts.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/providers/index.js +24 -10
- package/node_modules/@aspectcode/optimizer/dist/providers/index.js.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/providers/openai.d.ts.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/providers/openai.js +22 -0
- package/node_modules/@aspectcode/optimizer/dist/providers/openai.js.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/providers/retry.d.ts +14 -0
- package/node_modules/@aspectcode/optimizer/dist/providers/retry.d.ts.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/providers/retry.js +1 -0
- package/node_modules/@aspectcode/optimizer/dist/providers/retry.js.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/types.d.ts +14 -0
- package/node_modules/@aspectcode/optimizer/dist/types.d.ts.map +1 -1
- package/node_modules/@aspectcode/optimizer/dist/types.js.map +1 -1
- package/node_modules/@aspectcode/optimizer/package.json +2 -2
- package/node_modules/web-tree-sitter/LICENSE +21 -0
- package/node_modules/web-tree-sitter/README.md +198 -0
- package/node_modules/web-tree-sitter/package.json +36 -0
- package/node_modules/web-tree-sitter/tree-sitter-web.d.ts +204 -0
- package/node_modules/web-tree-sitter/tree-sitter.js +1 -0
- package/node_modules/web-tree-sitter/tree-sitter.wasm +0 -0
- package/package.json +8 -8
- package/dist/complaintProcessor.d.ts +0 -16
- package/dist/complaintProcessor.d.ts.map +0 -1
- package/dist/complaintProcessor.js +0 -134
- package/dist/complaintProcessor.js.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/emitter.d.ts +0 -72
- package/node_modules/@aspectcode/emitters/dist/emitter.d.ts.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/emitter.js +0 -10
- package/node_modules/@aspectcode/emitters/dist/emitter.js.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/instructions/content.d.ts +0 -26
- package/node_modules/@aspectcode/emitters/dist/instructions/content.d.ts.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/instructions/content.js +0 -501
- package/node_modules/@aspectcode/emitters/dist/instructions/content.js.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/instructions/detection.d.ts +0 -13
- package/node_modules/@aspectcode/emitters/dist/instructions/detection.d.ts.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/instructions/detection.js +0 -55
- package/node_modules/@aspectcode/emitters/dist/instructions/detection.js.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/instructions/instructionsEmitter.d.ts +0 -9
- package/node_modules/@aspectcode/emitters/dist/instructions/instructionsEmitter.d.ts.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/instructions/instructionsEmitter.js +0 -30
- package/node_modules/@aspectcode/emitters/dist/instructions/instructionsEmitter.js.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/kb/kbEmitter.d.ts +0 -21
- package/node_modules/@aspectcode/emitters/dist/kb/kbEmitter.d.ts.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/kb/kbEmitter.js +0 -125
- package/node_modules/@aspectcode/emitters/dist/kb/kbEmitter.js.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/manifest.d.ts +0 -37
- package/node_modules/@aspectcode/emitters/dist/manifest.d.ts.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/manifest.js +0 -50
- package/node_modules/@aspectcode/emitters/dist/manifest.js.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/report.d.ts +0 -22
- package/node_modules/@aspectcode/emitters/dist/report.d.ts.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/report.js +0 -3
- package/node_modules/@aspectcode/emitters/dist/report.js.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/stableJson.d.ts +0 -14
- package/node_modules/@aspectcode/emitters/dist/stableJson.d.ts.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/stableJson.js +0 -40
- package/node_modules/@aspectcode/emitters/dist/stableJson.js.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/transaction.d.ts +0 -29
- package/node_modules/@aspectcode/emitters/dist/transaction.d.ts.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/transaction.js +0 -104
- package/node_modules/@aspectcode/emitters/dist/transaction.js.map +0 -1
package/dist/pipeline.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* aspectcode pipeline —
|
|
3
|
+
* aspectcode pipeline — analyze, generate, watch, evaluate.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 4. Build KB-custom content from analysis
|
|
9
|
-
* 5. If generate=true → LLM generates AGENTS.md (or static fallback)
|
|
10
|
-
* If generate=false → write KB-custom content only (skip LLM)
|
|
11
|
-
* 6. If --kb flag → also write kb.md
|
|
12
|
-
* 7. If not --once → watch for changes and repeat (always generate)
|
|
5
|
+
* v2: After generating AGENTS.md, watch mode evaluates individual file
|
|
6
|
+
* changes in real time. Full probe-and-refine runs on first startup
|
|
7
|
+
* or when the user presses 'r'.
|
|
13
8
|
*/
|
|
14
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
10
|
if (k2 === undefined) k2 = k;
|
|
@@ -45,7 +40,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
45
40
|
};
|
|
46
41
|
})();
|
|
47
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.isIgnoredPath = isIgnoredPath;
|
|
44
|
+
exports.isSupportedSourceFile = isSupportedSourceFile;
|
|
45
|
+
exports.createFileWatcher = createFileWatcher;
|
|
48
46
|
exports.resolveRunMode = resolveRunMode;
|
|
47
|
+
exports.resolvePlatforms = resolvePlatforms;
|
|
49
48
|
exports.runPipeline = runPipeline;
|
|
50
49
|
const fs = __importStar(require("fs"));
|
|
51
50
|
const path = __importStar(require("path"));
|
|
@@ -59,17 +58,31 @@ const kbBuilder_1 = require("./kbBuilder");
|
|
|
59
58
|
const toolIngestion_1 = require("./toolIngestion");
|
|
60
59
|
const writer_1 = require("./writer");
|
|
61
60
|
const optimize_1 = require("./optimize");
|
|
62
|
-
const complaintProcessor_1 = require("./complaintProcessor");
|
|
63
61
|
const prompts_1 = require("./ui/prompts");
|
|
64
62
|
const store_1 = require("./ui/store");
|
|
65
63
|
const summary_1 = require("./summary");
|
|
66
64
|
const diffSummary_1 = require("./diffSummary");
|
|
67
|
-
|
|
68
|
-
const
|
|
65
|
+
const runtimeState_1 = require("./runtimeState");
|
|
66
|
+
const preferences_1 = require("./preferences");
|
|
67
|
+
const changeEvaluator_1 = require("./changeEvaluator");
|
|
68
|
+
const dreamCycle_1 = require("./dreamCycle");
|
|
69
|
+
const scopedRules_1 = require("./scopedRules");
|
|
70
|
+
const autoResolve_1 = require("./autoResolve");
|
|
71
|
+
const agentsMdRenderer_1 = require("./agentsMdRenderer");
|
|
72
|
+
const usageTracker_1 = require("./usageTracker");
|
|
73
|
+
const auth_1 = require("./auth");
|
|
74
|
+
const optimizer_1 = require("@aspectcode/optimizer");
|
|
75
|
+
// ── Constants ────────────────────────────────────────────────
|
|
76
|
+
const EVAL_DEBOUNCE_MS = 500;
|
|
77
|
+
const CO_CHANGE_SETTLE_MS = 5000;
|
|
78
|
+
const AUTO_PROBE_IDLE_MS = 5 * 60 * 1000;
|
|
79
|
+
const AUTO_PROBE_MIN_CHANGES = 20;
|
|
80
|
+
const AUTO_PROBE_COOLDOWN_MS = 12 * 60 * 60 * 1000; // 12 hours
|
|
69
81
|
const IGNORED_SEGMENTS = [
|
|
70
82
|
'/node_modules/', '/.git/', '/dist/', '/build/', '/target/',
|
|
71
83
|
'/coverage/', '/.next/', '/__pycache__/', '/.venv/', '/venv/',
|
|
72
84
|
'/.pytest_cache/', '/.mypy_cache/', '/.tox/', '/htmlcov/',
|
|
85
|
+
'/.aspectcode/',
|
|
73
86
|
];
|
|
74
87
|
function isIgnoredPath(filePath) {
|
|
75
88
|
const normalized = filePath.replace(/\\/g, '/').toLowerCase();
|
|
@@ -79,15 +92,227 @@ function isSupportedSourceFile(filePath) {
|
|
|
79
92
|
const ext = path.extname(filePath).toLowerCase();
|
|
80
93
|
return core_1.SUPPORTED_EXTENSIONS.includes(ext);
|
|
81
94
|
}
|
|
82
|
-
// ──
|
|
83
|
-
|
|
95
|
+
// ── Managed files for memory map ─────────────────────────────
|
|
96
|
+
function fileMtime(filePath) {
|
|
97
|
+
try {
|
|
98
|
+
return fs.statSync(filePath).mtimeMs;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function buildManagedFiles(root, preferenceCount, platforms = []) {
|
|
105
|
+
const files = [];
|
|
106
|
+
// ── Workspace-scope: AGENTS.md ──────────────────────────────
|
|
107
|
+
const agentsAbs = path.join(root, 'AGENTS.md');
|
|
108
|
+
if (fs.existsSync(agentsAbs)) {
|
|
109
|
+
files.push({ path: 'AGENTS.md', annotation: '', updatedAt: fileMtime(agentsAbs), category: 'agents', scope: 'workspace', owner: 'aspectcode' });
|
|
110
|
+
}
|
|
111
|
+
// ── Workspace-scope: platform instruction files ─────────────
|
|
112
|
+
if (platforms.includes('claude')) {
|
|
113
|
+
const claudeMdAbs = path.join(root, 'CLAUDE.md');
|
|
114
|
+
if (fs.existsSync(claudeMdAbs)) {
|
|
115
|
+
files.push({ path: 'CLAUDE.md', annotation: '○ user', updatedAt: fileMtime(claudeMdAbs), category: 'workspace-config', scope: 'workspace', owner: 'user' });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (platforms.includes('cursor')) {
|
|
119
|
+
const cursorrules = path.join(root, '.cursorrules');
|
|
120
|
+
if (fs.existsSync(cursorrules)) {
|
|
121
|
+
files.push({ path: '.cursorrules', annotation: '○ user', updatedAt: fileMtime(cursorrules), category: 'workspace-config', scope: 'workspace', owner: 'user' });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ── Workspace-scope: scoped rules from manifest ─────────────
|
|
125
|
+
const manifestPath = path.join(root, '.aspectcode', 'scoped-rules.json');
|
|
126
|
+
const manifestRulePaths = new Set();
|
|
127
|
+
let manifestNeedsCleanup = false;
|
|
128
|
+
if (fs.existsSync(manifestPath)) {
|
|
129
|
+
try {
|
|
130
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
131
|
+
const validRules = [];
|
|
132
|
+
for (const entry of manifest.rules ?? []) {
|
|
133
|
+
if (manifestRulePaths.has(entry.path))
|
|
134
|
+
continue;
|
|
135
|
+
const absPath = path.join(root, entry.path);
|
|
136
|
+
// Skip files that no longer exist on disk
|
|
137
|
+
if (!fs.existsSync(absPath)) {
|
|
138
|
+
manifestNeedsCleanup = true;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
manifestRulePaths.add(entry.path);
|
|
142
|
+
validRules.push(entry);
|
|
143
|
+
const cat = entry.path.startsWith('.claude/') ? 'claude-rule'
|
|
144
|
+
: entry.path.startsWith('.cursor/') ? 'cursor-rule'
|
|
145
|
+
: 'agents';
|
|
146
|
+
const ts = entry.updatedAt ? new Date(entry.updatedAt).getTime() : fileMtime(absPath);
|
|
147
|
+
files.push({ path: entry.path, annotation: '● active', updatedAt: ts, category: cat, scope: 'workspace', owner: 'aspectcode' });
|
|
148
|
+
}
|
|
149
|
+
// Clean stale entries from manifest so they don't persist across restarts
|
|
150
|
+
if (manifestNeedsCleanup) {
|
|
151
|
+
try {
|
|
152
|
+
fs.writeFileSync(manifestPath, JSON.stringify({ version: 1, rules: validRules }, null, 2) + '\n');
|
|
153
|
+
}
|
|
154
|
+
catch { /* ignore write errors */ }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch { /* malformed manifest */ }
|
|
158
|
+
}
|
|
159
|
+
// ── Workspace-scope: user-created scoped rules ──────────────
|
|
160
|
+
const platformRuleDirs = [];
|
|
161
|
+
if (platforms.includes('claude'))
|
|
162
|
+
platformRuleDirs.push({ dir: '.claude/rules', ext: '.md' });
|
|
163
|
+
if (platforms.includes('cursor'))
|
|
164
|
+
platformRuleDirs.push({ dir: '.cursor/rules', ext: '.mdc' });
|
|
165
|
+
for (const { dir: rulesDir, ext } of platformRuleDirs) {
|
|
166
|
+
const absDir = path.join(root, rulesDir);
|
|
167
|
+
try {
|
|
168
|
+
if (fs.existsSync(absDir)) {
|
|
169
|
+
for (const entry of fs.readdirSync(absDir)) {
|
|
170
|
+
if (!entry.endsWith(ext))
|
|
171
|
+
continue;
|
|
172
|
+
if (entry.startsWith('ac-'))
|
|
173
|
+
continue; // aspectcode-managed
|
|
174
|
+
const relPath = `${rulesDir}/${entry}`;
|
|
175
|
+
if (manifestRulePaths.has(relPath))
|
|
176
|
+
continue; // already tracked
|
|
177
|
+
files.push({ path: relPath, annotation: '○ user', updatedAt: fileMtime(path.join(absDir, entry)), category: 'user-rule', scope: 'workspace', owner: 'user' });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch { /* unreadable dir */ }
|
|
182
|
+
}
|
|
183
|
+
// ── Workspace-scope: settings ───────────────────────────────
|
|
184
|
+
if (platforms.includes('claude')) {
|
|
185
|
+
const settingsLocal = path.join(root, '.claude', 'settings.local.json');
|
|
186
|
+
if (fs.existsSync(settingsLocal)) {
|
|
187
|
+
files.push({ path: '.claude/settings.local.json', annotation: '○ user', updatedAt: fileMtime(settingsLocal), category: 'workspace-config', scope: 'workspace', owner: 'user' });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// ── Workspace-scope: .aspectcode files ──────────────────────
|
|
191
|
+
if (preferenceCount > 0) {
|
|
192
|
+
files.push({ path: '☁ preferences', annotation: `${preferenceCount} learned`, updatedAt: 0, category: 'cloud', scope: 'workspace', owner: 'aspectcode' });
|
|
193
|
+
}
|
|
194
|
+
const dreamStatePath = path.join(root, '.aspectcode', 'dream-state.json');
|
|
195
|
+
if (fs.existsSync(dreamStatePath)) {
|
|
196
|
+
try {
|
|
197
|
+
const ds = JSON.parse(fs.readFileSync(dreamStatePath, 'utf-8'));
|
|
198
|
+
const lastDream = ds.lastDreamAt ? new Date(ds.lastDreamAt).getTime() : 0;
|
|
199
|
+
files.push({ path: '.aspectcode/dream-state.json', annotation: '', updatedAt: lastDream, category: 'aspectcode', scope: 'workspace', owner: 'aspectcode' });
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
files.push({ path: '.aspectcode/dream-state.json', annotation: '', updatedAt: 0, category: 'aspectcode', scope: 'workspace', owner: 'aspectcode' });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// ── Device-scope: ~/.claude/ memory (Claude Code only) ──────
|
|
206
|
+
if (platforms.includes('claude')) {
|
|
207
|
+
const home = require('os').homedir();
|
|
208
|
+
// Device-root CLAUDE.md
|
|
209
|
+
const deviceClaudeMd = path.join(home, '.claude', 'CLAUDE.md');
|
|
210
|
+
if (fs.existsSync(deviceClaudeMd)) {
|
|
211
|
+
files.push({ path: '~/.claude/CLAUDE.md', annotation: '○ device', updatedAt: fileMtime(deviceClaudeMd), category: 'device', scope: 'device', owner: 'device' });
|
|
212
|
+
}
|
|
213
|
+
// Project-level CLAUDE.md (~/.claude/projects/<hash>/CLAUDE.md)
|
|
214
|
+
const projectClaudeMd = findDeviceProjectClaudeMd(home, root);
|
|
215
|
+
if (projectClaudeMd) {
|
|
216
|
+
files.push({ path: '~/.claude/projects/.../CLAUDE.md', annotation: '○ device', updatedAt: fileMtime(projectClaudeMd), category: 'device', scope: 'device', owner: 'device' });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return files;
|
|
220
|
+
}
|
|
221
|
+
/** Find the project-level CLAUDE.md in ~/.claude/projects/<hash>/ for the given workspace. */
|
|
222
|
+
function findDeviceProjectClaudeMd(home, root) {
|
|
223
|
+
const projectsBase = path.join(home, '.claude', 'projects');
|
|
224
|
+
if (!fs.existsSync(projectsBase))
|
|
225
|
+
return undefined;
|
|
226
|
+
const normalised = root.replace(/\\/g, '/');
|
|
227
|
+
const crypto = require('crypto');
|
|
228
|
+
// Try common hash strategies
|
|
229
|
+
const candidates = [
|
|
230
|
+
crypto.createHash('md5').update(normalised).digest('hex'),
|
|
231
|
+
crypto.createHash('sha256').update(normalised).digest('hex').slice(0, 32),
|
|
232
|
+
Buffer.from(normalised).toString('base64url'),
|
|
233
|
+
normalised.replace(/\//g, '-').replace(/^-/, ''),
|
|
234
|
+
];
|
|
235
|
+
for (const candidate of candidates) {
|
|
236
|
+
const claudeMd = path.join(projectsBase, candidate, 'CLAUDE.md');
|
|
237
|
+
if (fs.existsSync(claudeMd))
|
|
238
|
+
return claudeMd;
|
|
239
|
+
}
|
|
240
|
+
// Fallback: scan project directories for CLAUDE.md
|
|
241
|
+
try {
|
|
242
|
+
for (const dir of fs.readdirSync(projectsBase)) {
|
|
243
|
+
const claudeMd = path.join(projectsBase, dir, 'CLAUDE.md');
|
|
244
|
+
if (fs.existsSync(claudeMd)) {
|
|
245
|
+
// Verify this directory is for our workspace by checking a jsonl file
|
|
246
|
+
const fullDir = path.join(projectsBase, dir);
|
|
247
|
+
const jsonls = fs.readdirSync(fullDir).filter((f) => f.endsWith('.jsonl'));
|
|
248
|
+
if (jsonls.length > 0) {
|
|
249
|
+
try {
|
|
250
|
+
const fd = fs.openSync(path.join(fullDir, jsonls[0]), 'r');
|
|
251
|
+
const buf = Buffer.alloc(2048);
|
|
252
|
+
const bytesRead = fs.readSync(fd, buf, 0, 2048, 0);
|
|
253
|
+
fs.closeSync(fd);
|
|
254
|
+
const sample = buf.slice(0, bytesRead).toString('utf-8');
|
|
255
|
+
if (sample.includes(normalised) || sample.includes(root.replace(/\\/g, '\\\\'))) {
|
|
256
|
+
return claudeMd;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch { /* skip */ }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch { /* scan failed */ }
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
// ── File watcher ─────────────────────────────────────────────
|
|
268
|
+
function createFileWatcher(root, onEvent) {
|
|
269
|
+
return fs.watch(root, { recursive: true }, (event, filename) => {
|
|
270
|
+
if (!filename)
|
|
271
|
+
return;
|
|
272
|
+
const posixPath = filename.replace(/\\/g, '/');
|
|
273
|
+
const abs = path.resolve(root, filename);
|
|
274
|
+
if (!isSupportedSourceFile(abs) || isIgnoredPath(abs))
|
|
275
|
+
return;
|
|
276
|
+
const type = event === 'rename'
|
|
277
|
+
? (fs.existsSync(abs) ? 'add' : 'unlink')
|
|
278
|
+
: 'change';
|
|
279
|
+
onEvent(type, posixPath);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* @param probeAndRefine Run probe-based evaluation after LLM generation.
|
|
284
|
+
* Only on first run or when user presses 'r'.
|
|
285
|
+
*/
|
|
286
|
+
async function runOnce(ctx, ownership, probeAndRefine = false, preferences, activePlatforms = ['claude'], userSettings) {
|
|
84
287
|
const { root, flags, log } = ctx;
|
|
85
288
|
const config = (0, config_1.loadConfig)(root);
|
|
86
289
|
const startMs = Date.now();
|
|
87
290
|
store_1.store.resetRun();
|
|
88
291
|
store_1.store.setRunStartMs(startMs);
|
|
89
|
-
|
|
90
|
-
|
|
292
|
+
if (config)
|
|
293
|
+
store_1.store.addSetupNote('using config file');
|
|
294
|
+
// Clean stale manifest entries (files deleted by user)
|
|
295
|
+
const deletedSlugs = new Set();
|
|
296
|
+
const manifestPath = path.join(root, '.aspectcode', 'scoped-rules.json');
|
|
297
|
+
if (fs.existsSync(manifestPath)) {
|
|
298
|
+
try {
|
|
299
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
300
|
+
const validRules = [];
|
|
301
|
+
for (const entry of manifest.rules ?? []) {
|
|
302
|
+
if (fs.existsSync(path.join(root, entry.path))) {
|
|
303
|
+
validRules.push(entry);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
deletedSlugs.add(entry.slug);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (deletedSlugs.size > 0) {
|
|
310
|
+
fs.writeFileSync(manifestPath, JSON.stringify({ version: 1, rules: validRules }, null, 2) + '\n');
|
|
311
|
+
log.debug(`Cleaned ${deletedSlugs.size} stale manifest entries`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch { /* ignore */ }
|
|
315
|
+
}
|
|
91
316
|
const agentsPath = path.join(root, 'AGENTS.md');
|
|
92
317
|
if (!fs.existsSync(agentsPath)) {
|
|
93
318
|
store_1.store.setFirstRun(true);
|
|
@@ -96,7 +321,7 @@ async function runOnce(ctx, ownership) {
|
|
|
96
321
|
store_1.store.setPhase('discovering');
|
|
97
322
|
const workspace = await (0, workspace_1.loadWorkspaceFiles)(root, config, log, { quiet: flags.quiet, spin: ctx.spin });
|
|
98
323
|
if (workspace.discoveredPaths.length === 0) {
|
|
99
|
-
log.warn('No source files found.
|
|
324
|
+
log.warn('No source files found.');
|
|
100
325
|
return { code: cli_1.ExitCode.ERROR, kbContent: '' };
|
|
101
326
|
}
|
|
102
327
|
// ── 2. Analyze ────────────────────────────────────────────
|
|
@@ -104,255 +329,963 @@ async function runOnce(ctx, ownership) {
|
|
|
104
329
|
const model = await (0, core_1.analyzeRepoWithDependencies)(root, workspace.relativeFiles, workspace.absoluteFiles, workspace.host);
|
|
105
330
|
spinAnalyze.stop(`Analyzed ${model.files.length} files, ${model.graph.edges.length} edges`);
|
|
106
331
|
store_1.store.setStats(model.files.length, model.graph.edges.length);
|
|
107
|
-
// ── 3. Build KB
|
|
332
|
+
// ── 3. Build KB ───────────────────────────────────────────
|
|
108
333
|
const spinKb = ctx.spin('Building knowledge base…', 'building-kb');
|
|
109
334
|
const kbContent = (0, kbBuilder_1.buildKbContent)(model, root, workspace.relativeFiles);
|
|
110
335
|
spinKb.stop('Knowledge base built');
|
|
111
|
-
// ── 4. Read
|
|
336
|
+
// ── 4. Read tool instruction files ────────────────────────
|
|
112
337
|
const host = (0, emitters_1.createNodeEmitterHost)();
|
|
113
338
|
const toolInstructions = await (0, toolIngestion_1.readToolInstructions)(host, root);
|
|
114
339
|
if (toolInstructions.size > 0) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// ──
|
|
120
|
-
|
|
121
|
-
? (0, emitters_1.generateKbCustomContent)(kbContent, 'safe')
|
|
122
|
-
: (0, emitters_1.generateCanonicalContentForMode)('safe', false);
|
|
123
|
-
// ── 6. Generate or skip ────────────────────────────────────
|
|
124
|
-
// When ctx.generate is true: write base template immediately for
|
|
125
|
-
// early feedback, then run LLM (or static fallback), then overwrite.
|
|
126
|
-
// When ctx.generate is false: write KB-custom content only.
|
|
340
|
+
store_1.store.addSetupNote(`context: ${[...toolInstructions.keys()].join(', ')}`);
|
|
341
|
+
}
|
|
342
|
+
// ── 5. Build base content (directly from model, no KB extraction) ──
|
|
343
|
+
const baseContent = (0, agentsMdRenderer_1.renderAgentsMd)(model, path.basename(root));
|
|
344
|
+
// ── 6. Generate or skip ───────────────────────────────────
|
|
345
|
+
let finalContent = baseContent;
|
|
127
346
|
if (ctx.generate) {
|
|
128
|
-
// Write base immediately so user sees output before LLM finishes
|
|
129
347
|
if (!flags.dryRun) {
|
|
130
348
|
await (0, writer_1.writeAgentsMd)(host, root, baseContent, ownership);
|
|
131
349
|
store_1.store.addOutput('AGENTS.md written (base)');
|
|
132
|
-
log.debug('Base AGENTS.md written from static analysis');
|
|
133
350
|
}
|
|
134
351
|
store_1.store.setPhase('optimizing');
|
|
135
|
-
|
|
136
|
-
|
|
352
|
+
// Skip probe-and-refine if tier is already exhausted
|
|
353
|
+
const skipProbe = probeAndRefine && store_1.store.state.tierExhausted;
|
|
354
|
+
let optimizeResult;
|
|
355
|
+
try {
|
|
356
|
+
optimizeResult = await (0, optimize_1.tryOptimize)(ctx, kbContent, toolInstructions, config, baseContent, skipProbe ? false : probeAndRefine, preferences, userSettings, []);
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
if (err?.tierExhausted) {
|
|
360
|
+
store_1.store.setTierExhausted();
|
|
361
|
+
optimizeResult = { content: baseContent, reasoning: [], scopedRules: [], deleteSlugs: [] };
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
throw err;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
finalContent = optimizeResult.content;
|
|
137
368
|
store_1.store.setPhase('writing');
|
|
138
369
|
if (flags.dryRun) {
|
|
139
370
|
log.info(logger_1.fmt.bold('Dry run — proposed AGENTS.md:'));
|
|
140
371
|
log.blank();
|
|
141
|
-
log.info(
|
|
142
|
-
log.blank();
|
|
372
|
+
log.info(finalContent);
|
|
143
373
|
}
|
|
144
374
|
else {
|
|
145
|
-
// Compute diff before overwriting (for watch-mode change summary)
|
|
146
375
|
let previousContent;
|
|
147
376
|
try {
|
|
148
377
|
if (fs.existsSync(agentsPath)) {
|
|
149
378
|
previousContent = fs.readFileSync(agentsPath, 'utf-8');
|
|
150
379
|
}
|
|
151
380
|
}
|
|
152
|
-
catch { /* ignore
|
|
153
|
-
await (0, writer_1.writeAgentsMd)(host, root,
|
|
381
|
+
catch { /* ignore */ }
|
|
382
|
+
await (0, writer_1.writeAgentsMd)(host, root, finalContent, ownership);
|
|
154
383
|
const modeLabel = ownership === 'section' ? ' (section)' : '';
|
|
155
384
|
const verb = optimizeResult.reasoning.length > 0 ? 'generated' : 'written';
|
|
156
385
|
store_1.store.addOutput(`AGENTS.md ${verb}${modeLabel}`);
|
|
157
|
-
log.success(`AGENTS.md ${verb}${modeLabel}`);
|
|
158
|
-
// Diff summary (skip on first write when previous was just the base template)
|
|
159
386
|
if (previousContent !== undefined && previousContent !== baseContent) {
|
|
160
|
-
|
|
161
|
-
store_1.store.setDiffSummary(diff);
|
|
387
|
+
store_1.store.setDiffSummary((0, diffSummary_1.diffSummary)(previousContent, finalContent));
|
|
162
388
|
}
|
|
163
|
-
|
|
164
|
-
const summary = (0, summary_1.summarizeContent)(optimizeResult.content);
|
|
165
|
-
store_1.store.setSummary(summary);
|
|
389
|
+
store_1.store.setSummary((0, summary_1.summarizeContent)(finalContent));
|
|
166
390
|
}
|
|
167
391
|
}
|
|
168
392
|
else {
|
|
169
|
-
// Skip LLM — write KB-custom content only
|
|
170
393
|
store_1.store.setPhase('writing');
|
|
171
394
|
if (flags.dryRun) {
|
|
172
395
|
log.info(logger_1.fmt.bold('Dry run — proposed AGENTS.md (KB-custom):'));
|
|
173
396
|
log.blank();
|
|
174
397
|
log.info(baseContent);
|
|
175
|
-
log.blank();
|
|
176
398
|
}
|
|
177
399
|
else {
|
|
178
400
|
await (0, writer_1.writeAgentsMd)(host, root, baseContent, ownership);
|
|
179
401
|
store_1.store.addOutput('AGENTS.md written (KB-custom)');
|
|
180
|
-
|
|
181
|
-
const summary = (0, summary_1.summarizeContent)(baseContent);
|
|
182
|
-
store_1.store.setSummary(summary);
|
|
402
|
+
store_1.store.setSummary((0, summary_1.summarizeContent)(baseContent));
|
|
183
403
|
}
|
|
184
|
-
|
|
404
|
+
// No LLM available — use static extraction for scoped rules
|
|
405
|
+
// No static rules in non-LLM path — dream cycle handles rules
|
|
185
406
|
}
|
|
186
|
-
// ──
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
407
|
+
// ── 7. Persist runtime state ───────────────────────────────
|
|
408
|
+
// Snapshot hub counts before updating state (for new-hub detection)
|
|
409
|
+
const hubCounts = new Map();
|
|
410
|
+
for (const hub of model.metrics.hubs) {
|
|
411
|
+
hubCounts.set(hub.file, hub.inDegree);
|
|
191
412
|
}
|
|
413
|
+
(0, runtimeState_1.updateRuntimeState)({
|
|
414
|
+
model,
|
|
415
|
+
kbContent,
|
|
416
|
+
agentsContent: finalContent,
|
|
417
|
+
fileContents: workspace.relativeFiles,
|
|
418
|
+
previousHubCounts: hubCounts,
|
|
419
|
+
});
|
|
420
|
+
// ── 8. Scoped rules are managed exclusively by the dream cycle.
|
|
421
|
+
// No static rules written during runOnce.
|
|
422
|
+
// ── 9. Populate memory map ─────────────────────────────────
|
|
423
|
+
const prefs = await (0, preferences_1.loadPreferences)(root);
|
|
424
|
+
store_1.store.setManagedFiles(buildManagedFiles(root, prefs.preferences.length, activePlatforms));
|
|
192
425
|
const elapsedMs = Date.now() - startMs;
|
|
193
426
|
store_1.store.setElapsed(`${(elapsedMs / 1000).toFixed(1)}s`);
|
|
194
427
|
store_1.store.setPhase('done');
|
|
195
|
-
log.info(logger_1.fmt.dim(`Done in ${(elapsedMs / 1000).toFixed(1)}s`));
|
|
196
428
|
return { code: cli_1.ExitCode.OK, kbContent };
|
|
197
429
|
}
|
|
198
|
-
// ──
|
|
199
|
-
/**
|
|
200
|
-
* Resolve AGENTS.md ownership mode and whether to generate on this run.
|
|
201
|
-
*
|
|
202
|
-
* Called from main() BEFORE the ink dashboard is mounted, because
|
|
203
|
-
* the interactive prompt uses raw stdin which conflicts with ink's useInput.
|
|
204
|
-
*
|
|
205
|
-
* When AGENTS.md already has section markers → section + generate (auto).
|
|
206
|
-
* Otherwise (no file, or file without markers) → 3-option prompt:
|
|
207
|
-
* 1. Full control — generate now
|
|
208
|
-
* 2. Full control — skip generation (use KB-custom only)
|
|
209
|
-
* 3. Section control (preserve your content)
|
|
210
|
-
*/
|
|
430
|
+
// ── Resolve ownership mode ───────────────────────────────────
|
|
211
431
|
async function resolveRunMode(root) {
|
|
212
432
|
const config = (0, config_1.loadConfig)(root);
|
|
213
|
-
// Config-driven ownership skips prompt but still generates
|
|
214
433
|
if (config?.ownership) {
|
|
215
434
|
return { ownership: config.ownership, generate: true };
|
|
216
435
|
}
|
|
436
|
+
const agentsPath = path.join(root, 'AGENTS.md');
|
|
437
|
+
let existingContent = null;
|
|
217
438
|
try {
|
|
218
|
-
const agentsPath = path.join(root, 'AGENTS.md');
|
|
219
439
|
if (fs.existsSync(agentsPath)) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if ((0, writer_1.hasMarkers)(existing)) {
|
|
440
|
+
existingContent = fs.readFileSync(agentsPath, 'utf-8');
|
|
441
|
+
if ((0, writer_1.hasMarkers)(existingContent))
|
|
223
442
|
return { ownership: 'section', generate: true };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch { /* fall through */ }
|
|
446
|
+
try {
|
|
447
|
+
const hasExisting = existingContent !== null;
|
|
448
|
+
const options = hasExisting
|
|
449
|
+
? ['Full control (replace entire file)', 'Section control (preserve your content)', 'Preview current AGENTS.md']
|
|
450
|
+
: ['Full control (replace entire file)', 'Section control (preserve your content)'];
|
|
451
|
+
// eslint-disable-next-line no-constant-condition
|
|
452
|
+
while (true) {
|
|
453
|
+
const idx = await (0, prompts_1.selectPrompt)('How should AspectCode manage AGENTS.md?', options, 0);
|
|
454
|
+
// Preview option — show content, then loop back to prompt
|
|
455
|
+
if (hasExisting && idx === 2) {
|
|
456
|
+
showFilePreview(existingContent);
|
|
457
|
+
continue;
|
|
224
458
|
}
|
|
225
|
-
|
|
226
|
-
|
|
459
|
+
const ownership = idx === 1 ? 'section' : 'full';
|
|
460
|
+
// Save choice so we don't ask again
|
|
461
|
+
(0, config_1.saveConfig)(root, { ownership });
|
|
462
|
+
return { ownership, generate: true };
|
|
227
463
|
}
|
|
228
464
|
}
|
|
229
465
|
catch {
|
|
230
|
-
|
|
466
|
+
return { ownership: 'full', generate: true };
|
|
231
467
|
}
|
|
232
|
-
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Display file content in a scrollable pager-like view.
|
|
471
|
+
* Press any key to return.
|
|
472
|
+
*/
|
|
473
|
+
function showFilePreview(content) {
|
|
474
|
+
return new Promise((resolve) => {
|
|
475
|
+
const lines = content.split('\n');
|
|
476
|
+
const termHeight = process.stdout.rows || 24;
|
|
477
|
+
const visibleLines = termHeight - 4; // leave room for header/footer
|
|
478
|
+
let scrollOffset = 0;
|
|
479
|
+
const render = () => {
|
|
480
|
+
process.stdout.write('\x1b[2J\x1b[H'); // clear screen
|
|
481
|
+
process.stdout.write('\x1b[35m── AGENTS.md preview ──\x1b[0m\n\n');
|
|
482
|
+
const slice = lines.slice(scrollOffset, scrollOffset + visibleLines);
|
|
483
|
+
for (const line of slice) {
|
|
484
|
+
process.stdout.write(` ${line}\n`);
|
|
485
|
+
}
|
|
486
|
+
const pct = lines.length <= visibleLines
|
|
487
|
+
? 100
|
|
488
|
+
: Math.round(((scrollOffset + visibleLines) / lines.length) * 100);
|
|
489
|
+
process.stdout.write(`\n\x1b[90m ↑/↓ scroll · q/esc to go back · ${pct}%\x1b[0m`);
|
|
490
|
+
};
|
|
491
|
+
render();
|
|
492
|
+
process.stdin.setRawMode(true);
|
|
493
|
+
process.stdin.resume();
|
|
494
|
+
process.stdin.setEncoding('utf-8');
|
|
495
|
+
const onData = (key) => {
|
|
496
|
+
if (key === '\x1b[A' || key === 'k') {
|
|
497
|
+
scrollOffset = Math.max(0, scrollOffset - 1);
|
|
498
|
+
render();
|
|
499
|
+
}
|
|
500
|
+
else if (key === '\x1b[B' || key === 'j') {
|
|
501
|
+
scrollOffset = Math.min(Math.max(0, lines.length - visibleLines), scrollOffset + 1);
|
|
502
|
+
render();
|
|
503
|
+
}
|
|
504
|
+
else if (key === '\x1b[5~') {
|
|
505
|
+
// Page Up
|
|
506
|
+
scrollOffset = Math.max(0, scrollOffset - visibleLines);
|
|
507
|
+
render();
|
|
508
|
+
}
|
|
509
|
+
else if (key === '\x1b[6~') {
|
|
510
|
+
// Page Down
|
|
511
|
+
scrollOffset = Math.min(Math.max(0, lines.length - visibleLines), scrollOffset + visibleLines);
|
|
512
|
+
render();
|
|
513
|
+
}
|
|
514
|
+
else if (key === 'q' || key === '\x1b' || key === '\r' || key === '\n') {
|
|
515
|
+
process.stdin.setRawMode(false);
|
|
516
|
+
process.stdin.pause();
|
|
517
|
+
process.stdin.removeListener('data', onData);
|
|
518
|
+
process.stdout.write('\x1b[2J\x1b[H'); // clear screen
|
|
519
|
+
resolve();
|
|
520
|
+
}
|
|
521
|
+
else if (key === '\x03') {
|
|
522
|
+
process.stdin.setRawMode(false);
|
|
523
|
+
process.stdin.pause();
|
|
524
|
+
process.stdin.removeListener('data', onData);
|
|
525
|
+
process.exit(130);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
process.stdin.on('data', onData);
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
// ── Platform resolution ─────────────────────────────────────
|
|
532
|
+
const ALL_PLATFORMS = [
|
|
533
|
+
{ id: 'claude', label: 'Claude Code', detect: ['.claude'] },
|
|
534
|
+
{ id: 'cursor', label: 'Cursor', detect: ['.cursor', '.cursorrules'] },
|
|
535
|
+
{ id: 'copilot', label: 'GitHub Copilot', detect: ['.github'] },
|
|
536
|
+
{ id: 'windsurf', label: 'Windsurf', detect: ['.windsurfrules'] },
|
|
537
|
+
{ id: 'codex', label: 'Codex', detect: [] },
|
|
538
|
+
{ id: 'cline', label: 'Cline', detect: ['.clinerules'] },
|
|
539
|
+
{ id: 'gemini', label: 'Gemini', detect: ['GEMINI.md'] },
|
|
540
|
+
{ id: 'aider', label: 'Aider', detect: ['CONVENTIONS.md'] },
|
|
541
|
+
];
|
|
542
|
+
async function resolvePlatforms(root) {
|
|
543
|
+
const config = (0, config_1.loadConfig)(root);
|
|
544
|
+
const configured = (0, config_1.getConfigPlatforms)(config);
|
|
545
|
+
if (configured?.length) {
|
|
546
|
+
// Check if collaborator has a platform not in config
|
|
547
|
+
const declined = config?.declinedPlatforms ?? [];
|
|
548
|
+
const detected = ALL_PLATFORMS
|
|
549
|
+
.filter((p) => p.detect.some((d) => fs.existsSync(path.join(root, d))))
|
|
550
|
+
.map((p) => p.id);
|
|
551
|
+
const missing = detected.filter((d) => !configured.includes(d) && !declined.includes(d));
|
|
552
|
+
if (missing.length > 0) {
|
|
553
|
+
const names = missing.map((id) => ALL_PLATFORMS.find((p) => p.id === id)?.label ?? id).join(', ');
|
|
554
|
+
const { confirmPrompt } = await Promise.resolve().then(() => __importStar(require('./ui/prompts')));
|
|
555
|
+
const add = await confirmPrompt(`${names} detected but not configured. Add?`);
|
|
556
|
+
if (add) {
|
|
557
|
+
const updated = [...configured, ...missing];
|
|
558
|
+
(0, config_1.saveConfig)(root, { platforms: updated });
|
|
559
|
+
return updated;
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
// Remember declined so we don't ask again
|
|
563
|
+
(0, config_1.saveConfig)(root, { declinedPlatforms: [...declined, ...missing] });
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return configured;
|
|
567
|
+
}
|
|
568
|
+
// First run: auto-detect + prompt
|
|
569
|
+
const detected = ALL_PLATFORMS
|
|
570
|
+
.filter((p) => p.detect.some((d) => fs.existsSync(path.join(root, d))))
|
|
571
|
+
.map((p) => p.id);
|
|
572
|
+
const preselected = detected.map((id) => ALL_PLATFORMS.findIndex((p) => p.id === id)).filter((i) => i >= 0);
|
|
233
573
|
try {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
574
|
+
const { multiSelectPrompt } = await Promise.resolve().then(() => __importStar(require('./ui/prompts')));
|
|
575
|
+
const labels = ALL_PLATFORMS.map((p) => {
|
|
576
|
+
const det = detected.includes(p.id) ? ' (detected)' : '';
|
|
577
|
+
return `${p.label}${det}`;
|
|
578
|
+
});
|
|
579
|
+
const indices = await multiSelectPrompt('Which editors do you use?', labels, preselected);
|
|
580
|
+
const selected = indices.map((i) => ALL_PLATFORMS[i].id);
|
|
581
|
+
const platforms = selected.length > 0 ? selected : ['claude']; // default to Claude Code
|
|
582
|
+
(0, config_1.saveConfig)(root, { platforms });
|
|
583
|
+
return platforms;
|
|
240
584
|
}
|
|
241
585
|
catch {
|
|
242
|
-
|
|
243
|
-
|
|
586
|
+
const platforms = detected.length > 0 ? detected : ['claude'];
|
|
587
|
+
(0, config_1.saveConfig)(root, { platforms });
|
|
588
|
+
return platforms;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
async function handleAssessmentAction(action, prefs, root, _ownership) {
|
|
592
|
+
if (!action.assessment)
|
|
593
|
+
return prefs;
|
|
594
|
+
const a = action.assessment;
|
|
595
|
+
const dir = path.dirname(a.file) + '/';
|
|
596
|
+
if (action.type === 'dismiss') {
|
|
597
|
+
// Dismiss → directory-scoped allow (broad: "this is fine here")
|
|
598
|
+
prefs = (0, preferences_1.addPreference)(prefs, {
|
|
599
|
+
rule: a.rule,
|
|
600
|
+
pattern: a.message,
|
|
601
|
+
disposition: 'allow',
|
|
602
|
+
directory: dir,
|
|
603
|
+
details: a.details,
|
|
604
|
+
dependencyContext: a.dependencyContext,
|
|
605
|
+
});
|
|
606
|
+
(0, preferences_1.savePreferences)(root, prefs);
|
|
607
|
+
store_1.store.setPreferenceCount(prefs.preferences.length);
|
|
608
|
+
store_1.store.setLearnedMessage(`Suppressed: ${a.rule} in ${dir}`);
|
|
609
|
+
store_1.store.resolveAssessment('dismiss');
|
|
610
|
+
(0, dreamCycle_1.addCorrection)('dismiss', a);
|
|
611
|
+
}
|
|
612
|
+
else if (action.type === 'confirm') {
|
|
613
|
+
// Confirm → file-scoped deny (specific: "this matters for this file")
|
|
614
|
+
prefs = (0, preferences_1.addPreference)(prefs, {
|
|
615
|
+
rule: a.rule,
|
|
616
|
+
pattern: a.message,
|
|
617
|
+
disposition: 'deny',
|
|
618
|
+
file: a.file,
|
|
619
|
+
directory: dir,
|
|
620
|
+
details: a.details,
|
|
621
|
+
suggestion: a.suggestion,
|
|
622
|
+
dependencyContext: a.dependencyContext,
|
|
623
|
+
});
|
|
624
|
+
(0, preferences_1.savePreferences)(root, prefs);
|
|
625
|
+
store_1.store.setPreferenceCount(prefs.preferences.length);
|
|
626
|
+
store_1.store.setLearnedMessage(`Enforced: ${a.rule} for ${a.file}`);
|
|
627
|
+
store_1.store.setRecommendProbe(true);
|
|
628
|
+
store_1.store.resolveAssessment('confirm');
|
|
629
|
+
(0, dreamCycle_1.addCorrection)('confirm', a);
|
|
630
|
+
// Don't directly modify AGENTS.md — the dream cycle handles integration cleanly
|
|
631
|
+
}
|
|
632
|
+
else if (action.type === 'skip') {
|
|
633
|
+
store_1.store.advanceAssessment();
|
|
244
634
|
}
|
|
635
|
+
store_1.store.setCorrectionCount((0, dreamCycle_1.getUnprocessedCount)());
|
|
636
|
+
return prefs;
|
|
245
637
|
}
|
|
638
|
+
// ── Main pipeline ────────────────────────────────────────────
|
|
246
639
|
async function runPipeline(ctx) {
|
|
247
640
|
const { root, flags, log, ownership } = ctx;
|
|
641
|
+
// Set terminal title
|
|
642
|
+
const projectName = path.basename(root);
|
|
643
|
+
process.stdout.write(`\x1b]0;aspectcode — ${projectName}\x07`);
|
|
248
644
|
log.info(`${logger_1.fmt.bold('aspectcode')} — ${logger_1.fmt.cyan(root)}`);
|
|
249
645
|
log.blank();
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
646
|
+
store_1.store.setRootPath(root);
|
|
647
|
+
// ── Show update message if available ──────────────────────
|
|
648
|
+
const updateMsg = globalThis.__updateMessage;
|
|
649
|
+
if (updateMsg)
|
|
650
|
+
store_1.store.setUpdateMessage(updateMsg);
|
|
651
|
+
// ── Set platforms from pre-resolved context ────────────────
|
|
652
|
+
const activePlatforms = ctx.platforms;
|
|
653
|
+
store_1.store.setPlatform(activePlatforms.join(', '));
|
|
654
|
+
// ── Check login status ────────────────────────────────────
|
|
655
|
+
const creds = (0, auth_1.loadCredentials)();
|
|
656
|
+
store_1.store.setUserEmail(creds?.email ?? '');
|
|
657
|
+
// ── Detect tier ──────────────────────────────────────────
|
|
658
|
+
const projectConfig = (0, config_1.loadConfig)(root);
|
|
659
|
+
const hasByokKey = !!(projectConfig?.apiKey || process.env.ASPECTCODE_LLM_KEY);
|
|
660
|
+
if (hasByokKey) {
|
|
661
|
+
store_1.store.setTierInfo('byok', 0, 0);
|
|
662
|
+
}
|
|
663
|
+
else if (creds) {
|
|
664
|
+
// Fetch tier + usage from verify endpoint
|
|
665
|
+
try {
|
|
666
|
+
const res = await fetch(`${auth_1.WEB_APP_URL}/api/cli/verify`, {
|
|
667
|
+
method: 'POST',
|
|
668
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
669
|
+
});
|
|
670
|
+
if (res.ok) {
|
|
671
|
+
const data = (await res.json());
|
|
672
|
+
const tier = (data.tier === 'PRO' ? 'pro' : 'free');
|
|
673
|
+
const used = data.usage?.tokensUsed ?? creds.tierTokensUsed ?? 0;
|
|
674
|
+
const cap = data.usage?.tokensCap ?? creds.tierTokensCap ?? 100000;
|
|
675
|
+
const resetAt = data.usage?.resetAt ?? '';
|
|
676
|
+
store_1.store.setTierInfo(tier, used, cap, resetAt || undefined);
|
|
677
|
+
if (used >= cap)
|
|
678
|
+
store_1.store.setTierExhausted();
|
|
679
|
+
(0, auth_1.updateCredentials)({ tier, tierTokensUsed: used, tierTokensCap: cap });
|
|
680
|
+
}
|
|
681
|
+
else if (creds.tier) {
|
|
682
|
+
// Offline fallback — use cached tier
|
|
683
|
+
const used = creds.tierTokensUsed ?? 0;
|
|
684
|
+
const cap = creds.tierTokensCap ?? 100000;
|
|
685
|
+
store_1.store.setTierInfo(creds.tier, used, cap);
|
|
686
|
+
if (used >= cap)
|
|
687
|
+
store_1.store.setTierExhausted();
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
catch {
|
|
691
|
+
// Offline — use cached tier if available
|
|
692
|
+
if (creds.tier) {
|
|
693
|
+
const used = creds.tierTokensUsed ?? 0;
|
|
694
|
+
const cap = creds.tierTokensCap ?? 100000;
|
|
695
|
+
store_1.store.setTierInfo(creds.tier, used, cap);
|
|
696
|
+
if (used >= cap)
|
|
697
|
+
store_1.store.setTierExhausted();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
// ── Load user settings from cloud ─────────────────────────
|
|
702
|
+
const userSettings = await (0, config_1.loadUserSettings)();
|
|
703
|
+
// Stash for Dashboard settings panel access
|
|
704
|
+
store_1.store._userSettings = userSettings;
|
|
705
|
+
// ── Initial run — only probe-and-refine on first run (no existing AGENTS.md)
|
|
706
|
+
const initialProbeAndRefine = !fs.existsSync(path.join(root, 'AGENTS.md'));
|
|
707
|
+
const result = await runOnce(ctx, ownership, initialProbeAndRefine, undefined, activePlatforms, userSettings);
|
|
253
708
|
ctx.generate = true;
|
|
254
709
|
if (result.code !== cli_1.ExitCode.OK)
|
|
255
710
|
return result.code;
|
|
256
|
-
|
|
257
|
-
let latestKb = result.kbContent;
|
|
258
|
-
// ── --once: process any queued complaints, then exit ──────
|
|
259
|
-
if (flags.once) {
|
|
260
|
-
if (store_1.store.state.complaintQueue.length > 0) {
|
|
261
|
-
await (0, complaintProcessor_1.processComplaints)(ctx, ownership, latestKb);
|
|
262
|
-
}
|
|
711
|
+
if (flags.once)
|
|
263
712
|
return cli_1.ExitCode.OK;
|
|
713
|
+
// ── Load preferences ───────────────────────────────────────
|
|
714
|
+
let prefs = await (0, preferences_1.loadPreferences)(root);
|
|
715
|
+
store_1.store.setPreferenceCount(prefs.preferences.length);
|
|
716
|
+
// ── Fetch community suggestions ───────────────────────────
|
|
717
|
+
if (store_1.store.state.userTier !== 'byok') {
|
|
718
|
+
// Detect primary language from analysis model
|
|
719
|
+
const state = (0, runtimeState_1.getRuntimeState)();
|
|
720
|
+
if (state.model) {
|
|
721
|
+
const langCounts = new Map();
|
|
722
|
+
for (const f of state.model.files) {
|
|
723
|
+
langCounts.set(f.language, (langCounts.get(f.language) ?? 0) + 1);
|
|
724
|
+
}
|
|
725
|
+
let primaryLang = '';
|
|
726
|
+
let maxCount = 0;
|
|
727
|
+
for (const [lang, count] of langCounts) {
|
|
728
|
+
if (count > maxCount) {
|
|
729
|
+
primaryLang = lang;
|
|
730
|
+
maxCount = count;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (primaryLang) {
|
|
734
|
+
// Fetch community suggestions, filter to relevant ones, feed to dream cycle
|
|
735
|
+
const projectDirs = new Set(state.model.files.map((f) => {
|
|
736
|
+
const parts = f.relativePath.split('/');
|
|
737
|
+
return parts.length > 1 ? parts[0] + '/' : '';
|
|
738
|
+
}));
|
|
739
|
+
(0, preferences_1.fetchSuggestions)(primaryLang, undefined, { byok: false })
|
|
740
|
+
.then((suggestions) => {
|
|
741
|
+
// Filter out suggestions for directories that don't exist in this project
|
|
742
|
+
const relevant = suggestions.filter((s) => {
|
|
743
|
+
if (!s.directory)
|
|
744
|
+
return true; // project-wide suggestions always apply
|
|
745
|
+
return projectDirs.has(s.directory) || [...projectDirs].some((d) => s.directory.startsWith(d));
|
|
746
|
+
});
|
|
747
|
+
if (relevant.length > 0) {
|
|
748
|
+
store_1.store.setSuggestions(relevant.map((s) => ({ rule: s.rule, disposition: s.disposition, directory: s.directory, suggestion: s.suggestion })));
|
|
749
|
+
}
|
|
750
|
+
})
|
|
751
|
+
.catch(() => { });
|
|
752
|
+
// Pro users: refresh suggestions every 10 minutes (interval cleaned up in shutdown)
|
|
753
|
+
if (store_1.store.state.userTier === 'pro') {
|
|
754
|
+
store_1.store._suggestionsRefresh = { primaryLang };
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// ── Resolve provider for auto-resolve in watch mode ────────
|
|
760
|
+
let watchProvider;
|
|
761
|
+
try {
|
|
762
|
+
const env = (0, optimizer_1.loadEnvFile)(root);
|
|
763
|
+
const watchCreds = (0, auth_1.loadCredentials)();
|
|
764
|
+
if (watchCreds)
|
|
765
|
+
env['ASPECTCODE_CLI_TOKEN'] = watchCreds.token;
|
|
766
|
+
if (projectConfig?.apiKey && !env['ASPECTCODE_LLM_KEY'])
|
|
767
|
+
env['ASPECTCODE_LLM_KEY'] = projectConfig.apiKey;
|
|
768
|
+
watchProvider = (0, usageTracker_1.withUsageTracking)((0, optimizer_1.resolveProvider)(env));
|
|
264
769
|
}
|
|
265
|
-
|
|
770
|
+
catch { /* no LLM — assessments go to user as before */ }
|
|
771
|
+
// ── Watch mode with real-time evaluation ───────────────────
|
|
266
772
|
log.blank();
|
|
267
|
-
log.info(logger_1.fmt.dim('Watching for changes… (Ctrl+C to stop)'));
|
|
268
773
|
store_1.store.setPhase('watching');
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return isIgnoredPath(abs);
|
|
278
|
-
},
|
|
279
|
-
});
|
|
280
|
-
let timer;
|
|
281
|
-
let running = false;
|
|
282
|
-
let pending = false;
|
|
774
|
+
// Refresh memory map every 10s to catch file deletions/additions
|
|
775
|
+
const memoryMapInterval = setInterval(() => {
|
|
776
|
+
if (stopped)
|
|
777
|
+
return;
|
|
778
|
+
store_1.store.setManagedFiles(buildManagedFiles(root, prefs.preferences.length, activePlatforms));
|
|
779
|
+
}, 10000);
|
|
780
|
+
let evalTimer;
|
|
781
|
+
let pipelineRunning = false;
|
|
283
782
|
let stopped = false;
|
|
284
|
-
|
|
783
|
+
let pendingEvalEvents = [];
|
|
784
|
+
// Pro: refresh suggestions once per day (checked every hour, skips if last fetch was <24h ago)
|
|
785
|
+
const suggestionsRefreshInfo = store_1.store._suggestionsRefresh;
|
|
786
|
+
let lastSuggestionsFetch = Date.now();
|
|
787
|
+
const SUGGESTIONS_REFRESH_MS = 24 * 60 * 60 * 1000;
|
|
788
|
+
const suggestionsInterval = suggestionsRefreshInfo ? setInterval(() => {
|
|
285
789
|
if (stopped)
|
|
286
790
|
return;
|
|
287
|
-
if (
|
|
288
|
-
pending = true;
|
|
791
|
+
if (Date.now() - lastSuggestionsFetch < SUGGESTIONS_REFRESH_MS)
|
|
289
792
|
return;
|
|
793
|
+
(0, preferences_1.fetchSuggestions)(suggestionsRefreshInfo.primaryLang, undefined, { byok: false })
|
|
794
|
+
.then((suggestions) => {
|
|
795
|
+
lastSuggestionsFetch = Date.now();
|
|
796
|
+
if (suggestions.length > 0 && !store_1.store.state.suggestionsDismissed) {
|
|
797
|
+
store_1.store.setSuggestions(suggestions.map((s) => ({ rule: s.rule, disposition: s.disposition, directory: s.directory, suggestion: s.suggestion })));
|
|
798
|
+
}
|
|
799
|
+
})
|
|
800
|
+
.catch(() => { });
|
|
801
|
+
}, 60 * 60 * 1000) : undefined;
|
|
802
|
+
// Co-change settle window — hold co-change assessments for 5s to see if dependents get updated
|
|
803
|
+
const pendingCoChange = new Map();
|
|
804
|
+
let settleTimer;
|
|
805
|
+
const recentlyChangedFiles = new Set();
|
|
806
|
+
function parseDependents(ctx) {
|
|
807
|
+
const match = ctx.match(/missing: \[([^\]]*)\]/);
|
|
808
|
+
if (!match)
|
|
809
|
+
return [];
|
|
810
|
+
return match[1].split(',').map((s) => s.trim()).filter(Boolean);
|
|
811
|
+
}
|
|
812
|
+
const resolveSettledCoChange = async (threshold, forwarded) => {
|
|
813
|
+
settleTimer = undefined;
|
|
814
|
+
const surviving = [];
|
|
815
|
+
for (const [, entry] of pendingCoChange) {
|
|
816
|
+
// Drop if all dependents were updated during settle window
|
|
817
|
+
const allUpdated = entry.dependents.length > 0 && entry.dependents.every((d) => recentlyChangedFiles.has(d));
|
|
818
|
+
if (!allUpdated)
|
|
819
|
+
surviving.push(entry.assessment);
|
|
290
820
|
}
|
|
291
|
-
|
|
821
|
+
pendingCoChange.clear();
|
|
822
|
+
if (surviving.length > 0 && watchProvider) {
|
|
823
|
+
try {
|
|
824
|
+
const results = await (0, autoResolve_1.autoResolveBatch)(surviving, prefs, watchProvider, { threshold });
|
|
825
|
+
for (const result of results) {
|
|
826
|
+
if (result.autoResolved) {
|
|
827
|
+
const a = result.assessment;
|
|
828
|
+
const dir = path.dirname(a.file) + '/';
|
|
829
|
+
if (result.decision === 'allow') {
|
|
830
|
+
prefs = (0, preferences_1.addPreference)(prefs, { rule: a.rule, pattern: a.message, disposition: 'allow', directory: dir, details: a.details, dependencyContext: a.dependencyContext });
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
prefs = (0, preferences_1.addPreference)(prefs, { rule: a.rule, pattern: a.message, disposition: 'deny', file: a.file, directory: dir, details: a.details, suggestion: a.suggestion, dependencyContext: a.dependencyContext });
|
|
834
|
+
}
|
|
835
|
+
(0, dreamCycle_1.addCorrection)(result.decision === 'allow' ? 'dismiss' : 'confirm', a);
|
|
836
|
+
const stats = { ...store_1.store.state.assessmentStats };
|
|
837
|
+
stats.autoResolved++;
|
|
838
|
+
store_1.store.state.assessmentStats = stats;
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
const a = result.assessment;
|
|
842
|
+
a.llmRecommendation = { decision: result.decision, confidence: result.confidence, reasoning: result.reasoning };
|
|
843
|
+
forwarded.push(a);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
(0, preferences_1.savePreferences)(root, prefs);
|
|
847
|
+
store_1.store.setPreferenceCount(prefs.preferences.length);
|
|
848
|
+
if (forwarded.length > 0)
|
|
849
|
+
store_1.store.pushAssessments(forwarded);
|
|
850
|
+
}
|
|
851
|
+
catch (err) {
|
|
852
|
+
if (err?.tierExhausted)
|
|
853
|
+
store_1.store.setTierExhausted();
|
|
854
|
+
store_1.store.pushAssessments(surviving);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
else if (surviving.length > 0) {
|
|
858
|
+
store_1.store.pushAssessments(surviving);
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
// Auto-probe tracking
|
|
862
|
+
let lastChangeAt = Date.now();
|
|
863
|
+
let lastProbeAt = Date.now();
|
|
864
|
+
// Fire an immediate dream at session start to review/prune existing rules
|
|
865
|
+
let sessionDreamDone = false;
|
|
866
|
+
let lastDreamAt = Date.now();
|
|
867
|
+
const optLog = flags.quiet ? undefined : {
|
|
868
|
+
info(msg) { log.info(msg); },
|
|
869
|
+
warn(msg) { log.warn(msg); },
|
|
870
|
+
error(msg) { log.error(msg); },
|
|
871
|
+
debug(msg) { log.debug(msg); },
|
|
872
|
+
};
|
|
873
|
+
// ── Dream cycle (autonomous) ────────────────────────────────
|
|
874
|
+
const doDreamCycle = async () => {
|
|
875
|
+
if (pipelineRunning || stopped || store_1.store.state.tierExhausted)
|
|
876
|
+
return;
|
|
877
|
+
store_1.store.setDreamPrompt(false);
|
|
878
|
+
const state = (0, runtimeState_1.getRuntimeState)();
|
|
879
|
+
if (!state.agentsContent)
|
|
880
|
+
return;
|
|
881
|
+
let provider;
|
|
292
882
|
try {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
883
|
+
const env = (0, optimizer_1.loadEnvFile)(root);
|
|
884
|
+
const creds = (0, auth_1.loadCredentials)();
|
|
885
|
+
if (creds && !env['ASPECTCODE_CLI_TOKEN'])
|
|
886
|
+
env['ASPECTCODE_CLI_TOKEN'] = creds.token;
|
|
887
|
+
provider = (0, usageTracker_1.withUsageTracking)((0, optimizer_1.resolveProvider)(env));
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
store_1.store.setDreaming(true);
|
|
893
|
+
pipelineRunning = true;
|
|
894
|
+
try {
|
|
895
|
+
// Read current scoped rules for dream context
|
|
896
|
+
let scopedRulesContext = '';
|
|
897
|
+
try {
|
|
898
|
+
const manifestPath = path.join(root, '.aspectcode', 'scoped-rules.json');
|
|
899
|
+
if (fs.existsSync(manifestPath)) {
|
|
900
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
901
|
+
const parts = [];
|
|
902
|
+
for (const entry of manifest.rules ?? []) {
|
|
903
|
+
try {
|
|
904
|
+
const content = fs.readFileSync(path.join(root, entry.path), 'utf-8');
|
|
905
|
+
parts.push(`### ${entry.slug} (${entry.path})\n${content}`);
|
|
906
|
+
}
|
|
907
|
+
catch { /* skip missing files */ }
|
|
908
|
+
}
|
|
909
|
+
scopedRulesContext = parts.join('\n---\n');
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
catch { /* ignore */ }
|
|
913
|
+
// Read user-authored .claude/rules/ and .claude/skills/ (read-only context for dream)
|
|
914
|
+
let userRulesContext = '';
|
|
915
|
+
try {
|
|
916
|
+
const USER_CONTEXT_CAP = 3000;
|
|
917
|
+
const userParts = [];
|
|
918
|
+
const claudeRulesDir = path.join(root, '.claude', 'rules');
|
|
919
|
+
if (fs.existsSync(claudeRulesDir)) {
|
|
920
|
+
for (const file of fs.readdirSync(claudeRulesDir)) {
|
|
921
|
+
if (file.startsWith('ac-'))
|
|
922
|
+
continue;
|
|
923
|
+
try {
|
|
924
|
+
const content = fs.readFileSync(path.join(claudeRulesDir, file), 'utf-8');
|
|
925
|
+
userParts.push(`### ${file} (user rule, read-only)\n${content}`);
|
|
926
|
+
}
|
|
927
|
+
catch { /* skip */ }
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const claudeSkillsDir = path.join(root, '.claude', 'skills');
|
|
931
|
+
if (fs.existsSync(claudeSkillsDir)) {
|
|
932
|
+
for (const file of fs.readdirSync(claudeSkillsDir)) {
|
|
933
|
+
try {
|
|
934
|
+
const content = fs.readFileSync(path.join(claudeSkillsDir, file), 'utf-8');
|
|
935
|
+
const truncated = content.length > 500
|
|
936
|
+
? content.slice(0, 500) + `\n... (truncated, ${content.length} chars total)`
|
|
937
|
+
: content;
|
|
938
|
+
userParts.push(`### ${file} (user skill, read-only)\n${truncated}`);
|
|
939
|
+
}
|
|
940
|
+
catch { /* skip */ }
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
// Cap total user context
|
|
944
|
+
let combined = userParts.join('\n---\n');
|
|
945
|
+
if (combined.length > USER_CONTEXT_CAP) {
|
|
946
|
+
combined = combined.slice(0, USER_CONTEXT_CAP) + '\n... (truncated)';
|
|
947
|
+
}
|
|
948
|
+
userRulesContext = combined;
|
|
949
|
+
}
|
|
950
|
+
catch { /* ignore */ }
|
|
951
|
+
// Format community suggestions for dream cycle context
|
|
952
|
+
let communitySuggestions;
|
|
953
|
+
const suggestions = store_1.store.state.suggestions;
|
|
954
|
+
if (suggestions.length > 0 && !store_1.store.state.suggestionsDismissed) {
|
|
955
|
+
communitySuggestions = suggestions
|
|
956
|
+
.map((s) => `- [${s.rule}] ${s.suggestion}`)
|
|
957
|
+
.join('\n');
|
|
958
|
+
// Mark as consumed so they're only integrated once
|
|
959
|
+
store_1.store.dismissSuggestions();
|
|
960
|
+
}
|
|
961
|
+
const result = await (0, dreamCycle_1.runDreamCycle)({
|
|
962
|
+
currentAgentsMd: state.agentsContent,
|
|
963
|
+
corrections: (0, dreamCycle_1.getCorrections)(),
|
|
964
|
+
provider,
|
|
965
|
+
log: optLog,
|
|
966
|
+
scopedRulesContext,
|
|
967
|
+
userRulesContext: userRulesContext || undefined,
|
|
968
|
+
communitySuggestions,
|
|
969
|
+
});
|
|
970
|
+
const host = (0, emitters_1.createNodeEmitterHost)();
|
|
971
|
+
await (0, writer_1.writeAgentsMd)(host, root, result.updatedAgentsMd, ownership);
|
|
972
|
+
(0, runtimeState_1.updateRuntimeState)({ agentsContent: result.updatedAgentsMd });
|
|
973
|
+
// Write any scoped rules from the dream cycle
|
|
974
|
+
if ((result.scopedRules.length > 0 || result.deleteSlugs.length > 0) && activePlatforms.length > 0) {
|
|
975
|
+
// Delete rules the dream cycle marked for removal
|
|
976
|
+
if (result.deleteSlugs.length > 0) {
|
|
977
|
+
await (0, scopedRules_1.deleteScopedRules)(host, root, result.deleteSlugs);
|
|
978
|
+
}
|
|
979
|
+
if (result.scopedRules.length > 0) {
|
|
980
|
+
await (0, scopedRules_1.writeRulesForPlatforms)(host, root, result.scopedRules, activePlatforms);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
(0, dreamCycle_1.markProcessed)();
|
|
984
|
+
store_1.store.setCorrectionCount((0, dreamCycle_1.getUnprocessedCount)());
|
|
985
|
+
(0, dreamCycle_1.saveDreamState)(root, { lastDreamAt: new Date().toISOString() });
|
|
986
|
+
if (result.changes.length > 0) {
|
|
987
|
+
store_1.store.setLearnedMessage(`Refined: ${result.changes.join(', ')}`);
|
|
988
|
+
}
|
|
989
|
+
// Refresh memory map to reflect any new files
|
|
990
|
+
store_1.store.setManagedFiles(buildManagedFiles(root, (await (0, preferences_1.loadPreferences)(root)).preferences.length, activePlatforms));
|
|
991
|
+
}
|
|
992
|
+
catch (err) {
|
|
993
|
+
if (err?.tierExhausted) {
|
|
994
|
+
store_1.store.setTierExhausted();
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
998
|
+
log.warn(`Dream cycle failed: ${msg}`);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
finally {
|
|
1002
|
+
pipelineRunning = false;
|
|
1003
|
+
store_1.store.setDreaming(false);
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
// Auto-dream timer: fires every 30s, dreams if corrections exist and 2+ min since last dream
|
|
1007
|
+
const AUTO_DREAM_INTERVAL_MS = 2 * 60 * 1000;
|
|
1008
|
+
const autoDreamTimer = setInterval(() => {
|
|
1009
|
+
if (stopped || pipelineRunning)
|
|
1010
|
+
return;
|
|
1011
|
+
const hasCorrections = (0, dreamCycle_1.getUnprocessedCount)() > 0;
|
|
1012
|
+
const hasSuggestions = store_1.store.state.suggestions.length > 0 && !store_1.store.state.suggestionsDismissed;
|
|
1013
|
+
if (!hasCorrections && !hasSuggestions)
|
|
1014
|
+
return;
|
|
1015
|
+
if (Date.now() - lastDreamAt < AUTO_DREAM_INTERVAL_MS)
|
|
1016
|
+
return;
|
|
1017
|
+
void doDreamCycle().then(() => { lastDreamAt = Date.now(); });
|
|
1018
|
+
}, 30000);
|
|
1019
|
+
// ── Session-start dream: review rules immediately ───────────
|
|
1020
|
+
if (watchProvider) {
|
|
1021
|
+
// Small delay to let the dashboard render first
|
|
1022
|
+
setTimeout(() => {
|
|
1023
|
+
if (stopped || pipelineRunning || sessionDreamDone)
|
|
1024
|
+
return;
|
|
1025
|
+
sessionDreamDone = true;
|
|
1026
|
+
void doDreamCycle().then(() => { lastDreamAt = Date.now(); });
|
|
1027
|
+
}, 3000);
|
|
1028
|
+
}
|
|
1029
|
+
// ── Auto-probe timer: fires after sustained activity + idle period ──
|
|
1030
|
+
const autoProbeTimer = setInterval(() => {
|
|
1031
|
+
if (stopped || pipelineRunning)
|
|
1032
|
+
return;
|
|
1033
|
+
const stats = store_1.store.state.assessmentStats;
|
|
1034
|
+
if (stats.changes < AUTO_PROBE_MIN_CHANGES)
|
|
1035
|
+
return;
|
|
1036
|
+
if (Date.now() - lastChangeAt < AUTO_PROBE_IDLE_MS)
|
|
1037
|
+
return;
|
|
1038
|
+
if (Date.now() - lastProbeAt < AUTO_PROBE_COOLDOWN_MS)
|
|
1039
|
+
return;
|
|
1040
|
+
void doProbeAndRefine();
|
|
1041
|
+
}, 60000);
|
|
1042
|
+
// ── Probe and refine ──────────────────────────────────────
|
|
1043
|
+
const doProbeAndRefine = async () => {
|
|
1044
|
+
if (stopped || pipelineRunning)
|
|
1045
|
+
return;
|
|
1046
|
+
if (store_1.store.state.tierExhausted) {
|
|
1047
|
+
store_1.store.setLearnedMessage('Token limit reached — upgrade or add your own key');
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
// Run pending dream cycle before full probe-and-refine
|
|
1051
|
+
// Dream before re-running if corrections exist
|
|
1052
|
+
if ((0, dreamCycle_1.getUnprocessedCount)() > 0)
|
|
1053
|
+
await doDreamCycle();
|
|
1054
|
+
if (pipelineRunning)
|
|
1055
|
+
return;
|
|
1056
|
+
pipelineRunning = true;
|
|
1057
|
+
if (evalTimer) {
|
|
1058
|
+
clearTimeout(evalTimer);
|
|
1059
|
+
evalTimer = undefined;
|
|
1060
|
+
}
|
|
1061
|
+
pendingEvalEvents.length = 0;
|
|
1062
|
+
store_1.store.setRecommendProbe(false);
|
|
1063
|
+
try {
|
|
1064
|
+
await runOnce(ctx, ownership, true, prefs, activePlatforms, userSettings);
|
|
1065
|
+
prefs = await (0, preferences_1.loadPreferences)(root);
|
|
1066
|
+
store_1.store.setPreferenceCount(prefs.preferences.length);
|
|
299
1067
|
store_1.store.setPhase('watching');
|
|
1068
|
+
lastProbeAt = Date.now();
|
|
300
1069
|
}
|
|
301
|
-
catch (
|
|
302
|
-
const msg =
|
|
303
|
-
log.error(`
|
|
1070
|
+
catch (err) {
|
|
1071
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1072
|
+
log.error(`Probe and refine failed: ${msg}`);
|
|
304
1073
|
}
|
|
305
1074
|
finally {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
1075
|
+
pipelineRunning = false;
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
// ── Evaluate file changes (fast, no LLM) ──────────────────
|
|
1079
|
+
const RECOMMEND_THRESHOLD = 10;
|
|
1080
|
+
const evaluateEvents = async (events) => {
|
|
1081
|
+
const state = (0, runtimeState_1.getRuntimeState)();
|
|
1082
|
+
if (!state.model || !state.agentsContent)
|
|
1083
|
+
return;
|
|
1084
|
+
for (const event of events) {
|
|
1085
|
+
(0, changeEvaluator_1.trackChange)(event);
|
|
1086
|
+
// Track add vs change counts
|
|
1087
|
+
if (event.type === 'add')
|
|
1088
|
+
store_1.store.incrementAddCount();
|
|
1089
|
+
else if (event.type === 'change')
|
|
1090
|
+
store_1.store.incrementChangeCount();
|
|
1091
|
+
if (event.type !== 'unlink') {
|
|
1092
|
+
try {
|
|
1093
|
+
const absPath = path.join(root, event.path);
|
|
1094
|
+
if (fs.existsSync(absPath)) {
|
|
1095
|
+
const content = fs.readFileSync(absPath, 'utf-8');
|
|
1096
|
+
state.fileContents?.set(event.path, content);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
catch { /* skip unreadable files */ }
|
|
1100
|
+
}
|
|
1101
|
+
const assessments = (0, changeEvaluator_1.evaluateChange)(event, {
|
|
1102
|
+
model: state.model,
|
|
1103
|
+
agentsContent: state.agentsContent,
|
|
1104
|
+
preferences: prefs,
|
|
1105
|
+
recentChanges: (0, changeEvaluator_1.getRecentChanges)(),
|
|
1106
|
+
fileContents: state.fileContents,
|
|
1107
|
+
previousHubCounts: state.previousHubCounts,
|
|
1108
|
+
});
|
|
1109
|
+
// Auto-resolve assessments with LLM when available
|
|
1110
|
+
const actionable = assessments.filter((a) => a.type !== 'ok');
|
|
1111
|
+
const threshold = flags.background ? 0.0 : (userSettings?.autoResolveThreshold ?? 0.8);
|
|
1112
|
+
if (watchProvider && actionable.length > 0) {
|
|
1113
|
+
// Split into cached (preference match) and uncached (need LLM)
|
|
1114
|
+
const cached = [];
|
|
1115
|
+
const uncached = [];
|
|
1116
|
+
for (const a of actionable) {
|
|
1117
|
+
const match = (0, autoResolve_1.matchExistingPreference)(a, prefs);
|
|
1118
|
+
if (match) {
|
|
1119
|
+
cached.push({ result: match });
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
uncached.push(a);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
// Apply cached decisions immediately
|
|
1126
|
+
for (const { result } of cached) {
|
|
1127
|
+
const a = result.assessment;
|
|
1128
|
+
(0, dreamCycle_1.addCorrection)(result.decision === 'allow' ? 'dismiss' : 'confirm', a);
|
|
1129
|
+
const stats = { ...store_1.store.state.assessmentStats };
|
|
1130
|
+
stats.autoResolved++;
|
|
1131
|
+
store_1.store.state.assessmentStats = stats;
|
|
1132
|
+
}
|
|
1133
|
+
// Batch-resolve uncached in a single LLM call
|
|
1134
|
+
const forwarded = [];
|
|
1135
|
+
if (uncached.length > 0) {
|
|
1136
|
+
// Check for co-change assessments that should wait for settle
|
|
1137
|
+
const coChange = [];
|
|
1138
|
+
const immediate = [];
|
|
1139
|
+
for (const a of uncached) {
|
|
1140
|
+
if (a.rule === 'co-change' && a.dependencyContext) {
|
|
1141
|
+
coChange.push(a);
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
immediate.push(a);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
// Hold co-change assessments for settle window
|
|
1148
|
+
if (coChange.length > 0) {
|
|
1149
|
+
for (const a of coChange) {
|
|
1150
|
+
pendingCoChange.set(a.file, { assessment: a, dependents: parseDependents(a.dependencyContext ?? ''), addedAt: Date.now() });
|
|
1151
|
+
}
|
|
1152
|
+
if (!settleTimer) {
|
|
1153
|
+
settleTimer = setTimeout(() => resolveSettledCoChange(threshold, forwarded), CO_CHANGE_SETTLE_MS);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
// Batch-resolve immediate assessments
|
|
1157
|
+
if (immediate.length > 0) {
|
|
1158
|
+
try {
|
|
1159
|
+
const results = await (0, autoResolve_1.autoResolveBatch)(immediate, prefs, watchProvider, { threshold });
|
|
1160
|
+
for (const result of results) {
|
|
1161
|
+
if (result.autoResolved) {
|
|
1162
|
+
const a = result.assessment;
|
|
1163
|
+
const dir = path.dirname(a.file) + '/';
|
|
1164
|
+
if (result.decision === 'allow') {
|
|
1165
|
+
prefs = (0, preferences_1.addPreference)(prefs, { rule: a.rule, pattern: a.message, disposition: 'allow', directory: dir, details: a.details, dependencyContext: a.dependencyContext });
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1168
|
+
prefs = (0, preferences_1.addPreference)(prefs, { rule: a.rule, pattern: a.message, disposition: 'deny', file: a.file, directory: dir, details: a.details, suggestion: a.suggestion, dependencyContext: a.dependencyContext });
|
|
1169
|
+
}
|
|
1170
|
+
(0, dreamCycle_1.addCorrection)(result.decision === 'allow' ? 'dismiss' : 'confirm', a);
|
|
1171
|
+
const stats = { ...store_1.store.state.assessmentStats };
|
|
1172
|
+
stats.autoResolved++;
|
|
1173
|
+
store_1.store.state.assessmentStats = stats;
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
const a = result.assessment;
|
|
1177
|
+
a.llmRecommendation = { decision: result.decision, confidence: result.confidence, reasoning: result.reasoning };
|
|
1178
|
+
forwarded.push(a);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
(0, preferences_1.savePreferences)(root, prefs);
|
|
1182
|
+
store_1.store.setPreferenceCount(prefs.preferences.length);
|
|
1183
|
+
if (cached.length > 0 || results.some((r) => r.autoResolved)) {
|
|
1184
|
+
const autoCount = cached.length + results.filter((r) => r.autoResolved).length;
|
|
1185
|
+
store_1.store.setLearnedMessage(`Auto-resolved ${autoCount} assessment${autoCount === 1 ? '' : 's'}`);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
catch (err) {
|
|
1189
|
+
if (err?.tierExhausted) {
|
|
1190
|
+
store_1.store.setTierExhausted();
|
|
1191
|
+
}
|
|
1192
|
+
forwarded.push(...immediate);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
else if (cached.length > 0) {
|
|
1196
|
+
store_1.store.setLearnedMessage(`Auto-resolved ${cached.length} assessment${cached.length === 1 ? '' : 's'}`);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
else if (cached.length > 0) {
|
|
1200
|
+
store_1.store.setLearnedMessage(`Auto-resolved ${cached.length} assessment${cached.length === 1 ? '' : 's'}`);
|
|
1201
|
+
}
|
|
1202
|
+
store_1.store.pushAssessments(forwarded);
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
store_1.store.pushAssessments(assessments);
|
|
310
1206
|
}
|
|
1207
|
+
// Change flash for clean changes
|
|
1208
|
+
const hasNonOk = assessments.some((a) => a.type !== 'ok');
|
|
1209
|
+
if (!hasNonOk) {
|
|
1210
|
+
store_1.store.setLastChangeFlash(`${event.path} — ok`);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
// Auto-recommend probe-and-refine
|
|
1214
|
+
const stats = store_1.store.state.assessmentStats;
|
|
1215
|
+
if (stats.changes >= RECOMMEND_THRESHOLD && !store_1.store.state.recommendProbe) {
|
|
1216
|
+
store_1.store.setRecommendProbe(true);
|
|
311
1217
|
}
|
|
312
1218
|
};
|
|
1219
|
+
// ── File change handler ────────────────────────────────────
|
|
313
1220
|
const onFsEvent = (eventType, eventPath) => {
|
|
1221
|
+
if (pipelineRunning)
|
|
1222
|
+
return;
|
|
314
1223
|
const abs = path.resolve(root, eventPath);
|
|
315
1224
|
if (!isSupportedSourceFile(abs) || isIgnoredPath(abs))
|
|
316
1225
|
return;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
1226
|
+
const posixPath = eventPath.replace(/\\/g, '/');
|
|
1227
|
+
pendingEvalEvents.push({ type: eventType, path: posixPath });
|
|
1228
|
+
lastChangeAt = Date.now();
|
|
1229
|
+
// Track recently changed files for co-change settle window
|
|
1230
|
+
recentlyChangedFiles.add(posixPath);
|
|
1231
|
+
setTimeout(() => recentlyChangedFiles.delete(posixPath), CO_CHANGE_SETTLE_MS + 1000);
|
|
1232
|
+
if (evalTimer)
|
|
1233
|
+
clearTimeout(evalTimer);
|
|
1234
|
+
evalTimer = setTimeout(() => {
|
|
1235
|
+
evalTimer = undefined;
|
|
1236
|
+
const events = pendingEvalEvents.splice(0);
|
|
1237
|
+
evaluateEvents(events);
|
|
1238
|
+
}, EVAL_DEBOUNCE_MS);
|
|
323
1239
|
};
|
|
324
|
-
watcher
|
|
325
|
-
watcher
|
|
326
|
-
watcher.on('unlink', (p) => onFsEvent('unlink', p));
|
|
1240
|
+
// ── Start watcher (must be after onFsEvent is defined) ─────
|
|
1241
|
+
const watcher = createFileWatcher(root, onFsEvent);
|
|
327
1242
|
watcher.on('error', (e) => log.error(`Watcher error: ${String(e)}`));
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const complaintPoll = setInterval(async () => {
|
|
333
|
-
if (stopped || running || store_1.store.state.complaintQueue.length === 0)
|
|
1243
|
+
// ── Expose action handler for keyboard input ───────────────
|
|
1244
|
+
const onAssessmentAction = (action) => {
|
|
1245
|
+
if (action.type === 'probe-and-refine') {
|
|
1246
|
+
void doProbeAndRefine();
|
|
334
1247
|
return;
|
|
335
|
-
running = true;
|
|
336
|
-
try {
|
|
337
|
-
await (0, complaintProcessor_1.processComplaints)(ctx, ownership, latestKb);
|
|
338
1248
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
1249
|
+
// Dream cycle is autonomous — no manual trigger
|
|
1250
|
+
if (action.type === 'open-pricing') {
|
|
1251
|
+
const { exec } = require('child_process');
|
|
1252
|
+
const url = 'https://aspectcode.com/pricing';
|
|
1253
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start ""' : 'xdg-open';
|
|
1254
|
+
exec(`${cmd} "${url}"`);
|
|
1255
|
+
store_1.store.setLearnedMessage('opened pricing page');
|
|
1256
|
+
return;
|
|
342
1257
|
}
|
|
343
|
-
|
|
344
|
-
|
|
1258
|
+
if (action.type === 'login') {
|
|
1259
|
+
store_1.store.setLearnedMessage('opening browser…');
|
|
1260
|
+
void (0, auth_1.startBackgroundLogin)().then((email) => {
|
|
1261
|
+
if (email) {
|
|
1262
|
+
store_1.store.setUserEmail(email);
|
|
1263
|
+
store_1.store.setLearnedMessage(`logged in as ${email}`);
|
|
1264
|
+
}
|
|
1265
|
+
else {
|
|
1266
|
+
store_1.store.setLearnedMessage('login failed or timed out');
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
return;
|
|
345
1270
|
}
|
|
346
|
-
|
|
1271
|
+
void handleAssessmentAction(action, prefs, root, ownership).then((updated) => {
|
|
1272
|
+
prefs = updated;
|
|
1273
|
+
});
|
|
1274
|
+
};
|
|
1275
|
+
store_1.store._onAssessmentAction = onAssessmentAction;
|
|
347
1276
|
return await new Promise((resolve) => {
|
|
348
1277
|
const shutdown = async (signal) => {
|
|
349
1278
|
if (stopped)
|
|
350
1279
|
return;
|
|
351
1280
|
stopped = true;
|
|
352
|
-
clearInterval(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
1281
|
+
clearInterval(memoryMapInterval);
|
|
1282
|
+
clearInterval(autoDreamTimer);
|
|
1283
|
+
clearInterval(autoProbeTimer);
|
|
1284
|
+
if (suggestionsInterval)
|
|
1285
|
+
clearInterval(suggestionsInterval);
|
|
1286
|
+
if (evalTimer) {
|
|
1287
|
+
clearTimeout(evalTimer);
|
|
1288
|
+
evalTimer = undefined;
|
|
356
1289
|
}
|
|
357
1290
|
log.blank();
|
|
358
1291
|
log.info(logger_1.fmt.dim(`Stopping (${signal})…`));
|