codemini-cli 0.5.10 → 0.5.12
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/OPERATIONS.md +242 -242
- package/README.md +588 -588
- package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-7HL7yft8.js → highlighted-body-OFNGDK62-B-G99D0A.js} +1 -1
- package/codemini-web/dist/assets/{index-BK75hMb2.js → index-DIGUEzan.js} +108 -108
- package/codemini-web/dist/assets/index-Dkq1DdDX.css +2 -0
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-va2Kl89u.js +1 -0
- package/codemini-web/dist/index.html +35 -23
- package/codemini-web/lib/approval-manager.js +32 -32
- package/codemini-web/lib/runtime-bridge.js +17 -11
- package/codemini-web/server.js +534 -205
- package/deployment.md +212 -212
- package/package.json +2 -2
- package/skills/brainstorm/SKILL.md +77 -77
- package/skills/codemini.skills.json +40 -40
- package/skills/grill-me/SKILL.md +30 -30
- package/skills/superpowers-lite/SKILL.md +82 -82
- package/src/cli.js +74 -74
- package/src/commands/chat.js +210 -210
- package/src/commands/run.js +313 -313
- package/src/commands/skill.js +438 -304
- package/src/commands/web.js +57 -57
- package/src/core/agent-loop.js +980 -980
- package/src/core/ast.js +309 -307
- package/src/core/chat-runtime.js +6261 -6253
- package/src/core/command-evaluator.js +72 -72
- package/src/core/command-loader.js +311 -311
- package/src/core/command-policy.js +301 -301
- package/src/core/command-risk.js +156 -156
- package/src/core/config-store.js +286 -285
- package/src/core/constants.js +18 -1
- package/src/core/context-compact.js +365 -365
- package/src/core/default-system-prompt.js +114 -107
- package/src/core/dream-audit.js +105 -105
- package/src/core/dream-consolidate.js +229 -229
- package/src/core/dream-evaluator.js +185 -185
- package/src/core/fff-adapter.js +383 -383
- package/src/core/memory-store.js +543 -543
- package/src/core/project-index.js +737 -548
- package/src/core/project-instructions.js +98 -98
- package/src/core/provider/anthropic.js +514 -514
- package/src/core/provider/openai-compatible.js +501 -501
- package/src/core/reflect-skill.js +178 -178
- package/src/core/reply-language.js +40 -40
- package/src/core/session-store.js +474 -474
- package/src/core/shell-profile.js +237 -237
- package/src/core/shell.js +323 -323
- package/src/core/soul.js +69 -69
- package/src/core/system-prompt-composer.js +52 -52
- package/src/core/tool-args.js +199 -154
- package/src/core/tool-output.js +184 -184
- package/src/core/tool-result-store.js +206 -206
- package/src/core/tools.js +3024 -2893
- package/src/core/version.js +11 -11
- package/src/tui/chat-app.js +5173 -5171
- package/src/tui/tool-activity/presenters/misc.js +30 -30
- package/src/tui/tool-activity/presenters/system.js +20 -20
- package/templates/project-requirements/report-shell.html +582 -582
- package/codemini-web/dist/assets/index-BSdIdn3L.css +0 -2
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-Dg9qh8mg.js +0 -1
|
@@ -1,110 +1,117 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { getShellSystemPrompt } from './shell-profile.js';
|
|
5
|
-
|
|
6
|
-
function formatToolPath(...segments) {
|
|
7
|
-
return JSON.stringify(path.join(process.cwd(), ...segments));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function getToolFewShotBlock() {
|
|
11
|
-
const cwd = process.cwd();
|
|
12
|
-
const authServicePath = formatToolPath('src', 'auth', 'service.ts');
|
|
13
|
-
const reducerRangePath = JSON.stringify(`${path.join(cwd, 'src', 'store', 'reducer.ts')}:110-150`);
|
|
14
|
-
const notesPath = formatToolPath('notes.txt');
|
|
15
|
-
return `# Tool Examples
|
|
16
|
-
|
|
17
|
-
Use these as style examples for tool calls:
|
|
18
|
-
|
|
19
|
-
Current working directory: ${cwd}
|
|
20
|
-
When a tool takes path, build it from the current working directory and prefer absolute paths.
|
|
21
|
-
If the user mentions a project-relative path like src/app.ts, resolve it from ${cwd} instead of guessing parent directories.
|
|
22
|
-
|
|
23
|
-
1. File discovery then read
|
|
24
|
-
User: compare the auth flow
|
|
25
|
-
Assistant: first narrow the search with the project index
|
|
26
|
-
Tool: query_project_index({"query":"auth flow","path":"src","max_results":3})
|
|
27
|
-
Tool: read({"path":${authServicePath}})
|
|
28
|
-
|
|
29
|
-
If the visible tool list does not include a needed capability, load it with tool_search instead of assuming it does not exist.
|
|
30
|
-
Example:
|
|
31
|
-
Tool: tool_search({"query":"glob"})
|
|
32
|
-
Tool: glob({"pattern":"src/**/*.ts"})
|
|
33
|
-
|
|
34
|
-
2. Targeted search then exact text edit
|
|
35
|
-
User: rename loginUser to signInUser
|
|
36
|
-
Assistant: first find the exact occurrences
|
|
37
|
-
Tool: grep({"pattern":"loginUser","path":"src"})
|
|
38
|
-
Tool: edit({"path":${authServicePath},"old_text":"loginUser","new_text":"signInUser"})
|
|
39
|
-
|
|
40
|
-
3. Read a specific range
|
|
41
|
-
User: inspect the reducer around line 120
|
|
42
|
-
Assistant: read only the needed range
|
|
43
|
-
Tool: read({"path":${reducerRangePath}})
|
|
44
|
-
|
|
45
|
-
4. Track a complex task with todos
|
|
46
|
-
User: update the login flow and verify it
|
|
47
|
-
Assistant: create a focused todo checklist before starting
|
|
48
|
-
Tool: update_todos({"todos":[{"content":"Inspect the current login flow","activeForm":"Inspecting the current login flow","status":"in_progress"},{"content":"Implement the requested login changes","activeForm":"Implementing the requested login changes","status":"pending"},{"content":"Run focused verification for the login flow","activeForm":"Running focused verification for the login flow","status":"pending"}]})
|
|
49
|
-
Assistant: keep the checklist updated as each phase finishes, and do not give a completion-style wrap-up until the checklist is complete or a blocker is recorded
|
|
50
|
-
|
|
51
|
-
5. Create a new file
|
|
52
|
-
User: add a notes file
|
|
53
|
-
Assistant: create the file directly
|
|
54
|
-
Tool: write({"path":${notesPath},"content":"todo\\n"})
|
|
55
|
-
|
|
56
|
-
6. Save a high-signal observation to memory
|
|
57
|
-
When you notice a reusable pattern, a user correction, a repeated failure, or a stable preference — save it to persistent memory. Choose scope carefully:
|
|
58
|
-
- scope "user" for personal preferences (language, reply style, interaction habits)
|
|
59
|
-
- scope "global" for cross-project lessons (environment quirks, general tool workflows)
|
|
60
|
-
- scope "project" for project-specific knowledge (architecture conventions, local config, test commands, file locations)
|
|
61
|
-
|
|
62
|
-
Examples:
|
|
63
|
-
Tool: save_memory({"content":"User prefers tab size 2 for all JSON files","scope":"user","kind":"preference"})
|
|
64
|
-
Tool: save_memory({"content":"This project uses vitest, not jest — run tests with npx vitest run","scope":"project","kind":"pattern"})
|
|
65
|
-
Tool: save_memory({"content":"WSL2 bash exec prefix does not support cd as a command","scope":"global","kind":"correction"})
|
|
66
|
-
|
|
67
|
-
7. Run a dream loop consolidation pass
|
|
68
|
-
When you want to review and consolidate inbox entries into long-term memory.
|
|
69
|
-
Tool: dream_consolidate({})
|
|
70
|
-
|
|
71
|
-
8. Read a live web page by URL
|
|
72
|
-
User: summarize https://example.com/docs
|
|
73
|
-
Assistant: load the web fetch tool and read the page directly
|
|
74
|
-
Tool: tool_search({"query":"web_fetch"})
|
|
75
|
-
Tool: web_fetch({"url":"https://example.com/docs"})
|
|
76
|
-
|
|
77
|
-
9. Search the web
|
|
78
|
-
User: search the web for latest pnpm release
|
|
79
|
-
Assistant: load the web search tool and run a targeted search
|
|
80
|
-
Tool: tool_search({"query":"web_search"})
|
|
81
|
-
Tool: web_search({"query":"latest pnpm release","max_results":5})
|
|
82
|
-
|
|
83
|
-
Prefer these direct tool shapes over multi-step metadata reads or shell fallbacks.
|
|
84
|
-
Prefer explicit absolute path values when the current working directory is known.`;
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getShellSystemPrompt } from './shell-profile.js';
|
|
5
|
+
|
|
6
|
+
function formatToolPath(...segments) {
|
|
7
|
+
return JSON.stringify(path.join(process.cwd(), ...segments));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getToolFewShotBlock() {
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const authServicePath = formatToolPath('src', 'auth', 'service.ts');
|
|
13
|
+
const reducerRangePath = JSON.stringify(`${path.join(cwd, 'src', 'store', 'reducer.ts')}:110-150`);
|
|
14
|
+
const notesPath = formatToolPath('notes.txt');
|
|
15
|
+
return `# Tool Examples
|
|
16
|
+
|
|
17
|
+
Use these as style examples for tool calls:
|
|
18
|
+
|
|
19
|
+
Current working directory: ${cwd}
|
|
20
|
+
When a tool takes path, build it from the current working directory and prefer absolute paths.
|
|
21
|
+
If the user mentions a project-relative path like src/app.ts, resolve it from ${cwd} instead of guessing parent directories.
|
|
22
|
+
|
|
23
|
+
1. File discovery then read
|
|
24
|
+
User: compare the auth flow
|
|
25
|
+
Assistant: first narrow the search with the project index
|
|
26
|
+
Tool: query_project_index({"query":"auth flow","path":"src","max_results":3})
|
|
27
|
+
Tool: read({"path":${authServicePath}})
|
|
28
|
+
|
|
29
|
+
If the visible tool list does not include a needed capability, load it with tool_search instead of assuming it does not exist.
|
|
30
|
+
Example:
|
|
31
|
+
Tool: tool_search({"query":"glob"})
|
|
32
|
+
Tool: glob({"pattern":"src/**/*.ts"})
|
|
33
|
+
|
|
34
|
+
2. Targeted search then exact text edit
|
|
35
|
+
User: rename loginUser to signInUser
|
|
36
|
+
Assistant: first find the exact occurrences
|
|
37
|
+
Tool: grep({"pattern":"loginUser","path":"src"})
|
|
38
|
+
Tool: edit({"path":${authServicePath},"old_text":"loginUser","new_text":"signInUser"})
|
|
39
|
+
|
|
40
|
+
3. Read a specific range
|
|
41
|
+
User: inspect the reducer around line 120
|
|
42
|
+
Assistant: read only the needed range
|
|
43
|
+
Tool: read({"path":${reducerRangePath}})
|
|
44
|
+
|
|
45
|
+
4. Track a complex task with todos
|
|
46
|
+
User: update the login flow and verify it
|
|
47
|
+
Assistant: create a focused todo checklist before starting
|
|
48
|
+
Tool: update_todos({"todos":[{"content":"Inspect the current login flow","activeForm":"Inspecting the current login flow","status":"in_progress"},{"content":"Implement the requested login changes","activeForm":"Implementing the requested login changes","status":"pending"},{"content":"Run focused verification for the login flow","activeForm":"Running focused verification for the login flow","status":"pending"}]})
|
|
49
|
+
Assistant: keep the checklist updated as each phase finishes, and do not give a completion-style wrap-up until the checklist is complete or a blocker is recorded
|
|
50
|
+
|
|
51
|
+
5. Create a new file
|
|
52
|
+
User: add a notes file
|
|
53
|
+
Assistant: create the file directly
|
|
54
|
+
Tool: write({"path":${notesPath},"content":"todo\\n"})
|
|
55
|
+
|
|
56
|
+
6. Save a high-signal observation to memory
|
|
57
|
+
When you notice a reusable pattern, a user correction, a repeated failure, or a stable preference — save it to persistent memory. Choose scope carefully:
|
|
58
|
+
- scope "user" for personal preferences (language, reply style, interaction habits)
|
|
59
|
+
- scope "global" for cross-project lessons (environment quirks, general tool workflows)
|
|
60
|
+
- scope "project" for project-specific knowledge (architecture conventions, local config, test commands, file locations)
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
Tool: save_memory({"content":"User prefers tab size 2 for all JSON files","scope":"user","kind":"preference"})
|
|
64
|
+
Tool: save_memory({"content":"This project uses vitest, not jest — run tests with npx vitest run","scope":"project","kind":"pattern"})
|
|
65
|
+
Tool: save_memory({"content":"WSL2 bash exec prefix does not support cd as a command","scope":"global","kind":"correction"})
|
|
66
|
+
|
|
67
|
+
7. Run a dream loop consolidation pass
|
|
68
|
+
When you want to review and consolidate inbox entries into long-term memory.
|
|
69
|
+
Tool: dream_consolidate({})
|
|
70
|
+
|
|
71
|
+
8. Read a live web page by URL
|
|
72
|
+
User: summarize https://example.com/docs
|
|
73
|
+
Assistant: load the web fetch tool and read the page directly
|
|
74
|
+
Tool: tool_search({"query":"web_fetch"})
|
|
75
|
+
Tool: web_fetch({"url":"https://example.com/docs"})
|
|
76
|
+
|
|
77
|
+
9. Search the web
|
|
78
|
+
User: search the web for latest pnpm release
|
|
79
|
+
Assistant: load the web search tool and run a targeted search
|
|
80
|
+
Tool: tool_search({"query":"web_search"})
|
|
81
|
+
Tool: web_search({"query":"latest pnpm release","max_results":5})
|
|
82
|
+
|
|
83
|
+
Prefer these direct tool shapes over multi-step metadata reads or shell fallbacks.
|
|
84
|
+
Prefer explicit absolute path values when the current working directory is known.`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getEnvBlock() {
|
|
88
|
+
const cwd = process.cwd();
|
|
89
|
+
let isGitRepo = false;
|
|
90
|
+
try {
|
|
91
|
+
fs.accessSync(`${cwd}/.git`);
|
|
92
|
+
isGitRepo = true;
|
|
93
|
+
} catch {}
|
|
94
|
+
|
|
95
|
+
return `<env>
|
|
96
|
+
Working directory: ${cwd}
|
|
97
|
+
Is directory a git repo: ${isGitRepo ? 'Yes' : 'No'}
|
|
98
|
+
Platform: ${process.platform}
|
|
99
|
+
Shell: ${os.userInfo().shell || 'unknown'}
|
|
100
|
+
OS Version: ${os.version || os.release()}
|
|
101
|
+
</env>`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizePromptBlocks(blocks) {
|
|
105
|
+
if (!blocks) return [];
|
|
106
|
+
if (Array.isArray(blocks)) return blocks.filter(Boolean).map(String);
|
|
107
|
+
return [String(blocks)].filter(Boolean);
|
|
85
108
|
}
|
|
86
109
|
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return `<env>
|
|
96
|
-
Working directory: ${cwd}
|
|
97
|
-
Is directory a git repo: ${isGitRepo ? 'Yes' : 'No'}
|
|
98
|
-
Platform: ${process.platform}
|
|
99
|
-
Shell: ${os.userInfo().shell || 'unknown'}
|
|
100
|
-
OS Version: ${os.version || os.release()}
|
|
101
|
-
</env>`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function buildDefaultSystemPrompt(config = {}) {
|
|
105
|
-
return `${getShellSystemPrompt(config?.shell?.default)}
|
|
106
|
-
|
|
107
|
-
${getToolFewShotBlock()}
|
|
108
|
-
|
|
109
|
-
${getEnvBlock()}`;
|
|
110
|
+
export function buildDefaultSystemPrompt(config = {}, options = {}) {
|
|
111
|
+
return [
|
|
112
|
+
getShellSystemPrompt(config?.shell?.default),
|
|
113
|
+
getToolFewShotBlock(),
|
|
114
|
+
getEnvBlock(),
|
|
115
|
+
...normalizePromptBlocks(options.extraPrompts)
|
|
116
|
+
].filter(Boolean).join('\n\n');
|
|
110
117
|
}
|
package/src/core/dream-audit.js
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { getDreamAuditDir } from './paths.js';
|
|
4
|
-
|
|
5
|
-
function nowStamp() {
|
|
6
|
-
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function renderReport(report) {
|
|
10
|
-
const lines = [
|
|
11
|
-
`# Dream Consolidation Report`,
|
|
12
|
-
`Date: ${report.timestamp}`,
|
|
13
|
-
``
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
if (report.filesRead?.length) {
|
|
17
|
-
lines.push('## Files Read');
|
|
18
|
-
for (const f of report.filesRead) lines.push(`- ${f}`);
|
|
19
|
-
lines.push('');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (report.candidatesGenerated) {
|
|
23
|
-
lines.push(`## Candidates Generated: ${report.candidatesGenerated}`);
|
|
24
|
-
lines.push('');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (report.promotions?.length) {
|
|
28
|
-
lines.push('## Promotions');
|
|
29
|
-
for (const p of report.promotions) {
|
|
30
|
-
lines.push(`- [${p.lifecycle}] ${p.summary} (scope: ${p.scope})`);
|
|
31
|
-
if (p.rationale) lines.push(` Rationale: ${p.rationale}`);
|
|
32
|
-
}
|
|
33
|
-
lines.push('');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (report.rejections?.length) {
|
|
37
|
-
lines.push('## Rejections');
|
|
38
|
-
for (const r of report.rejections) {
|
|
39
|
-
lines.push(`- ${r.summary}`);
|
|
40
|
-
if (r.reason) lines.push(` Reason: ${r.reason}`);
|
|
41
|
-
}
|
|
42
|
-
lines.push('');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (report.archives?.length) {
|
|
46
|
-
lines.push('## Archives');
|
|
47
|
-
for (const a of report.archives) {
|
|
48
|
-
lines.push(`- ${a.summary} (reason: ${a.reason || 'expired'})`);
|
|
49
|
-
}
|
|
50
|
-
lines.push('');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (report.filesChanged?.length) {
|
|
54
|
-
lines.push('## Files Changed');
|
|
55
|
-
for (const fc of report.filesChanged) lines.push(`- ${fc.file}: ${fc.why}`);
|
|
56
|
-
lines.push('');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (report.maintenance?.length) {
|
|
60
|
-
lines.push('## Memory Maintenance');
|
|
61
|
-
for (const item of report.maintenance) {
|
|
62
|
-
if (item.skipped) {
|
|
63
|
-
lines.push(`- [${item.scope}] skipped: ${item.reason}`);
|
|
64
|
-
} else {
|
|
65
|
-
lines.push(`- [${item.scope}] ${item.before} -> ${item.after} item(s)${item.changed ? ' changed' : ' marked clean'}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
lines.push('');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (report.disagreements?.length) {
|
|
72
|
-
lines.push('## Reviewer Disagreements');
|
|
73
|
-
for (const d of report.disagreements) {
|
|
74
|
-
lines.push(`- ${d.item}: main=${d.mainVerdict}, reviewer=${d.reviewerVerdict}, resolved=${d.resolution}`);
|
|
75
|
-
}
|
|
76
|
-
lines.push('');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return lines.join('\n');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export async function writeDreamAuditReport(report) {
|
|
83
|
-
const dir = getDreamAuditDir();
|
|
84
|
-
await fs.mkdir(dir, { recursive: true });
|
|
85
|
-
const stamp = nowStamp();
|
|
86
|
-
const filePath = path.join(dir, `dream-${stamp}.md`);
|
|
87
|
-
const content = renderReport(report);
|
|
88
|
-
await fs.writeFile(filePath, content, 'utf8');
|
|
89
|
-
return filePath;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export async function listDreamAuditReports() {
|
|
93
|
-
const dir = getDreamAuditDir();
|
|
94
|
-
let entries;
|
|
95
|
-
try {
|
|
96
|
-
entries = await fs.readdir(dir);
|
|
97
|
-
} catch {
|
|
98
|
-
return [];
|
|
99
|
-
}
|
|
100
|
-
return entries
|
|
101
|
-
.filter((e) => e.startsWith('dream-') && e.endsWith('.md'))
|
|
102
|
-
.sort()
|
|
103
|
-
.reverse()
|
|
104
|
-
.map((e) => path.join(dir, e));
|
|
105
|
-
}
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getDreamAuditDir } from './paths.js';
|
|
4
|
+
|
|
5
|
+
function nowStamp() {
|
|
6
|
+
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function renderReport(report) {
|
|
10
|
+
const lines = [
|
|
11
|
+
`# Dream Consolidation Report`,
|
|
12
|
+
`Date: ${report.timestamp}`,
|
|
13
|
+
``
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
if (report.filesRead?.length) {
|
|
17
|
+
lines.push('## Files Read');
|
|
18
|
+
for (const f of report.filesRead) lines.push(`- ${f}`);
|
|
19
|
+
lines.push('');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (report.candidatesGenerated) {
|
|
23
|
+
lines.push(`## Candidates Generated: ${report.candidatesGenerated}`);
|
|
24
|
+
lines.push('');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (report.promotions?.length) {
|
|
28
|
+
lines.push('## Promotions');
|
|
29
|
+
for (const p of report.promotions) {
|
|
30
|
+
lines.push(`- [${p.lifecycle}] ${p.summary} (scope: ${p.scope})`);
|
|
31
|
+
if (p.rationale) lines.push(` Rationale: ${p.rationale}`);
|
|
32
|
+
}
|
|
33
|
+
lines.push('');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (report.rejections?.length) {
|
|
37
|
+
lines.push('## Rejections');
|
|
38
|
+
for (const r of report.rejections) {
|
|
39
|
+
lines.push(`- ${r.summary}`);
|
|
40
|
+
if (r.reason) lines.push(` Reason: ${r.reason}`);
|
|
41
|
+
}
|
|
42
|
+
lines.push('');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (report.archives?.length) {
|
|
46
|
+
lines.push('## Archives');
|
|
47
|
+
for (const a of report.archives) {
|
|
48
|
+
lines.push(`- ${a.summary} (reason: ${a.reason || 'expired'})`);
|
|
49
|
+
}
|
|
50
|
+
lines.push('');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (report.filesChanged?.length) {
|
|
54
|
+
lines.push('## Files Changed');
|
|
55
|
+
for (const fc of report.filesChanged) lines.push(`- ${fc.file}: ${fc.why}`);
|
|
56
|
+
lines.push('');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (report.maintenance?.length) {
|
|
60
|
+
lines.push('## Memory Maintenance');
|
|
61
|
+
for (const item of report.maintenance) {
|
|
62
|
+
if (item.skipped) {
|
|
63
|
+
lines.push(`- [${item.scope}] skipped: ${item.reason}`);
|
|
64
|
+
} else {
|
|
65
|
+
lines.push(`- [${item.scope}] ${item.before} -> ${item.after} item(s)${item.changed ? ' changed' : ' marked clean'}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
lines.push('');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (report.disagreements?.length) {
|
|
72
|
+
lines.push('## Reviewer Disagreements');
|
|
73
|
+
for (const d of report.disagreements) {
|
|
74
|
+
lines.push(`- ${d.item}: main=${d.mainVerdict}, reviewer=${d.reviewerVerdict}, resolved=${d.resolution}`);
|
|
75
|
+
}
|
|
76
|
+
lines.push('');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return lines.join('\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function writeDreamAuditReport(report) {
|
|
83
|
+
const dir = getDreamAuditDir();
|
|
84
|
+
await fs.mkdir(dir, { recursive: true });
|
|
85
|
+
const stamp = nowStamp();
|
|
86
|
+
const filePath = path.join(dir, `dream-${stamp}.md`);
|
|
87
|
+
const content = renderReport(report);
|
|
88
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
89
|
+
return filePath;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function listDreamAuditReports() {
|
|
93
|
+
const dir = getDreamAuditDir();
|
|
94
|
+
let entries;
|
|
95
|
+
try {
|
|
96
|
+
entries = await fs.readdir(dir);
|
|
97
|
+
} catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
return entries
|
|
101
|
+
.filter((e) => e.startsWith('dream-') && e.endsWith('.md'))
|
|
102
|
+
.sort()
|
|
103
|
+
.reverse()
|
|
104
|
+
.map((e) => path.join(dir, e));
|
|
105
|
+
}
|