aspectcode 0.4.0 → 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/README.md +13 -0
- 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 +385 -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 +12 -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 +149 -8
- 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 +21 -18
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +1139 -162
- 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 -141
- 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 -20
- 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 -89
- 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 -29
- package/node_modules/@aspectcode/evaluator/dist/probes.d.ts.map +1 -1
- package/node_modules/@aspectcode/evaluator/dist/probes.js +188 -204
- 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/package.json +6 -7
- 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 -15
- package/node_modules/@aspectcode/emitters/dist/instructions/content.d.ts.map +0 -1
- package/node_modules/@aspectcode/emitters/dist/instructions/content.js +0 -289
- 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. Write static-template AGENTS.md for immediate feedback
|
|
9
|
-
* 5. If API key present → LLM generates AGENTS.md from KB → overwrite
|
|
10
|
-
* If no API key → keep static AGENTS.md + warn
|
|
11
|
-
* 6. If --kb flag → also write kb.md
|
|
12
|
-
* 7. If not --once → watch for changes and repeat
|
|
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 });
|
|
48
|
-
exports.
|
|
43
|
+
exports.isIgnoredPath = isIgnoredPath;
|
|
44
|
+
exports.isSupportedSourceFile = isSupportedSourceFile;
|
|
45
|
+
exports.createFileWatcher = createFileWatcher;
|
|
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,25 +92,236 @@ 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
|
-
|
|
93
|
-
if (!fs.existsSync(agentsPath) && !fs.existsSync(configPath)) {
|
|
317
|
+
if (!fs.existsSync(agentsPath)) {
|
|
94
318
|
store_1.store.setFirstRun(true);
|
|
95
319
|
}
|
|
96
320
|
// ── 1. Discover & read files ──────────────────────────────
|
|
97
321
|
store_1.store.setPhase('discovering');
|
|
98
322
|
const workspace = await (0, workspace_1.loadWorkspaceFiles)(root, config, log, { quiet: flags.quiet, spin: ctx.spin });
|
|
99
323
|
if (workspace.discoveredPaths.length === 0) {
|
|
100
|
-
log.warn('No source files found.
|
|
324
|
+
log.warn('No source files found.');
|
|
101
325
|
return { code: cli_1.ExitCode.ERROR, kbContent: '' };
|
|
102
326
|
}
|
|
103
327
|
// ── 2. Analyze ────────────────────────────────────────────
|
|
@@ -105,210 +329,963 @@ async function runOnce(ctx, ownership) {
|
|
|
105
329
|
const model = await (0, core_1.analyzeRepoWithDependencies)(root, workspace.relativeFiles, workspace.absoluteFiles, workspace.host);
|
|
106
330
|
spinAnalyze.stop(`Analyzed ${model.files.length} files, ${model.graph.edges.length} edges`);
|
|
107
331
|
store_1.store.setStats(model.files.length, model.graph.edges.length);
|
|
108
|
-
// ── 3. Build KB
|
|
332
|
+
// ── 3. Build KB ───────────────────────────────────────────
|
|
109
333
|
const spinKb = ctx.spin('Building knowledge base…', 'building-kb');
|
|
110
334
|
const kbContent = (0, kbBuilder_1.buildKbContent)(model, root, workspace.relativeFiles);
|
|
111
335
|
spinKb.stop('Knowledge base built');
|
|
112
|
-
// ── 4. Read
|
|
336
|
+
// ── 4. Read tool instruction files ────────────────────────
|
|
113
337
|
const host = (0, emitters_1.createNodeEmitterHost)();
|
|
114
338
|
const toolInstructions = await (0, toolIngestion_1.readToolInstructions)(host, root);
|
|
115
339
|
if (toolInstructions.size > 0) {
|
|
116
|
-
|
|
117
|
-
store_1.store.addSetupNote(`context: ${toolNames}`);
|
|
118
|
-
log.debug(`Read ${toolInstructions.size} AI tool instruction file(s) as context`);
|
|
119
|
-
}
|
|
120
|
-
// ── 5. Write static-template AGENTS.md for immediate feedback ─
|
|
121
|
-
// Written to disk right away so the user sees output early,
|
|
122
|
-
// even before the LLM generation finishes.
|
|
123
|
-
const baseContent = (0, emitters_1.generateCanonicalContentForMode)('safe', kbContent.length > 0);
|
|
124
|
-
if (!flags.dryRun) {
|
|
125
|
-
await (0, writer_1.writeAgentsMd)(host, root, baseContent, ownership);
|
|
126
|
-
store_1.store.addOutput('AGENTS.md written (base)');
|
|
127
|
-
log.debug('Base AGENTS.md written from static analysis');
|
|
128
|
-
}
|
|
129
|
-
// ── 6. LLM generation or static fallback ───────────────
|
|
130
|
-
store_1.store.setPhase('optimizing');
|
|
131
|
-
const optimizeResult = await (0, optimize_1.tryOptimize)(ctx, kbContent, toolInstructions, config, baseContent);
|
|
132
|
-
// ── 7. Write LLM-generated AGENTS.md ───────────────────
|
|
133
|
-
store_1.store.setPhase('writing');
|
|
134
|
-
if (flags.dryRun) {
|
|
135
|
-
log.info(logger_1.fmt.bold('Dry run — proposed AGENTS.md:'));
|
|
136
|
-
log.blank();
|
|
137
|
-
log.info(optimizeResult.content);
|
|
138
|
-
log.blank();
|
|
340
|
+
store_1.store.addSetupNote(`context: ${[...toolInstructions.keys()].join(', ')}`);
|
|
139
341
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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;
|
|
346
|
+
if (ctx.generate) {
|
|
347
|
+
if (!flags.dryRun) {
|
|
348
|
+
await (0, writer_1.writeAgentsMd)(host, root, baseContent, ownership);
|
|
349
|
+
store_1.store.addOutput('AGENTS.md written (base)');
|
|
350
|
+
}
|
|
351
|
+
store_1.store.setPhase('optimizing');
|
|
352
|
+
// Skip probe-and-refine if tier is already exhausted
|
|
353
|
+
const skipProbe = probeAndRefine && store_1.store.state.tierExhausted;
|
|
354
|
+
let optimizeResult;
|
|
143
355
|
try {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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;
|
|
368
|
+
store_1.store.setPhase('writing');
|
|
369
|
+
if (flags.dryRun) {
|
|
370
|
+
log.info(logger_1.fmt.bold('Dry run — proposed AGENTS.md:'));
|
|
371
|
+
log.blank();
|
|
372
|
+
log.info(finalContent);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
let previousContent;
|
|
376
|
+
try {
|
|
377
|
+
if (fs.existsSync(agentsPath)) {
|
|
378
|
+
previousContent = fs.readFileSync(agentsPath, 'utf-8');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch { /* ignore */ }
|
|
382
|
+
await (0, writer_1.writeAgentsMd)(host, root, finalContent, ownership);
|
|
383
|
+
const modeLabel = ownership === 'section' ? ' (section)' : '';
|
|
384
|
+
const verb = optimizeResult.reasoning.length > 0 ? 'generated' : 'written';
|
|
385
|
+
store_1.store.addOutput(`AGENTS.md ${verb}${modeLabel}`);
|
|
386
|
+
if (previousContent !== undefined && previousContent !== baseContent) {
|
|
387
|
+
store_1.store.setDiffSummary((0, diffSummary_1.diffSummary)(previousContent, finalContent));
|
|
388
|
+
}
|
|
389
|
+
store_1.store.setSummary((0, summary_1.summarizeContent)(finalContent));
|
|
390
|
+
}
|
|
168
391
|
}
|
|
392
|
+
else {
|
|
393
|
+
store_1.store.setPhase('writing');
|
|
394
|
+
if (flags.dryRun) {
|
|
395
|
+
log.info(logger_1.fmt.bold('Dry run — proposed AGENTS.md (KB-custom):'));
|
|
396
|
+
log.blank();
|
|
397
|
+
log.info(baseContent);
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
await (0, writer_1.writeAgentsMd)(host, root, baseContent, ownership);
|
|
401
|
+
store_1.store.addOutput('AGENTS.md written (KB-custom)');
|
|
402
|
+
store_1.store.setSummary((0, summary_1.summarizeContent)(baseContent));
|
|
403
|
+
}
|
|
404
|
+
// No LLM available — use static extraction for scoped rules
|
|
405
|
+
// No static rules in non-LLM path — dream cycle handles rules
|
|
406
|
+
}
|
|
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);
|
|
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));
|
|
169
425
|
const elapsedMs = Date.now() - startMs;
|
|
170
426
|
store_1.store.setElapsed(`${(elapsedMs / 1000).toFixed(1)}s`);
|
|
171
427
|
store_1.store.setPhase('done');
|
|
172
|
-
log.info(logger_1.fmt.dim(`Done in ${(elapsedMs / 1000).toFixed(1)}s`));
|
|
173
428
|
return { code: cli_1.ExitCode.OK, kbContent };
|
|
174
429
|
}
|
|
175
|
-
// ──
|
|
176
|
-
|
|
177
|
-
* Resolve AGENTS.md ownership mode.
|
|
178
|
-
*
|
|
179
|
-
* Called from main() BEFORE the ink dashboard is mounted, because
|
|
180
|
-
* the interactive prompt uses raw stdin which conflicts with ink's useInput.
|
|
181
|
-
*/
|
|
182
|
-
async function resolveOwnership(root) {
|
|
430
|
+
// ── Resolve ownership mode ───────────────────────────────────
|
|
431
|
+
async function resolveRunMode(root) {
|
|
183
432
|
const config = (0, config_1.loadConfig)(root);
|
|
184
|
-
if (config?.ownership)
|
|
185
|
-
return config.ownership;
|
|
433
|
+
if (config?.ownership) {
|
|
434
|
+
return { ownership: config.ownership, generate: true };
|
|
435
|
+
}
|
|
436
|
+
const agentsPath = path.join(root, 'AGENTS.md');
|
|
437
|
+
let existingContent = null;
|
|
186
438
|
try {
|
|
187
|
-
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
188
|
-
const agentsPath = path.join(root, 'AGENTS.md');
|
|
189
439
|
if (fs.existsSync(agentsPath)) {
|
|
190
|
-
|
|
191
|
-
if ((0, writer_1.hasMarkers)(
|
|
192
|
-
return 'section';
|
|
193
|
-
|
|
440
|
+
existingContent = fs.readFileSync(agentsPath, 'utf-8');
|
|
441
|
+
if ((0, writer_1.hasMarkers)(existingContent))
|
|
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;
|
|
458
|
+
}
|
|
194
459
|
const ownership = idx === 1 ? 'section' : 'full';
|
|
460
|
+
// Save choice so we don't ask again
|
|
195
461
|
(0, config_1.saveConfig)(root, { ownership });
|
|
196
|
-
return ownership;
|
|
462
|
+
return { ownership, generate: true };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
return { ownership: 'full', generate: true };
|
|
467
|
+
}
|
|
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
|
+
}
|
|
197
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);
|
|
573
|
+
try {
|
|
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;
|
|
198
584
|
}
|
|
199
585
|
catch {
|
|
200
|
-
|
|
586
|
+
const platforms = detected.length > 0 ? detected : ['claude'];
|
|
587
|
+
(0, config_1.saveConfig)(root, { platforms });
|
|
588
|
+
return platforms;
|
|
201
589
|
}
|
|
202
|
-
return 'full';
|
|
203
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();
|
|
634
|
+
}
|
|
635
|
+
store_1.store.setCorrectionCount((0, dreamCycle_1.getUnprocessedCount)());
|
|
636
|
+
return prefs;
|
|
637
|
+
}
|
|
638
|
+
// ── Main pipeline ────────────────────────────────────────────
|
|
204
639
|
async function runPipeline(ctx) {
|
|
205
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`);
|
|
206
644
|
log.info(`${logger_1.fmt.bold('aspectcode')} — ${logger_1.fmt.cyan(root)}`);
|
|
207
645
|
log.blank();
|
|
208
|
-
|
|
209
|
-
|
|
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);
|
|
708
|
+
ctx.generate = true;
|
|
210
709
|
if (result.code !== cli_1.ExitCode.OK)
|
|
211
710
|
return result.code;
|
|
212
|
-
|
|
213
|
-
let latestKb = result.kbContent;
|
|
214
|
-
// ── --once: process any queued complaints, then exit ──────
|
|
215
|
-
if (flags.once) {
|
|
216
|
-
if (store_1.store.state.complaintQueue.length > 0) {
|
|
217
|
-
await (0, complaintProcessor_1.processComplaints)(ctx, ownership, latestKb);
|
|
218
|
-
}
|
|
711
|
+
if (flags.once)
|
|
219
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
|
+
}
|
|
220
758
|
}
|
|
221
|
-
// ──
|
|
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));
|
|
769
|
+
}
|
|
770
|
+
catch { /* no LLM — assessments go to user as before */ }
|
|
771
|
+
// ── Watch mode with real-time evaluation ───────────────────
|
|
222
772
|
log.blank();
|
|
223
|
-
log.info(logger_1.fmt.dim('Watching for changes… (Ctrl+C to stop)'));
|
|
224
773
|
store_1.store.setPhase('watching');
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return isIgnoredPath(abs);
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
let timer;
|
|
237
|
-
let running = false;
|
|
238
|
-
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;
|
|
239
782
|
let stopped = false;
|
|
240
|
-
|
|
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(() => {
|
|
241
789
|
if (stopped)
|
|
242
790
|
return;
|
|
243
|
-
if (
|
|
244
|
-
pending = true;
|
|
791
|
+
if (Date.now() - lastSuggestionsFetch < SUGGESTIONS_REFRESH_MS)
|
|
245
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);
|
|
246
820
|
}
|
|
247
|
-
|
|
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;
|
|
248
882
|
try {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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);
|
|
255
1067
|
store_1.store.setPhase('watching');
|
|
1068
|
+
lastProbeAt = Date.now();
|
|
256
1069
|
}
|
|
257
|
-
catch (
|
|
258
|
-
const msg =
|
|
259
|
-
log.error(`
|
|
1070
|
+
catch (err) {
|
|
1071
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1072
|
+
log.error(`Probe and refine failed: ${msg}`);
|
|
260
1073
|
}
|
|
261
1074
|
finally {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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);
|
|
266
1203
|
}
|
|
1204
|
+
else {
|
|
1205
|
+
store_1.store.pushAssessments(assessments);
|
|
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);
|
|
267
1217
|
}
|
|
268
1218
|
};
|
|
1219
|
+
// ── File change handler ────────────────────────────────────
|
|
269
1220
|
const onFsEvent = (eventType, eventPath) => {
|
|
1221
|
+
if (pipelineRunning)
|
|
1222
|
+
return;
|
|
270
1223
|
const abs = path.resolve(root, eventPath);
|
|
271
1224
|
if (!isSupportedSourceFile(abs) || isIgnoredPath(abs))
|
|
272
1225
|
return;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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);
|
|
279
1239
|
};
|
|
280
|
-
watcher
|
|
281
|
-
watcher
|
|
282
|
-
watcher.on('unlink', (p) => onFsEvent('unlink', p));
|
|
1240
|
+
// ── Start watcher (must be after onFsEvent is defined) ─────
|
|
1241
|
+
const watcher = createFileWatcher(root, onFsEvent);
|
|
283
1242
|
watcher.on('error', (e) => log.error(`Watcher error: ${String(e)}`));
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const complaintPoll = setInterval(async () => {
|
|
289
|
-
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();
|
|
290
1247
|
return;
|
|
291
|
-
running = true;
|
|
292
|
-
try {
|
|
293
|
-
await (0, complaintProcessor_1.processComplaints)(ctx, ownership, latestKb);
|
|
294
1248
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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;
|
|
298
1257
|
}
|
|
299
|
-
|
|
300
|
-
|
|
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;
|
|
301
1270
|
}
|
|
302
|
-
|
|
1271
|
+
void handleAssessmentAction(action, prefs, root, ownership).then((updated) => {
|
|
1272
|
+
prefs = updated;
|
|
1273
|
+
});
|
|
1274
|
+
};
|
|
1275
|
+
store_1.store._onAssessmentAction = onAssessmentAction;
|
|
303
1276
|
return await new Promise((resolve) => {
|
|
304
1277
|
const shutdown = async (signal) => {
|
|
305
1278
|
if (stopped)
|
|
306
1279
|
return;
|
|
307
1280
|
stopped = true;
|
|
308
|
-
clearInterval(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
1281
|
+
clearInterval(memoryMapInterval);
|
|
1282
|
+
clearInterval(autoDreamTimer);
|
|
1283
|
+
clearInterval(autoProbeTimer);
|
|
1284
|
+
if (suggestionsInterval)
|
|
1285
|
+
clearInterval(suggestionsInterval);
|
|
1286
|
+
if (evalTimer) {
|
|
1287
|
+
clearTimeout(evalTimer);
|
|
1288
|
+
evalTimer = undefined;
|
|
312
1289
|
}
|
|
313
1290
|
log.blank();
|
|
314
1291
|
log.info(logger_1.fmt.dim(`Stopping (${signal})…`));
|