feat(mcp): P2 hygiene — honest category suggestions, skill-reference lint, cadence copy (#882)
* feat(mcp): counterparty-tied category suggestions + no_signal (P2-1)
suggest_categories padded every transaction with a company-wide
category-frequency fallback at <=0.5 confidence — an identical four-way
spread on 20+/24 items that agents correctly reported as pure noise
(agent.feedback). Real signal came from memory atoms and query_journal.
- History is now counterparty-keyed: buildMerchantHistory groups past
categorized transactions by normalized merchant; the engine only
surfaces history for THIS transaction's merchant, with provenance
('Bokförd N gånger tidigare för denna motpart') and occurrence-scaled
confidence (0.56 at 1x, capped 0.85). No global padding — an empty
list is the honest answer.
- The MCP tool returns no_signal_transaction_ids for transactions where
NO source matched, steering agents to investigate (query_journal)
instead of pattern-matching on unrelated rows.
- Both callers (REST suggest-categories route + MCP tool) share the new
helpers, so web UI and agents improve together.
Part of dev_docs/mcp_optimization_plan.md (P2-1).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(skills): dangling-reference validation in skills:check + fix 10 dangling links (P2-2)
skills:generate/check now fail when an atom SKILL.md links a
references/*.md that does not exist on disk — a dangling pointer ships
a 404 to every agent that follows it (the weekly-booking-check
incident, agent.feedback).
The validator immediately caught 10 live dangling links in 4 atoms,
three distinct flavors:
- filename typo: swedish-asset-accounting/references/depreciaton.md
renamed to depreciation.md (the link was right, the file misspelled)
- link mismatch: swedish-e-invoicing linked market-providers-pricing.md;
the file is market-provider-pricing.md (link fixed)
- unauthored plans: single-shareholder-ab-fmb TODOs and reklambyra's
'planerad utbyggnad' section used resolvable references/ paths for
files that were never written — rephrased as plans without paths
Seed migration regenerated (4 atoms bumped, renamed reference child).
Part of dev_docs/mcp_optimization_plan.md (P2-2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(events): align agent-feedback review cadence copy (P2-4)
gnubok_feedback replies 'we aggregate signal weekly'; the event-log
handler comment said quarterly. One of them was lying — weekly wins
(the mcp_optimization_plan triage is the living example).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b27f6cdb04
commit
678f2ccffd
@@ -24,14 +24,14 @@
|
||||
"version": 3
|
||||
},
|
||||
"horizontal/swedish-asset-accounting": {
|
||||
"hash": "f7c79332c0fbc24f391aac3aeb1a01327bb803d11864dd68e52e37a6df2e8588",
|
||||
"version": 2
|
||||
"hash": "8f2e88b7d1b1d1f309c8533e8d05b8d0504a1c21d52a4d3257127eb25a52767f",
|
||||
"version": 3
|
||||
},
|
||||
"horizontal/swedish-asset-accounting/accounts-and-registry": {
|
||||
"hash": "b33d044b36d290aebb3677ae82020e0890aff3664070f2d1ec510f38b63793f4",
|
||||
"version": 1
|
||||
},
|
||||
"horizontal/swedish-asset-accounting/depreciaton": {
|
||||
"horizontal/swedish-asset-accounting/depreciation": {
|
||||
"hash": "f764c19f11c8613ba5014ca2767ddb1ae2926928e04c3abb43444ba27c286aec",
|
||||
"version": 1
|
||||
},
|
||||
@@ -40,8 +40,8 @@
|
||||
"version": 1
|
||||
},
|
||||
"horizontal/swedish-e-invoicing": {
|
||||
"hash": "bbcb94de81be7663384085104811a801ed6a12aa37cb2a34eefc6d0ba41267e6",
|
||||
"version": 4
|
||||
"hash": "0483ca6d461699a9651ab6e5b7484dfcc2442f4746f3a0fada4dd97269942496",
|
||||
"version": 5
|
||||
},
|
||||
"horizontal/swedish-e-invoicing/consumer-and-b2c": {
|
||||
"hash": "886cf35d3663b88d3b5bc8ca1766e14f073eb3de1f9e75aa55479582e638f32c",
|
||||
@@ -328,8 +328,8 @@
|
||||
"version": 1
|
||||
},
|
||||
"modifier/single-shareholder-ab-fmb": {
|
||||
"hash": "d79be2b50e33774453b8d6baaec59b79fd4bb7771550cec00f7edd7afedfb30a",
|
||||
"version": 2
|
||||
"hash": "f2398ee0bbf5e76e796a986f89b2f56e518bcc8830997fec197fac563e20960b",
|
||||
"version": 3
|
||||
},
|
||||
"vertical/bygg-hantverk": {
|
||||
"hash": "9670e3515b607176efbdab05be8fc0d76f531ddb1fee8b2d70cbf78802ef571a",
|
||||
@@ -424,8 +424,8 @@
|
||||
"version": 1
|
||||
},
|
||||
"vertical/reklambyra-marknadsforing": {
|
||||
"hash": "6d67fd8ab8c1032168d072025241a0157d68f13f87b92be5e3a84e554265afee",
|
||||
"version": 1
|
||||
"hash": "4eb3a4770caa13d16d693e4ec92aa63c5097e223913a7adc08528051e41b72ee",
|
||||
"version": 2
|
||||
},
|
||||
"vertical/software-saas-ai": {
|
||||
"hash": "63fd0c3f1ddfde9978cd072ee5c76f89900acc5807d5085fa626e611b28a81cd",
|
||||
|
||||
@@ -160,6 +160,30 @@ async function main() {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Dangling references/ links (mcp_optimization_plan P2-2): an atom body
|
||||
// pointing at a references/ file that does not exist ships a 404 to every
|
||||
// agent that follows it. Fails BOTH modes so a dangling pointer can never
|
||||
// reach the registry. Top-level atoms only — reference children live inside
|
||||
// references/ themselves, so relative links would double-resolve.
|
||||
const refLinkRe = /(?:\]\(|\b)\.?\/?(references\/[A-Za-z0-9._/-]+\.md)/g
|
||||
const danglingRefs: string[] = []
|
||||
for (const atom of atoms) {
|
||||
if (atom.parent_atom_id) continue
|
||||
const baseDir = dirname(join(ROOT, atom.body_path))
|
||||
const seen = new Set<string>()
|
||||
for (const m of atom.body.matchAll(refLinkRe)) {
|
||||
const rel = m[1]
|
||||
if (seen.has(rel)) continue
|
||||
seen.add(rel)
|
||||
if (!existsSync(join(baseDir, rel))) danglingRefs.push(`${atom.id}: ${rel}`)
|
||||
}
|
||||
}
|
||||
if (danglingRefs.length > 0) {
|
||||
console.error('✗ dangling references/ link(s) in atom bodies — create the file or remove the pointer:')
|
||||
for (const d of danglingRefs) console.error(` ${d}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const manifest = loadManifest()
|
||||
const onDisk = new Map(atoms.map((a) => [a.id, sha256(a.body)]))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user