@inerrata-corporation/errata 2.0.21 → 2.0.22-dev.4020

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.
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The capture protocol's inline tags, tidied for the person reading the chat.
3
+ *
4
+ * Display only: the `AssistantMessage` render hook rewrites what the row
5
+ * draws, never the stored reply, so the daemon still harvests every tag from
6
+ * the transcript. Deterministic — the same text and mode draw the same row.
7
+ *
8
+ * margin (default) — references become marginal marks (◎ cited, ✓ held,
9
+ * ✗ refuted, ⇢ linked); a flag reads as a sentence
10
+ * (⚑ …); asides (constraint, tried, failed, cause) go
11
+ * italic; graph metadata (domain, package, …) is shelved
12
+ * out of sight; an errata-* answer block folds to a line.
13
+ * quiet — every tag gone; a flag keeps its words, unmarked,
14
+ * because it is often the only place a problem is said.
15
+ * raw — drawn as written.
16
+ *
17
+ * Tags inside code (fences, inline spans) are prose ABOUT the protocol and
18
+ * are never touched; neither is a GitHub admonition (`> [!NOTE]`).
19
+ */
20
+
21
+ export type TagMode = 'margin' | 'quiet' | 'raw'
22
+ export const TAG_MODES: readonly TagMode[] = ['margin', 'quiet', 'raw']
23
+
24
+ /** Every verb the grammar teaches (agent-signals.ts TAG_EXAMPLE); src/claude-mods-tags.test.ts holds them in step. */
25
+ const VERBS = 'fix|confirm|refute|cause|aids|instance|pattern|builds-on|supersedes|alternative|constraint|tried|failed|domain|package|component'
26
+
27
+ const HANDLE = String.raw`\[[^\]\s]{1,120}\]`
28
+ const HANDLES = String.raw`${HANDLE}(?:\s*,\s*${HANDLE})*`
29
+ const THREAD = String.raw`(?:#[a-z][\w-]{0,63})?`
30
+ /** A tag body: text with at most one level of nested parentheses, one line. */
31
+ const BODY = String.raw`(?:[^()\n]|\([^()\n]*\))`
32
+
33
+ /** `([h])`, `([a], [b])` — leaning on a shown prior. */
34
+ const CITE = new RegExp(String.raw`\(\s*${HANDLES}\s*\)`, 'g')
35
+ /** Verdicts and links that name priors and say nothing else. */
36
+ const REF_VERB = new RegExp(
37
+ String.raw`\(\s*(fix|confirm|cause|aids|instance|pattern|builds-on|supersedes|alternative)\s*:\s*${THREAD}\s*${HANDLES}\s*\)`,
38
+ 'gi',
39
+ )
40
+ /** `(refute:[h] why)`, `(cause:[h] mechanism)` — a judgment on a prior, with its reason. */
41
+ const REASONED = new RegExp(String.raw`\(\s*(refute|cause)\s*:\s*${HANDLES}\s*(${BODY}+?)\s*\)`, 'gi')
42
+ /** Free-text asides the reader may want: why a choice, what was tried, why it broke. */
43
+ const ASIDE = new RegExp(String.raw`\(\s*(constraint|tried|failed|cause|fix)\s*:\s*${THREAD}\s*(${BODY}{2,}?)\s*\)`, 'gi')
44
+ /** Classification for the graph, not the reader. */
45
+ const LABEL = /\(\s*(?:domain|package|component|pattern)\s*:\s*[^()\n]{2,}?\s*\)/gi
46
+ /** `[!one line @ path:42]`, `[?vuln: …]`, `[!#slug …]` — a problem the agent states. */
47
+ const FLAG = /\[([!?])(?:#[a-z][\w-]{0,63}\s+)?\s*((?:\[[^[\]\n]*\]|[^\]\n])+?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g
48
+ const ADMONITION = /^(?:NOTE|TIP|IMPORTANT|WARNING|CAUTION)$/
49
+
50
+ const ERRATA_FENCE = /^```errata-([a-z-]+)[^\n]*\n([\s\S]*?)^```[ \t]*$\n?/gm
51
+
52
+ const MARK = { cite: '◎', held: '✓', refute: '✗', link: '⇢', flag: '⚑', suspect: '⚐' } as const
53
+
54
+ /** Where a tag was taken out: the seam `mend` closes, so spacing the agent wrote stays as written. */
55
+ const GONE = '\u0001'
56
+
57
+ /** Code (fences and inline spans) as opaque slots: tags inside are protocol prose, not tags. */
58
+ function shelveCode(text: string): { text: string; code: string[] } {
59
+ const code: string[] = []
60
+ const slot = (m: string) => `\u0000${code.push(m) - 1}\u0000`
61
+ const fenced = text.replace(/^(```|~~~)(?!errata-)[^\n]*\n[\s\S]*?^\1[ \t]*$/gm, slot)
62
+ return { text: fenced.replace(/`[^`\n]+`/g, slot), code }
63
+ }
64
+
65
+ function unshelveCode(text: string, code: string[]): string {
66
+ return text.replace(/\u0000(\d+)\u0000/g, (_, i: string) => code[Number(i)]!)
67
+ }
68
+
69
+ function refMark(verb: string): string {
70
+ const v = verb.toLowerCase()
71
+ if (v === 'confirm' || v === 'fix') return MARK.held
72
+ return v === 'cause' ? MARK.cite : MARK.link
73
+ }
74
+
75
+ /** One errata-* answer block → a single quiet line, or nothing. */
76
+ function answerBlock(kind: string, body: string, mode: TagMode): string {
77
+ if (mode === 'quiet') return GONE
78
+ if (kind === 'intent') {
79
+ const intent = /^\s*intent:\s*(.+)$/m.exec(body)?.[1]?.trim()
80
+ return intent ? `*✎ ${intent}*\n` : GONE
81
+ }
82
+ return `*✎ errata ${kind} noted*\n`
83
+ }
84
+
85
+ /** Closes the seams removals left — a line or bullet with nothing left, a space before punctuation — and only those. */
86
+ function mend(text: string): string {
87
+ return text
88
+ .split('\n')
89
+ .filter(line => !/^[ \t]*(?:(?:[-*+]|\d+\.)[ \t]*)?(?:\u0001[ \t]*)+$/.test(line))
90
+ .join('\n')
91
+ .replace(/[ \t]*(?:\u0001[ \t]*)+(?=[.,;:!?)]|$)/gm, '')
92
+ .replace(/^([ \t]*(?:[-*+>] |\d+\. )?)(?:\u0001[ \t]*)+/gm, '$1')
93
+ .replace(/[ \t]*(?:\u0001[ \t]*)+/g, ' ')
94
+ }
95
+
96
+ export function tidyTags(input: string, mode: TagMode = 'margin'): string {
97
+ if (mode === 'raw' || !input) return input
98
+ const shelved = shelveCode(input)
99
+ const isQuiet = mode === 'quiet'
100
+ let text = shelved.text
101
+
102
+ text = text.replace(ERRATA_FENCE, (_, kind: string, body: string) => answerBlock(kind, body, mode))
103
+ text = text.replace(REASONED, (_, verb: string, why: string) => {
104
+ if (isQuiet) return GONE
105
+ return `${verb.toLowerCase() === 'refute' ? MARK.refute : MARK.cite} *${why}*`
106
+ })
107
+ text = text.replace(REF_VERB, (_, verb: string) => (isQuiet ? GONE : refMark(verb)))
108
+ text = text.replace(CITE, () => (isQuiet ? GONE : MARK.cite))
109
+ text = text.replace(ASIDE, (_, verb: string, body: string) => {
110
+ if (isQuiet) return GONE
111
+ const v = verb.toLowerCase()
112
+ return v === 'constraint' ? `*(${body})*` : `*${v === 'fix' ? 'fixed' : v}: ${body}*`
113
+ })
114
+ text = text.replace(LABEL, GONE)
115
+ text = text.replace(FLAG, (whole: string, bang: string, body: string, path?: string, line?: string) => {
116
+ if (ADMONITION.test(body)) return whole
117
+ const said = body.replace(/\s+(?:<-|←)\s+/g, ' ← ')
118
+ const where = path ? ` (${path}${line ? `:${line}` : ''})` : ''
119
+ return isQuiet ? `${said}${where}` : `${bang === '?' ? MARK.suspect : MARK.flag} ${said}${where}`
120
+ })
121
+ return unshelveCode(mend(text), shelved.code)
122
+ }
123
+
124
+ const TAGGED = new RegExp(String.raw`\[[!?]|\(\s*\[|\(\s*(?:${VERBS})\s*:|${'`'}{3}errata-`, 'i')
125
+
126
+ /** Whether a reply holds anything `tidyTags` could change: the render hook passes other rows through untouched. */
127
+ export function hasTags(text: string): boolean {
128
+ return TAGGED.test(text)
129
+ }
@@ -4,6 +4,9 @@ export type BurstOrigin = 'agent' | 'daemon-file' | 'daemon-prompt'
4
4
  /** Which membrane a prior lives in: this machine only, or the project / team / org membrane, or the public Errata Network. */
5
5
  export type Scope = 'machine' | 'project' | 'team' | 'org' | 'public'
6
6
 
7
+ /** A cloud community as the walker tools report it: tonight's partition number (compare within one answer only) and its theme when labelled. */
8
+ export type CloudCommunity = { id: number; label: string | null }
9
+
7
10
  export type BurstNode = {
8
11
  id: string
9
12
  label: string
@@ -11,11 +14,14 @@ export type BurstNode = {
11
14
  /** Burst relevance; daemon-delivered nodes get a rank-derived stand-in. */
12
15
  score: number
13
16
  hops: number
14
- /** The node the walk reached this one from, and by which edge. */
17
+ /** The node the walk reached this one from, and by which edge; `forward` ⇒ parent —edge→ node. */
15
18
  parent?: string
16
19
  edge?: string
20
+ forward?: boolean
17
21
  /** Absent when no client-visible signal says. */
18
22
  scope?: Scope
23
+ /** Only cloud-merged hits carry one. */
24
+ community?: CloudCommunity
19
25
  }
20
26
 
21
27
  /** One neighborhood that entered the agent's context. */
@@ -23,8 +29,7 @@ export type Burst = {
23
29
  origin: BurstOrigin
24
30
  seed: { id: string; label: string; name: string }
25
31
  nodes: BurstNode[]
26
- /** Real edges among the delivered nodes, found after the fact. */
27
- extra: [string, string][]
32
+ /** The seed's real name has been looked up (a burst names it by id). */
28
33
  isEnriched: boolean
29
34
  at: number
30
35
  }
@@ -35,25 +40,52 @@ export type MapNode = {
35
40
  name: string
36
41
  isSeed: boolean
37
42
  score: number
38
- /** Rings out from the seed (0 for the seed): the order the reveal grows in. */
43
+ /** Steps out from the seed (0 for the seed). */
39
44
  hops: number
40
45
  scope?: Scope
41
- x: number
42
- y: number
46
+ /** Index of the node the walk reached this one from; -1 for the seed. */
47
+ parent: number
48
+ edge?: string
49
+ forward?: boolean
50
+ community?: CloudCommunity
43
51
  }
44
52
 
53
+ /** A burst catalogued: its nodes in walk order, ready to bind into chains (hooks/chains.ts). */
45
54
  export type MapGraph = {
46
55
  title: string
47
56
  /** The seed's node type (File, Function, Prompt, ...). */
48
57
  seedLabel?: string
49
58
  origin?: BurstOrigin
50
59
  nodes: MapNode[]
51
- /** Index pairs into `nodes`; `true` = an edge of the walk, `false` = a cross edge. */
52
- edges: [number, number, boolean][]
53
60
  at: number
54
61
  note?: string
55
62
  }
56
63
 
64
+ /** A node at the far end of an edge, as `errata_neighbors` names it. */
65
+ export type FarNode = { id: string; label: string; name: string; community?: string }
66
+
67
+ /**
68
+ * What `errata_neighbors` says about one chain's node: the edges that bind a
69
+ * chain (causes, fixes, the problems a lone cause/fix belongs to) and the ones
70
+ * that place it (tech anchors, patterns, the file it is anchored at, its
71
+ * community in the local partition).
72
+ */
73
+ export type ChainFacts = {
74
+ id: string
75
+ label: string
76
+ name: string
77
+ /** `community_<id>` in the local partition; null = unplaced. */
78
+ community: string | null
79
+ causes: FarNode[]
80
+ fixes: FarNode[]
81
+ /** Problems this node causes or fixes (a lone cause/fix's chain). */
82
+ problems: FarNode[]
83
+ /** Tech anchors in edge order: Package (DEPENDS_ON), Tool/Component (CONCERNS), Language (OCCURS_IN). */
84
+ techs: FarNode[]
85
+ patterns: FarNode[]
86
+ anchors: string[]
87
+ }
88
+
57
89
  /** One judgment the agent made on a prior in its reply: cited, confirmed, resolved or refuted. */
58
90
  export type Verdict = {
59
91
  id: string
@@ -69,6 +101,8 @@ declare module 'claude-code' {
69
101
  burst: Burst | null
70
102
  graph: MapGraph
71
103
  verdicts: Verdict[]
104
+ /** `errata_neighbors` answers by node id, for the chains on the shelves; missing = not asked yet, null = asked, not found. */
105
+ facts: Record<string, ChainFacts | null>
72
106
  }
73
107
  }
74
108
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errata-context",
3
- "version": "2.0.21",
3
+ "version": "2.0.22-dev.4020",
4
4
  "description": "A minimal 3D view of loaded Errata priors and recent graph bursts.",
5
5
  "author": {
6
6
  "name": "inErrata"
@@ -1,4 +1,4 @@
1
- 56d81c847556db679faed369632a1f32e234c0ef41ff3b4475efc1af77983a21 .codex-plugin/plugin.json
1
+ cefcb61c20b7e27f11cb65c4ab1bf3352957edd54c21cd575122b54dfd7f6581 .codex-plugin/plugin.json
2
2
  a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
3
3
  fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
4
4
  73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js