@selvakumaresra/specship 0.7.0 → 0.10.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/commands/ss-domain.md +98 -0
- package/commands/ss-triage.md +177 -0
- package/dist/bin/specship.js +308 -0
- package/dist/bin/specship.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +28 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/schema.sql +26 -2
- package/dist/db/spec-queries.d.ts +29 -0
- package/dist/db/spec-queries.d.ts.map +1 -1
- package/dist/db/spec-queries.js +63 -0
- package/dist/db/spec-queries.js.map +1 -1
- package/dist/enforce/enforce.d.ts +70 -0
- package/dist/enforce/enforce.d.ts.map +1 -0
- package/dist/enforce/enforce.js +125 -0
- package/dist/enforce/enforce.js.map +1 -0
- package/dist/extraction/specs/markdown-spec-extractor.d.ts +21 -0
- package/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -1
- package/dist/extraction/specs/markdown-spec-extractor.js +144 -0
- package/dist/extraction/specs/markdown-spec-extractor.js.map +1 -1
- package/dist/fitness/fitness.d.ts +75 -0
- package/dist/fitness/fitness.d.ts.map +1 -0
- package/dist/fitness/fitness.js +204 -0
- package/dist/fitness/fitness.js.map +1 -0
- package/dist/graph/maintainability.d.ts +101 -0
- package/dist/graph/maintainability.d.ts.map +1 -0
- package/dist/graph/maintainability.js +278 -0
- package/dist/graph/maintainability.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +149 -1
- package/dist/index.js.map +1 -1
- package/dist/installer/instructions-template.d.ts +5 -4
- package/dist/installer/instructions-template.d.ts.map +1 -1
- package/dist/installer/instructions-template.js +10 -4
- package/dist/installer/instructions-template.js.map +1 -1
- package/dist/installer/targets/claude.d.ts.map +1 -1
- package/dist/installer/targets/claude.js +4 -0
- package/dist/installer/targets/claude.js.map +1 -1
- package/dist/installer/targets/shared.d.ts.map +1 -1
- package/dist/installer/targets/shared.js +4 -0
- package/dist/installer/targets/shared.js.map +1 -1
- package/dist/mcp/fitness-tool.d.ts +12 -0
- package/dist/mcp/fitness-tool.d.ts.map +1 -0
- package/dist/mcp/fitness-tool.js +46 -0
- package/dist/mcp/fitness-tool.js.map +1 -0
- package/dist/mcp/maintainability-tool.d.ts +13 -0
- package/dist/mcp/maintainability-tool.d.ts.map +1 -0
- package/dist/mcp/maintainability-tool.js +64 -0
- package/dist/mcp/maintainability-tool.js.map +1 -0
- package/dist/mcp/server-instructions.d.ts +1 -1
- package/dist/mcp/server-instructions.d.ts.map +1 -1
- package/dist/mcp/server-instructions.js +3 -2
- package/dist/mcp/server-instructions.js.map +1 -1
- package/dist/mcp/spec-tools.d.ts.map +1 -1
- package/dist/mcp/spec-tools.js +115 -1
- package/dist/mcp/spec-tools.js.map +1 -1
- package/dist/mcp/tools.d.ts +13 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +75 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/reflect/apply.d.ts +31 -0
- package/dist/reflect/apply.d.ts.map +1 -0
- package/dist/reflect/apply.js +286 -0
- package/dist/reflect/apply.js.map +1 -0
- package/dist/reflect/hash.d.ts +20 -0
- package/dist/reflect/hash.d.ts.map +1 -0
- package/dist/reflect/hash.js +36 -0
- package/dist/reflect/hash.js.map +1 -0
- package/dist/reflect/index.d.ts +16 -0
- package/dist/reflect/index.d.ts.map +1 -0
- package/dist/reflect/index.js +43 -0
- package/dist/reflect/index.js.map +1 -0
- package/dist/reflect/miner.d.ts +21 -0
- package/dist/reflect/miner.d.ts.map +1 -0
- package/dist/reflect/miner.js +463 -0
- package/dist/reflect/miner.js.map +1 -0
- package/dist/reflect/store.d.ts +31 -0
- package/dist/reflect/store.d.ts.map +1 -0
- package/dist/reflect/store.js +101 -0
- package/dist/reflect/store.js.map +1 -0
- package/dist/reflect/sweep.d.ts +26 -0
- package/dist/reflect/sweep.d.ts.map +1 -0
- package/dist/reflect/sweep.js +42 -0
- package/dist/reflect/sweep.js.map +1 -0
- package/dist/reflect/targets.d.ts +52 -0
- package/dist/reflect/targets.d.ts.map +1 -0
- package/dist/reflect/targets.js +192 -0
- package/dist/reflect/targets.js.map +1 -0
- package/dist/reflect/types.d.ts +96 -0
- package/dist/reflect/types.d.ts.map +1 -0
- package/dist/reflect/types.js +13 -0
- package/dist/reflect/types.js.map +1 -0
- package/dist/resolution/domain-gap-seed.d.ts +60 -0
- package/dist/resolution/domain-gap-seed.d.ts.map +1 -0
- package/dist/resolution/domain-gap-seed.js +87 -0
- package/dist/resolution/domain-gap-seed.js.map +1 -0
- package/dist/resolution/spec-link-resolver.d.ts +46 -1
- package/dist/resolution/spec-link-resolver.d.ts.map +1 -1
- package/dist/resolution/spec-link-resolver.js +78 -0
- package/dist/resolution/spec-link-resolver.js.map +1 -1
- package/dist/server/routes/domain.js +0 -0
- package/dist/server/routes/events.js +20 -0
- package/dist/server/routes/maintainability.js +18 -0
- package/dist/server/routes/reflect.js +93 -0
- package/dist/server/server.js +6 -0
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -1
- package/dist/web/chunk-EZZBWC7Z.js +1 -0
- package/dist/web/chunk-ITHLF4GI.js +1 -0
- package/dist/web/{chunk-UBOZGQNK.js → chunk-IZQXYQNQ.js} +1 -1
- package/dist/web/chunk-JBO7ZIPO.js +1 -0
- package/dist/web/chunk-JQXCRIK2.js +1 -0
- package/dist/web/{chunk-ASZ77FMZ.js → chunk-JT2YIHPL.js} +1 -1
- package/dist/web/{chunk-WLIMNDS3.js → chunk-ONKHTXHJ.js} +1 -1
- package/dist/web/{chunk-FHZHD2ZG.js → chunk-P5JX67LT.js} +1 -1
- package/dist/web/chunk-PDTQX2UL.js +6 -0
- package/dist/web/chunk-TPDB5GJN.js +1 -0
- package/dist/web/{chunk-RASJHUXS.js → chunk-XWDR6MPC.js} +1 -1
- package/dist/web/chunk-Y6WWDS4R.js +1 -0
- package/dist/web/chunk-Z5L3T5EO.js +1 -0
- package/dist/web/{chunk-D5OCNEJA.js → chunk-ZG7H3XPL.js} +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/main-GYPY5V5H.js +1 -0
- package/dist/workflows/executor.d.ts.map +1 -1
- package/dist/workflows/executor.js +3 -0
- package/dist/workflows/executor.js.map +1 -1
- package/dist/workflows/runners/prompt.d.ts +31 -8
- package/dist/workflows/runners/prompt.d.ts.map +1 -1
- package/dist/workflows/runners/prompt.js +117 -23
- package/dist/workflows/runners/prompt.js.map +1 -1
- package/dist/workflows/runners/types.d.ts +7 -1
- package/dist/workflows/runners/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/web/chunk-HGBF7AY5.js +0 -1
- package/dist/web/chunk-JQ534IB6.js +0 -6
- package/dist/web/chunk-MVOMVPYB.js +0 -1
- package/dist/web/chunk-NZEZCT65.js +0 -1
- package/dist/web/chunk-WQWYTRFN.js +0 -1
- package/dist/web/main-LHCYPOXF.js +0 -1
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Proposal miner (REQ-REFLECT-001 / 002).
|
|
4
|
+
*
|
|
5
|
+
* Reads the ingested `claude_*` transcript tables and detects recurring,
|
|
6
|
+
* actionable patterns, emitting one typed Proposal per finding with cited
|
|
7
|
+
* evidence and a severity. Each rule is a bounded SQL query (mirroring the tips
|
|
8
|
+
* engine's shape) feeding `buildProposal`, which resolves the concrete write
|
|
9
|
+
* target and the stable content hash.
|
|
10
|
+
*
|
|
11
|
+
* The *set* of rules is the implementation frontier the spec deliberately left
|
|
12
|
+
* open — new detectors slot in here without touching apply/store/surface.
|
|
13
|
+
*
|
|
14
|
+
* When the transcript tables are absent (a headless `specship reflect` against a
|
|
15
|
+
* project the dashboard has never ingested), the miner returns `[]` so the
|
|
16
|
+
* caller can render the empty state rather than erroring (REQ-REFLECT-001.A2).
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.mineProposals = mineProposals;
|
|
20
|
+
const targets_1 = require("./targets");
|
|
21
|
+
/** Tuning knobs — thresholds above which a pattern is worth proposing. */
|
|
22
|
+
const THRESHOLDS = {
|
|
23
|
+
repeatedReadsPerSession: 10, // R1: same file Read ≥N times in one session
|
|
24
|
+
grepHabitTotal: 12, // R2: grep/find used ≥N times overall
|
|
25
|
+
repeatedPromptCount: 3, // R3: identical ask appears ≥N times
|
|
26
|
+
repeatedCmdSessions: 2, // R4: same bash command in ≥N sessions
|
|
27
|
+
repeatedCmdTotal: 5, // R4: …and ≥N times overall
|
|
28
|
+
destructiveMin: 2, // R5: a destructive shell op seen ≥N times
|
|
29
|
+
editHotspotTotal: 8, // R6: same file Edited ≥N times overall
|
|
30
|
+
editHotspotSessions: 2, // R6: …across ≥N sessions
|
|
31
|
+
correctionMin: 2, // R7: same corrective instruction repeated ≥N times
|
|
32
|
+
specshipColdReadMin: 15, // R8: reads/greps in a session to count as read-heavy
|
|
33
|
+
specshipColdSessions: 2, // R8: ≥N read-heavy sessions with zero specship calls
|
|
34
|
+
heavyOutputBytes: 50000, // R9: a Bash call whose result exceeds N "tokens"
|
|
35
|
+
heavyOutputMin: 2, // R9: …seen ≥N times
|
|
36
|
+
refDocSessions: 3, // R10: a doc file Read across ≥N distinct sessions
|
|
37
|
+
};
|
|
38
|
+
/** Per-rule cap so a noisy corpus can't flood the Improvements list. */
|
|
39
|
+
const PER_RULE_LIMIT = 5;
|
|
40
|
+
function tableExists(db, name) {
|
|
41
|
+
try {
|
|
42
|
+
const row = db
|
|
43
|
+
.prepare(`SELECT 1 AS x FROM sqlite_master WHERE type='table' AND name=?`)
|
|
44
|
+
.get(name);
|
|
45
|
+
return !!row;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Mine all rules. Pure read — never writes the DB or disk. */
|
|
52
|
+
function mineProposals(db, ctx) {
|
|
53
|
+
if (!tableExists(db, 'claude_tool_calls') || !tableExists(db, 'claude_prompts')) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const out = [];
|
|
57
|
+
out.push(...ruleRepeatedReads(db, ctx));
|
|
58
|
+
out.push(...ruleGrepHabit(db, ctx));
|
|
59
|
+
out.push(...ruleRepeatedPrompts(db, ctx));
|
|
60
|
+
out.push(...ruleRepeatedCommands(db, ctx));
|
|
61
|
+
// Round 2 detectors.
|
|
62
|
+
out.push(...ruleDestructiveCommands(db, ctx));
|
|
63
|
+
out.push(...ruleEditHotspot(db, ctx));
|
|
64
|
+
out.push(...ruleRecurringCorrection(db, ctx));
|
|
65
|
+
// Round 3 detectors.
|
|
66
|
+
out.push(...ruleSpecshipCold(db, ctx));
|
|
67
|
+
out.push(...ruleHeavyOutput(db, ctx));
|
|
68
|
+
out.push(...ruleReferenceDoc(db, ctx));
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* R1 → memory_rule (project CLAUDE.md): the agent Read the same file many times
|
|
73
|
+
* in a single session. A project-specific rule steering it to specship_explore
|
|
74
|
+
* for that area is the durable fix.
|
|
75
|
+
*/
|
|
76
|
+
function ruleRepeatedReads(db, ctx) {
|
|
77
|
+
const rows = db
|
|
78
|
+
.prepare(`SELECT session_id, input_summary AS file, COUNT(*) AS n
|
|
79
|
+
FROM claude_tool_calls
|
|
80
|
+
WHERE tool_name = 'Read' AND input_summary != ''
|
|
81
|
+
GROUP BY session_id, input_summary
|
|
82
|
+
HAVING n >= ?
|
|
83
|
+
ORDER BY n DESC
|
|
84
|
+
LIMIT ?`)
|
|
85
|
+
.all(THRESHOLDS.repeatedReadsPerSession, PER_RULE_LIMIT);
|
|
86
|
+
return rows.map((r) => {
|
|
87
|
+
const base = r.file.split('/').pop() || r.file;
|
|
88
|
+
return (0, targets_1.buildProposal)(ctx, {
|
|
89
|
+
type: 'memory_rule',
|
|
90
|
+
scope: 'project',
|
|
91
|
+
severity: 'high',
|
|
92
|
+
nameSeed: base,
|
|
93
|
+
title: `Prefer specship_explore over re-reading ${base}`,
|
|
94
|
+
body: `${base} was Read ${r.n} times in a single session. A structural specship_explore query returns its callers, callees, and linked specs in one call — cheaper and complete.`,
|
|
95
|
+
content: `When you need to understand \`${base}\` (or the area around it), call \`specship_explore\` with the relevant symbol names first and treat the returned source as already Read — do not re-Read the file repeatedly.`,
|
|
96
|
+
evidence: {
|
|
97
|
+
sessions: [r.session_id],
|
|
98
|
+
prompts: [],
|
|
99
|
+
detail: `Read(${r.file}) × ${r.n} in one session`,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* R2 → memory_rule (portable ~/.claude/memory): heavy grep/find usage is a
|
|
106
|
+
* cross-project habit, so the learning is portable, not repo-specific.
|
|
107
|
+
*/
|
|
108
|
+
function ruleGrepHabit(db, ctx) {
|
|
109
|
+
const row = db
|
|
110
|
+
.prepare(`SELECT COUNT(*) AS n, COUNT(DISTINCT session_id) AS s
|
|
111
|
+
FROM claude_tool_calls
|
|
112
|
+
WHERE tool_name = 'Bash'
|
|
113
|
+
AND (input_summary LIKE 'grep%' OR input_summary LIKE '%grep %'
|
|
114
|
+
OR input_summary LIKE 'find %' OR input_summary LIKE 'rg %')`)
|
|
115
|
+
.get();
|
|
116
|
+
if (!row || row.n < THRESHOLDS.grepHabitTotal)
|
|
117
|
+
return [];
|
|
118
|
+
const sessRows = db
|
|
119
|
+
.prepare(`SELECT DISTINCT session_id FROM claude_tool_calls
|
|
120
|
+
WHERE tool_name = 'Bash' AND (input_summary LIKE '%grep %' OR input_summary LIKE 'grep%'
|
|
121
|
+
OR input_summary LIKE 'find %' OR input_summary LIKE 'rg %')
|
|
122
|
+
LIMIT 8`)
|
|
123
|
+
.all();
|
|
124
|
+
return [
|
|
125
|
+
(0, targets_1.buildProposal)(ctx, {
|
|
126
|
+
type: 'memory_rule',
|
|
127
|
+
scope: 'portable',
|
|
128
|
+
severity: 'warn',
|
|
129
|
+
nameSeed: 'prefer-specship-search-over-grep',
|
|
130
|
+
title: 'Prefer specship_search over grep/find',
|
|
131
|
+
body: `grep/find ran ${row.n} times across ${row.s} sessions. specship_search hits the pre-built FTS index and returns symbol locations directly — usually one call instead of a grep+read loop.`,
|
|
132
|
+
content: 'For "where is X" / "find the symbol named X" questions, reach for `specship_search` (or `specship_explore` for "how does X work") before shelling out to grep/find/rg — SpecShip is the pre-built search index, so a grep+read loop repeats work it already did.',
|
|
133
|
+
evidence: {
|
|
134
|
+
sessions: sessRows.map((s) => s.session_id),
|
|
135
|
+
prompts: [],
|
|
136
|
+
detail: `grep/find/rg × ${row.n} across ${row.s} sessions`,
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
/** Normalize a prompt for grouping: trim, lowercase, collapse whitespace. */
|
|
142
|
+
function normalizePrompt(t) {
|
|
143
|
+
return t.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* R3 → skill (commands/ss-<name>.md): the user typed essentially the same ask
|
|
147
|
+
* many times. A reusable slash command captures the routine.
|
|
148
|
+
*/
|
|
149
|
+
function ruleRepeatedPrompts(db, ctx) {
|
|
150
|
+
const rows = db
|
|
151
|
+
.prepare(`SELECT id, text FROM claude_prompts
|
|
152
|
+
WHERE text IS NOT NULL AND length(text) BETWEEN 12 AND 200
|
|
153
|
+
AND text NOT LIKE '<%' AND text NOT LIKE '%<command-name>%'
|
|
154
|
+
AND is_sidechain = 0
|
|
155
|
+
ORDER BY ts DESC
|
|
156
|
+
LIMIT 4000`)
|
|
157
|
+
.all();
|
|
158
|
+
const groups = new Map();
|
|
159
|
+
for (const r of rows) {
|
|
160
|
+
const key = normalizePrompt(r.text);
|
|
161
|
+
if (!key)
|
|
162
|
+
continue;
|
|
163
|
+
const g = groups.get(key) ?? { count: 0, prompts: [], sample: r.text.trim() };
|
|
164
|
+
g.count++;
|
|
165
|
+
if (g.prompts.length < 8)
|
|
166
|
+
g.prompts.push(r.id);
|
|
167
|
+
groups.set(key, g);
|
|
168
|
+
}
|
|
169
|
+
const repeated = [...groups.values()]
|
|
170
|
+
.filter((g) => g.count >= THRESHOLDS.repeatedPromptCount)
|
|
171
|
+
.sort((a, b) => b.count - a.count)
|
|
172
|
+
.slice(0, PER_RULE_LIMIT);
|
|
173
|
+
return repeated.map((g) => (0, targets_1.buildProposal)(ctx, {
|
|
174
|
+
type: 'skill',
|
|
175
|
+
severity: 'warn',
|
|
176
|
+
nameSeed: g.sample,
|
|
177
|
+
title: `Capture "${truncate(g.sample, 48)}" as a command`,
|
|
178
|
+
body: `This ask appeared ${g.count} times. A slash command turns the repeated instruction into one reusable invocation.`,
|
|
179
|
+
content: `Run the following routine:\n\n${g.sample}`,
|
|
180
|
+
evidence: {
|
|
181
|
+
sessions: [],
|
|
182
|
+
prompts: g.prompts,
|
|
183
|
+
detail: `Same ask repeated ${g.count}×`,
|
|
184
|
+
},
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* R4 → hook (.claude/settings.json): the agent ran the same shell command after
|
|
189
|
+
* edits across multiple sessions. A PostToolUse hook automates the manual step.
|
|
190
|
+
*/
|
|
191
|
+
function ruleRepeatedCommands(db, ctx) {
|
|
192
|
+
const rows = db
|
|
193
|
+
.prepare(`SELECT input_summary AS cmd, COUNT(*) AS n, COUNT(DISTINCT session_id) AS s,
|
|
194
|
+
GROUP_CONCAT(DISTINCT session_id) AS sessions
|
|
195
|
+
FROM claude_tool_calls
|
|
196
|
+
WHERE tool_name = 'Bash' AND input_summary != ''
|
|
197
|
+
AND input_summary NOT LIKE 'grep%' AND input_summary NOT LIKE '%grep %'
|
|
198
|
+
AND input_summary NOT LIKE 'find %' AND input_summary NOT LIKE 'cd %'
|
|
199
|
+
AND input_summary NOT LIKE 'ls%' AND input_summary NOT LIKE 'cat %'
|
|
200
|
+
GROUP BY input_summary
|
|
201
|
+
HAVING s >= ? AND n >= ?
|
|
202
|
+
ORDER BY n DESC
|
|
203
|
+
LIMIT ?`)
|
|
204
|
+
.all(THRESHOLDS.repeatedCmdSessions, THRESHOLDS.repeatedCmdTotal, PER_RULE_LIMIT);
|
|
205
|
+
return rows.map((r) => (0, targets_1.buildProposal)(ctx, {
|
|
206
|
+
type: 'hook',
|
|
207
|
+
severity: 'info',
|
|
208
|
+
nameSeed: r.cmd,
|
|
209
|
+
title: `Automate \`${truncate(r.cmd, 40)}\` with a hook`,
|
|
210
|
+
body: `\`${truncate(r.cmd, 80)}\` was run ${r.n} times across ${r.s} sessions, mostly by hand. A PostToolUse hook can run it automatically after edits.`,
|
|
211
|
+
content: r.cmd,
|
|
212
|
+
hook: { event: 'PostToolUse', matcher: 'Edit|Write', command: r.cmd },
|
|
213
|
+
evidence: {
|
|
214
|
+
sessions: (r.sessions || '').split(',').filter(Boolean).slice(0, 8),
|
|
215
|
+
prompts: [],
|
|
216
|
+
detail: `\`${truncate(r.cmd, 60)}\` × ${r.n} across ${r.s} sessions`,
|
|
217
|
+
},
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
/** Destructive shell-operation categories the agent should treat with care. */
|
|
221
|
+
const DESTRUCTIVE = [
|
|
222
|
+
{ key: 'rm-rf', label: 'recursive force-delete (`rm -rf`)', re: /\brm\s+-[a-z]*r[a-z]*f[a-z]*\b|\brm\s+-[a-z]*f[a-z]*r[a-z]*\b/i },
|
|
223
|
+
{ key: 'git-force-push', label: 'force push (`git push --force`)', re: /\bgit\s+push\b[^|;&]*(--force\b|--force-with-lease\b|\s-f\b)/i },
|
|
224
|
+
{ key: 'git-reset-hard', label: 'hard reset (`git reset --hard`)', re: /\bgit\s+reset\b[^|;&]*--hard\b/i },
|
|
225
|
+
{ key: 'git-clean', label: 'force clean (`git clean -fd`)', re: /\bgit\s+clean\b[^|;&]*-[a-z]*f/i },
|
|
226
|
+
{ key: 'drop-db', label: 'dropping a table/database (`DROP …`)', re: /\bdrop\s+(table|database|schema)\b/i },
|
|
227
|
+
];
|
|
228
|
+
/**
|
|
229
|
+
* R5 → memory_rule (project CLAUDE.md): destructive shell operations showed up
|
|
230
|
+
* in the transcripts. A high-severity caution rule is the durable response — we
|
|
231
|
+
* deliberately propose a *rule* rather than auto-generating a fragile guard
|
|
232
|
+
* script (a guard-hook variant is a future detector).
|
|
233
|
+
*/
|
|
234
|
+
function ruleDestructiveCommands(db, ctx) {
|
|
235
|
+
const rows = db
|
|
236
|
+
.prepare(`SELECT session_id, input_summary AS cmd FROM claude_tool_calls WHERE tool_name = 'Bash' AND input_summary != ''`)
|
|
237
|
+
.all();
|
|
238
|
+
const buckets = new Map();
|
|
239
|
+
for (const r of rows) {
|
|
240
|
+
for (const d of DESTRUCTIVE) {
|
|
241
|
+
if (d.re.test(r.cmd)) {
|
|
242
|
+
const b = buckets.get(d.key) ?? { count: 0, sessions: new Set(), sample: r.cmd };
|
|
243
|
+
b.count++;
|
|
244
|
+
b.sessions.add(r.session_id);
|
|
245
|
+
buckets.set(d.key, b);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const out = [];
|
|
250
|
+
for (const d of DESTRUCTIVE) {
|
|
251
|
+
const b = buckets.get(d.key);
|
|
252
|
+
if (!b || b.count < THRESHOLDS.destructiveMin)
|
|
253
|
+
continue;
|
|
254
|
+
out.push((0, targets_1.buildProposal)(ctx, {
|
|
255
|
+
type: 'memory_rule',
|
|
256
|
+
scope: 'project',
|
|
257
|
+
severity: 'high',
|
|
258
|
+
nameSeed: `caution-${d.key}`,
|
|
259
|
+
title: `Treat ${d.label} with care`,
|
|
260
|
+
body: `${d.label} ran ${b.count} times across ${b.sessions.size} session(s). A standing caution keeps an irreversible operation from being run without a beat of confirmation.`,
|
|
261
|
+
content: `Before running ${d.label}, pause and confirm the target is correct — these operations are irreversible. Prefer a dry-run or a narrower, reversible alternative when one exists.`,
|
|
262
|
+
evidence: {
|
|
263
|
+
sessions: [...b.sessions].slice(0, 8),
|
|
264
|
+
prompts: [],
|
|
265
|
+
detail: `${d.label} × ${b.count} across ${b.sessions.size} session(s)`,
|
|
266
|
+
},
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* R6 → memory_rule (project CLAUDE.md): a file is edited over and over across
|
|
273
|
+
* sessions — a hotspot whose contract is worth documenting so future edits
|
|
274
|
+
* don't relearn it from scratch.
|
|
275
|
+
*/
|
|
276
|
+
function ruleEditHotspot(db, ctx) {
|
|
277
|
+
const rows = db
|
|
278
|
+
.prepare(`SELECT input_summary AS file, COUNT(*) AS n, COUNT(DISTINCT session_id) AS s,
|
|
279
|
+
GROUP_CONCAT(DISTINCT session_id) AS sessions
|
|
280
|
+
FROM claude_tool_calls
|
|
281
|
+
WHERE tool_name IN ('Edit', 'Write', 'MultiEdit') AND input_summary != ''
|
|
282
|
+
GROUP BY input_summary
|
|
283
|
+
HAVING n >= ? AND s >= ?
|
|
284
|
+
ORDER BY n DESC
|
|
285
|
+
LIMIT ?`)
|
|
286
|
+
.all(THRESHOLDS.editHotspotTotal, THRESHOLDS.editHotspotSessions, PER_RULE_LIMIT);
|
|
287
|
+
return rows.map((r) => {
|
|
288
|
+
const base = r.file.split('/').pop() || r.file;
|
|
289
|
+
return (0, targets_1.buildProposal)(ctx, {
|
|
290
|
+
type: 'memory_rule',
|
|
291
|
+
scope: 'project',
|
|
292
|
+
severity: 'warn',
|
|
293
|
+
nameSeed: `hotspot-${base}`,
|
|
294
|
+
title: `Document the contract of ${base} — it changes often`,
|
|
295
|
+
body: `${base} was edited ${r.n} times across ${r.s} sessions. Capturing its invariants (and what must stay true after a change) saves rediscovering them on every edit.`,
|
|
296
|
+
content: `\`${base}\` is a frequently-edited hotspot. Before changing it, check \`specship_explore\` for its callers and document the invariants a change must preserve. Consider whether it needs tighter test coverage.`,
|
|
297
|
+
evidence: {
|
|
298
|
+
sessions: (r.sessions || '').split(',').filter(Boolean).slice(0, 8),
|
|
299
|
+
prompts: [],
|
|
300
|
+
detail: `${base} edited × ${r.n} across ${r.s} sessions`,
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/** Leading cues that mark a prompt as a correction of the agent's behavior. */
|
|
306
|
+
const CORRECTION_CUE = /^(no\b|don'?t\b|do not\b|never\b|always\b|stop\b|instead\b|actually\b|you should\b|please don'?t\b)/i;
|
|
307
|
+
/**
|
|
308
|
+
* R7 → memory_rule (portable ~/.claude/memory): the user keeps issuing the same
|
|
309
|
+
* corrective instruction. That's a durable preference worth recording so it
|
|
310
|
+
* doesn't have to be repeated — the core Hermes "learn from corrections" idea.
|
|
311
|
+
*/
|
|
312
|
+
function ruleRecurringCorrection(db, ctx) {
|
|
313
|
+
const rows = db
|
|
314
|
+
.prepare(`SELECT id, text FROM claude_prompts
|
|
315
|
+
WHERE text IS NOT NULL AND length(text) BETWEEN 12 AND 200
|
|
316
|
+
AND text NOT LIKE '<%' AND text NOT LIKE '%<command-name>%'
|
|
317
|
+
AND is_sidechain = 0
|
|
318
|
+
ORDER BY ts DESC
|
|
319
|
+
LIMIT 4000`)
|
|
320
|
+
.all();
|
|
321
|
+
const groups = new Map();
|
|
322
|
+
for (const r of rows) {
|
|
323
|
+
const trimmed = r.text.trim();
|
|
324
|
+
if (!CORRECTION_CUE.test(trimmed))
|
|
325
|
+
continue;
|
|
326
|
+
const key = trimmed.replace(/\s+/g, ' ').toLowerCase();
|
|
327
|
+
const g = groups.get(key) ?? { count: 0, prompts: [], sample: trimmed };
|
|
328
|
+
g.count++;
|
|
329
|
+
if (g.prompts.length < 8)
|
|
330
|
+
g.prompts.push(r.id);
|
|
331
|
+
groups.set(key, g);
|
|
332
|
+
}
|
|
333
|
+
const repeated = [...groups.values()]
|
|
334
|
+
.filter((g) => g.count >= THRESHOLDS.correctionMin)
|
|
335
|
+
.sort((a, b) => b.count - a.count)
|
|
336
|
+
.slice(0, PER_RULE_LIMIT);
|
|
337
|
+
return repeated.map((g) => (0, targets_1.buildProposal)(ctx, {
|
|
338
|
+
type: 'memory_rule',
|
|
339
|
+
scope: 'portable',
|
|
340
|
+
severity: 'warn',
|
|
341
|
+
nameSeed: `correction-${g.sample}`,
|
|
342
|
+
title: `Recurring correction: "${truncate(g.sample, 48)}"`,
|
|
343
|
+
body: `You've given this correction ${g.count} times. Recording it as a durable preference means you shouldn't have to repeat it.`,
|
|
344
|
+
content: `Standing user preference (you have repeated this): ${g.sample}`,
|
|
345
|
+
evidence: {
|
|
346
|
+
sessions: [],
|
|
347
|
+
prompts: g.prompts,
|
|
348
|
+
detail: `Correction repeated ${g.count}×`,
|
|
349
|
+
},
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* R8 → memory_rule (portable ~/.claude/memory): sessions that lean hard on
|
|
354
|
+
* Read/grep while making zero specship calls. SpecShip is indexed here, so a
|
|
355
|
+
* portable habit-note to query it first is the durable fix. Uses the
|
|
356
|
+
* `is_specship` classification on tool calls.
|
|
357
|
+
*/
|
|
358
|
+
function ruleSpecshipCold(db, ctx) {
|
|
359
|
+
const rows = db
|
|
360
|
+
.prepare(`SELECT session_id,
|
|
361
|
+
SUM(CASE WHEN tool_name IN ('Read','Grep')
|
|
362
|
+
OR (tool_name='Bash' AND (input_summary LIKE 'grep%' OR input_summary LIKE '%grep %'
|
|
363
|
+
OR input_summary LIKE 'find %' OR input_summary LIKE 'rg %'))
|
|
364
|
+
THEN 1 ELSE 0 END) AS reads,
|
|
365
|
+
SUM(is_specship) AS ss
|
|
366
|
+
FROM claude_tool_calls
|
|
367
|
+
GROUP BY session_id
|
|
368
|
+
HAVING reads >= ? AND ss = 0`)
|
|
369
|
+
.all(THRESHOLDS.specshipColdReadMin);
|
|
370
|
+
if (rows.length < THRESHOLDS.specshipColdSessions)
|
|
371
|
+
return [];
|
|
372
|
+
const totalReads = rows.reduce((a, r) => a + r.reads, 0);
|
|
373
|
+
return [
|
|
374
|
+
(0, targets_1.buildProposal)(ctx, {
|
|
375
|
+
type: 'memory_rule',
|
|
376
|
+
scope: 'portable',
|
|
377
|
+
severity: 'warn',
|
|
378
|
+
nameSeed: 'query-specship-first',
|
|
379
|
+
title: 'Query SpecShip before reading or grepping',
|
|
380
|
+
body: `${rows.length} read-heavy sessions (${totalReads} Read/grep calls) made no specship calls at all. SpecShip is the pre-built index for this code — a structural query usually answers in one call what a grep+read loop takes many.`,
|
|
381
|
+
content: 'This project is indexed by SpecShip. For "how does X work" / "where is X" / trace / impact questions, call `specship_explore` (or `specship_search`) FIRST and treat the returned source as already Read — only fall back to Read/grep to confirm a detail it did not cover.',
|
|
382
|
+
evidence: {
|
|
383
|
+
sessions: rows.map((r) => r.session_id).slice(0, 8),
|
|
384
|
+
prompts: [],
|
|
385
|
+
detail: `${rows.length} read-heavy sessions with 0 specship calls`,
|
|
386
|
+
},
|
|
387
|
+
}),
|
|
388
|
+
];
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* R9 → memory_rule (project CLAUDE.md): a Bash command that dumps a huge result
|
|
392
|
+
* into context, repeatedly. Scoping its output (or using a structural query) is
|
|
393
|
+
* the durable fix. Uses `result_length`.
|
|
394
|
+
*/
|
|
395
|
+
function ruleHeavyOutput(db, ctx) {
|
|
396
|
+
const rows = db
|
|
397
|
+
.prepare(`SELECT input_summary AS cmd, COUNT(*) AS n, MAX(result_length) AS maxlen,
|
|
398
|
+
GROUP_CONCAT(DISTINCT session_id) AS sessions
|
|
399
|
+
FROM claude_tool_calls
|
|
400
|
+
WHERE tool_name = 'Bash' AND input_summary != '' AND result_length > ?
|
|
401
|
+
GROUP BY input_summary
|
|
402
|
+
HAVING n >= ?
|
|
403
|
+
ORDER BY maxlen DESC
|
|
404
|
+
LIMIT ?`)
|
|
405
|
+
.all(THRESHOLDS.heavyOutputBytes, THRESHOLDS.heavyOutputMin, PER_RULE_LIMIT);
|
|
406
|
+
return rows.map((r) => (0, targets_1.buildProposal)(ctx, {
|
|
407
|
+
type: 'memory_rule',
|
|
408
|
+
scope: 'project',
|
|
409
|
+
severity: 'warn',
|
|
410
|
+
nameSeed: `heavy-output-${r.cmd}`,
|
|
411
|
+
title: `Scope the output of \`${truncate(r.cmd, 40)}\``,
|
|
412
|
+
body: `\`${truncate(r.cmd, 80)}\` returned up to ~${Math.round(r.maxlen / 1000)}k tokens and ran ${r.n} times. Dumping large output into context is a dominant cost driver.`,
|
|
413
|
+
content: `When you need the result of \`${truncate(r.cmd, 80)}\`, scope it (filter, head, or target a path) — or answer the underlying question with a \`specship_search\`/\`specship_explore\` query instead of reading the full output into context.`,
|
|
414
|
+
evidence: {
|
|
415
|
+
sessions: (r.sessions || '').split(',').filter(Boolean).slice(0, 8),
|
|
416
|
+
prompts: [],
|
|
417
|
+
detail: `~${Math.round(r.maxlen / 1000)}k-token output × ${r.n}`,
|
|
418
|
+
},
|
|
419
|
+
}));
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* R10 → memory_rule (project CLAUDE.md): a documentation file read across many
|
|
423
|
+
* separate sessions is context the agent keeps rediscovering. Pointing at it
|
|
424
|
+
* from CLAUDE.md keeps it in context. Distinct from R1 (in-session re-reads of
|
|
425
|
+
* any file) — this keys on cross-session breadth of a *doc* file.
|
|
426
|
+
*/
|
|
427
|
+
function ruleReferenceDoc(db, ctx) {
|
|
428
|
+
const rows = db
|
|
429
|
+
.prepare(`SELECT input_summary AS file, COUNT(DISTINCT session_id) AS s,
|
|
430
|
+
GROUP_CONCAT(DISTINCT session_id) AS sessions
|
|
431
|
+
FROM claude_tool_calls
|
|
432
|
+
WHERE tool_name = 'Read' AND input_summary != ''
|
|
433
|
+
AND (input_summary LIKE '%.md' OR input_summary LIKE '%.mdx'
|
|
434
|
+
OR input_summary LIKE '%.txt' OR input_summary LIKE '%.rst'
|
|
435
|
+
OR input_summary LIKE '%.adoc')
|
|
436
|
+
GROUP BY input_summary
|
|
437
|
+
HAVING s >= ?
|
|
438
|
+
ORDER BY s DESC
|
|
439
|
+
LIMIT ?`)
|
|
440
|
+
.all(THRESHOLDS.refDocSessions, PER_RULE_LIMIT);
|
|
441
|
+
return rows.map((r) => {
|
|
442
|
+
const base = r.file.split('/').pop() || r.file;
|
|
443
|
+
return (0, targets_1.buildProposal)(ctx, {
|
|
444
|
+
type: 'memory_rule',
|
|
445
|
+
scope: 'project',
|
|
446
|
+
severity: 'warn',
|
|
447
|
+
nameSeed: `reference-${base}`,
|
|
448
|
+
title: `Reference ${base} from CLAUDE.md`,
|
|
449
|
+
body: `${base} was read in ${r.s} separate sessions — context the agent keeps rediscovering. Pointing at it from CLAUDE.md (or importing it with @path) keeps it on hand without a Read each time.`,
|
|
450
|
+
content: `\`${base}\` is frequently-needed context. Add a short pointer to it here (or import it with \`@${r.file}\`) so its guidance is always in context instead of re-Read each session.`,
|
|
451
|
+
evidence: {
|
|
452
|
+
sessions: (r.sessions || '').split(',').filter(Boolean).slice(0, 8),
|
|
453
|
+
prompts: [],
|
|
454
|
+
detail: `${base} read across ${r.s} sessions`,
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function truncate(s, n) {
|
|
460
|
+
const t = s.replace(/\s+/g, ' ').trim();
|
|
461
|
+
return t.length > n ? t.slice(0, n) + '…' : t;
|
|
462
|
+
}
|
|
463
|
+
//# sourceMappingURL=miner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"miner.js","sourceRoot":"","sources":["../../src/reflect/miner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proposal persistence (REQ-REFLECT-007).
|
|
3
|
+
*
|
|
4
|
+
* Wraps the `reflect_proposals` table. Proposals are keyed by `contentHash`, so
|
|
5
|
+
* re-mining the same pattern UPSERTs the existing row (refreshing the
|
|
6
|
+
* user-facing fields + evidence) while PRESERVING its state — a dismissed
|
|
7
|
+
* proposal stays dismissed, an applied one stays applied. `upsertMined` reports
|
|
8
|
+
* which hashes were freshly inserted, which the sweep uses to decide what's
|
|
9
|
+
* "new" and therefore notify-worthy (REQ-REFLECT-006).
|
|
10
|
+
*/
|
|
11
|
+
import { SqliteDatabase } from '../db/sqlite-adapter';
|
|
12
|
+
import { Proposal, ProposalState } from './types';
|
|
13
|
+
export declare class ReflectStore {
|
|
14
|
+
private db;
|
|
15
|
+
private now;
|
|
16
|
+
constructor(db: SqliteDatabase, now?: () => number);
|
|
17
|
+
/**
|
|
18
|
+
* Persist a freshly-mined batch. Returns the proposals as stored (with state
|
|
19
|
+
* resolved) plus the set of hashes that were newly inserted this call.
|
|
20
|
+
*/
|
|
21
|
+
upsertMined(mined: Proposal[]): {
|
|
22
|
+
stored: Proposal[];
|
|
23
|
+
insertedHashes: Set<string>;
|
|
24
|
+
};
|
|
25
|
+
get(hash: string): Proposal | null;
|
|
26
|
+
/** List proposals, optionally filtered by state. Newest-updated first. */
|
|
27
|
+
list(state?: ProposalState): Proposal[];
|
|
28
|
+
/** Transition a proposal's state. `applied` stamps `applied_at` on first apply. */
|
|
29
|
+
setState(hash: string, state: ProposalState): void;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/reflect/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAoClD,qBAAa,YAAY;IACX,OAAO,CAAC,EAAE;IAAkB,OAAO,CAAC,GAAG;gBAA/B,EAAE,EAAE,cAAc,EAAU,GAAG,GAAE,MAAM,MAAyB;IAEpF;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG;QAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;KAAE;IAwCnF,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAOlC,0EAA0E;IAC1E,IAAI,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,QAAQ,EAAE;IASvC,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI;CAgBnD"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Proposal persistence (REQ-REFLECT-007).
|
|
4
|
+
*
|
|
5
|
+
* Wraps the `reflect_proposals` table. Proposals are keyed by `contentHash`, so
|
|
6
|
+
* re-mining the same pattern UPSERTs the existing row (refreshing the
|
|
7
|
+
* user-facing fields + evidence) while PRESERVING its state — a dismissed
|
|
8
|
+
* proposal stays dismissed, an applied one stays applied. `upsertMined` reports
|
|
9
|
+
* which hashes were freshly inserted, which the sweep uses to decide what's
|
|
10
|
+
* "new" and therefore notify-worthy (REQ-REFLECT-006).
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ReflectStore = void 0;
|
|
14
|
+
function rowToProposal(r) {
|
|
15
|
+
return {
|
|
16
|
+
contentHash: r.content_hash,
|
|
17
|
+
type: r.type,
|
|
18
|
+
severity: r.severity,
|
|
19
|
+
title: r.title,
|
|
20
|
+
body: r.body,
|
|
21
|
+
targetKind: r.target_kind,
|
|
22
|
+
targetPath: r.target_path,
|
|
23
|
+
payload: JSON.parse(r.payload),
|
|
24
|
+
evidence: JSON.parse(r.evidence),
|
|
25
|
+
state: r.state,
|
|
26
|
+
createdAt: r.created_at,
|
|
27
|
+
updatedAt: r.updated_at,
|
|
28
|
+
appliedAt: r.applied_at,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
class ReflectStore {
|
|
32
|
+
db;
|
|
33
|
+
now;
|
|
34
|
+
constructor(db, now = () => Date.now()) {
|
|
35
|
+
this.db = db;
|
|
36
|
+
this.now = now;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Persist a freshly-mined batch. Returns the proposals as stored (with state
|
|
40
|
+
* resolved) plus the set of hashes that were newly inserted this call.
|
|
41
|
+
*/
|
|
42
|
+
upsertMined(mined) {
|
|
43
|
+
const insertedHashes = new Set();
|
|
44
|
+
const ts = this.now();
|
|
45
|
+
const existsStmt = this.db.prepare(`SELECT 1 AS x FROM reflect_proposals WHERE content_hash = ?`);
|
|
46
|
+
const insertStmt = this.db.prepare(`INSERT INTO reflect_proposals
|
|
47
|
+
(content_hash, type, severity, title, body, target_kind, target_path, payload, evidence, state, created_at, updated_at, applied_at)
|
|
48
|
+
VALUES (?,?,?,?,?,?,?,?,?, 'open', ?, ?, NULL)
|
|
49
|
+
ON CONFLICT(content_hash) DO UPDATE SET
|
|
50
|
+
severity = excluded.severity,
|
|
51
|
+
title = excluded.title,
|
|
52
|
+
body = excluded.body,
|
|
53
|
+
payload = excluded.payload,
|
|
54
|
+
evidence = excluded.evidence,
|
|
55
|
+
updated_at = excluded.updated_at`);
|
|
56
|
+
const tx = this.db.transaction((items) => {
|
|
57
|
+
for (const p of items) {
|
|
58
|
+
const already = existsStmt.get(p.contentHash);
|
|
59
|
+
if (!already)
|
|
60
|
+
insertedHashes.add(p.contentHash);
|
|
61
|
+
insertStmt.run(p.contentHash, p.type, p.severity, p.title, p.body, p.targetKind, p.targetPath, JSON.stringify(p.payload), JSON.stringify(p.evidence), ts, ts);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
tx(mined);
|
|
65
|
+
const stored = mined.map((p) => this.get(p.contentHash)).filter((x) => !!x);
|
|
66
|
+
return { stored, insertedHashes };
|
|
67
|
+
}
|
|
68
|
+
get(hash) {
|
|
69
|
+
const r = this.db
|
|
70
|
+
.prepare(`SELECT * FROM reflect_proposals WHERE content_hash = ?`)
|
|
71
|
+
.get(hash);
|
|
72
|
+
return r ? rowToProposal(r) : null;
|
|
73
|
+
}
|
|
74
|
+
/** List proposals, optionally filtered by state. Newest-updated first. */
|
|
75
|
+
list(state) {
|
|
76
|
+
const rows = state
|
|
77
|
+
? this.db
|
|
78
|
+
.prepare(`SELECT * FROM reflect_proposals WHERE state = ? ORDER BY updated_at DESC`)
|
|
79
|
+
.all(state)
|
|
80
|
+
: this.db.prepare(`SELECT * FROM reflect_proposals ORDER BY updated_at DESC`).all();
|
|
81
|
+
return rows.map(rowToProposal);
|
|
82
|
+
}
|
|
83
|
+
/** Transition a proposal's state. `applied` stamps `applied_at` on first apply. */
|
|
84
|
+
setState(hash, state) {
|
|
85
|
+
const ts = this.now();
|
|
86
|
+
if (state === 'applied') {
|
|
87
|
+
this.db
|
|
88
|
+
.prepare(`UPDATE reflect_proposals
|
|
89
|
+
SET state = ?, updated_at = ?, applied_at = COALESCE(applied_at, ?)
|
|
90
|
+
WHERE content_hash = ?`)
|
|
91
|
+
.run(state, ts, ts, hash);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
this.db
|
|
95
|
+
.prepare(`UPDATE reflect_proposals SET state = ?, updated_at = ? WHERE content_hash = ?`)
|
|
96
|
+
.run(state, ts, hash);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.ReflectStore = ReflectStore;
|
|
101
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/reflect/store.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAqBH,SAAS,aAAa,CAAC,CAAM;IAC3B,OAAO;QACL,WAAW,EAAE,CAAC,CAAC,YAAY;QAC3B,IAAI,EAAE,CAAC,CAAC,IAAwB;QAChC,QAAQ,EAAE,CAAC,CAAC,QAAgC;QAC5C,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,UAAU,EAAE,CAAC,CAAC,WAAqC;QACnD,UAAU,EAAE,CAAC,CAAC,WAAW;QACzB,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAwB;QACrD,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAyB;QACxD,KAAK,EAAE,CAAC,CAAC,KAAsB;QAC/B,SAAS,EAAE,CAAC,CAAC,UAAU;QACvB,SAAS,EAAE,CAAC,CAAC,UAAU;QACvB,SAAS,EAAE,CAAC,CAAC,UAAU;KACxB,CAAC;AACJ,CAAC;AAED,MAAa,YAAY;IACH;IAA4B;IAAhD,YAAoB,EAAkB,EAAU,MAAoB,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;QAAhE,OAAE,GAAF,EAAE,CAAgB;QAAU,QAAG,GAAH,GAAG,CAAiC;IAAG,CAAC;IAExF;;;OAGG;IACH,WAAW,CAAC,KAAiB;QAC3B,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;QACzC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,6DAA6D,CAAC,CAAC;QAClG,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAChC;;;;;;;;;0CASoC,CACrC,CAAC;QACF,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,KAAiB,EAAE,EAAE;YACnD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;gBAC9C,IAAI,CAAC,OAAO;oBAAE,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;gBAChD,UAAU,CAAC,GAAG,CACZ,CAAC,CAAC,WAAW,EACb,CAAC,CAAC,IAAI,EACN,CAAC,CAAC,QAAQ,EACV,CAAC,CAAC,KAAK,EACP,CAAC,CAAC,IAAI,EACN,CAAC,CAAC,UAAU,EACZ,CAAC,CAAC,UAAU,EACZ,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EACzB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,EAC1B,EAAE,EACF,EAAE,CACH,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,KAAK,CAAC,CAAC;QACV,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAiB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3F,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACpC,CAAC;IAED,GAAG,CAAC,IAAY;QACd,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE;aACd,OAAO,CAAC,wDAAwD,CAAC;aACjE,GAAG,CAAC,IAAI,CAAoB,CAAC;QAChC,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrC,CAAC;IAED,0EAA0E;IAC1E,IAAI,CAAC,KAAqB;QACxB,MAAM,IAAI,GAAG,KAAK;YAChB,CAAC,CAAE,IAAI,CAAC,EAAE;iBACL,OAAO,CAAC,0EAA0E,CAAC;iBACnF,GAAG,CAAC,KAAK,CAAW;YACzB,CAAC,CAAE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,0DAA0D,CAAC,CAAC,GAAG,EAAY,CAAC;QACjG,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACjC,CAAC;IAED,mFAAmF;IACnF,QAAQ,CAAC,IAAY,EAAE,KAAoB;QACzC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,IAAI,CAAC,EAAE;iBACJ,OAAO,CACN;;kCAEwB,CACzB;iBACA,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,EAAE;iBACJ,OAAO,CAAC,+EAA+E,CAAC;iBACxF,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;CACF;AAjFD,oCAiFC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reflection orchestration (REQ-REFLECT-001 / 006).
|
|
3
|
+
*
|
|
4
|
+
* `analyze` runs the miner and persists the batch, returning the current open
|
|
5
|
+
* proposals — this is what the on-demand button and `specship reflect` call.
|
|
6
|
+
*
|
|
7
|
+
* `sweep` does the same but additionally returns the proposals that should fire
|
|
8
|
+
* a notification: freshly-inserted (not previously seen, applied, or dismissed)
|
|
9
|
+
* AND high-severity. Already-seen and lower-severity findings are persisted and
|
|
10
|
+
* listed, but never re-notified.
|
|
11
|
+
*/
|
|
12
|
+
import { SqliteDatabase } from '../db/sqlite-adapter';
|
|
13
|
+
import { Proposal, ReflectContext } from './types';
|
|
14
|
+
export interface AnalyzeResult {
|
|
15
|
+
/** All currently-open proposals after this run. */
|
|
16
|
+
open: Proposal[];
|
|
17
|
+
/** Whether the transcript corpus yielded any signal at all (REQ-REFLECT-001.A2). */
|
|
18
|
+
empty: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface SweepResult extends AnalyzeResult {
|
|
21
|
+
/** New high-severity proposals worth a notification (REQ-REFLECT-006.A2/A3). */
|
|
22
|
+
notify: Proposal[];
|
|
23
|
+
}
|
|
24
|
+
export declare function analyze(db: SqliteDatabase, ctx: ReflectContext, now?: () => number): AnalyzeResult;
|
|
25
|
+
export declare function sweep(db: SqliteDatabase, ctx: ReflectContext, now?: () => number): SweepResult;
|
|
26
|
+
//# sourceMappingURL=sweep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sweep.d.ts","sourceRoot":"","sources":["../../src/reflect/sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,oFAAoF;IACpF,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,gFAAgF;IAChF,MAAM,EAAE,QAAQ,EAAE,CAAC;CACpB;AAED,wBAAgB,OAAO,CACrB,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,cAAc,EACnB,GAAG,GAAE,MAAM,MAAyB,GACnC,aAAa,CAKf;AAED,wBAAgB,KAAK,CACnB,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,cAAc,EACnB,GAAG,GAAE,MAAM,MAAyB,GACnC,WAAW,CAgBb"}
|