@spexcode/spec-core 0.6.2 → 0.6.4

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.
Files changed (58) hide show
  1. package/dist/anchors.d.ts +94 -0
  2. package/dist/anchors.js +730 -0
  3. package/dist/git.d.ts +166 -0
  4. package/dist/git.js +2736 -0
  5. package/dist/graph-delta.d.ts +44 -0
  6. package/dist/graph-delta.js +72 -0
  7. package/dist/graph.d.ts +34 -0
  8. package/dist/graph.js +237 -0
  9. package/dist/graphDelta.d.ts +3 -0
  10. package/dist/graphDelta.js +13 -0
  11. package/dist/harness-identity.d.ts +31 -0
  12. package/dist/harness-identity.js +20 -0
  13. package/dist/identity-presets.d.ts +152 -0
  14. package/dist/identity-presets.js +132 -0
  15. package/dist/index.d.ts +45 -0
  16. package/dist/index.js +19 -0
  17. package/dist/layout.d.ts +183 -0
  18. package/dist/layout.js +548 -0
  19. package/dist/process-identity.d.ts +37 -0
  20. package/dist/process-identity.js +214 -0
  21. package/dist/project-identity.d.ts +12 -0
  22. package/dist/project-identity.js +71 -0
  23. package/dist/project-store.d.ts +3 -0
  24. package/dist/project-store.js +14 -0
  25. package/dist/resilience.d.ts +2 -0
  26. package/dist/resilience.js +40 -0
  27. package/dist/review/index.d.ts +3 -0
  28. package/{src → dist}/review/index.js +3 -3
  29. package/dist/review/reviewFilters.d.ts +77 -0
  30. package/dist/review/reviewFilters.js +308 -0
  31. package/dist/review/reviewQuery.d.ts +66 -0
  32. package/dist/review/reviewQuery.js +180 -0
  33. package/dist/review/session.d.ts +4 -0
  34. package/dist/review/session.js +8 -0
  35. package/dist/reviewSnapshot.d.ts +15 -0
  36. package/dist/reviewSnapshot.js +12 -0
  37. package/dist/root-lru.d.ts +4 -0
  38. package/{src/root-lru.ts → dist/root-lru.js} +26 -30
  39. package/dist/specs.d.ts +117 -0
  40. package/dist/specs.js +489 -0
  41. package/package.json +23 -8
  42. package/src/anchors.ts +0 -728
  43. package/src/git.ts +0 -2556
  44. package/src/graph.ts +0 -251
  45. package/src/harness-identity.ts +0 -26
  46. package/src/identity-presets.d.ts +0 -13
  47. package/src/identity-presets.js +0 -138
  48. package/src/index.ts +0 -20
  49. package/src/layout.ts +0 -637
  50. package/src/process-identity.ts +0 -207
  51. package/src/project-identity.ts +0 -73
  52. package/src/project-store.ts +0 -17
  53. package/src/resilience.ts +0 -41
  54. package/src/review/reviewFilters.js +0 -324
  55. package/src/review/reviewQuery.js +0 -174
  56. package/src/review/session.js +0 -13
  57. package/src/reviewSnapshot.ts +0 -28
  58. package/src/specs.ts +0 -498
@@ -1,174 +0,0 @@
1
- // The ONE token-query engine ([[review-query]]) — pure JS, no React/DOM. Both review ListViews, the
2
- // route layer's legacy replay, and any consumer minting canonical eval/issue addresses import from HERE:
3
- // the visible query TEXT is the single source of truth, and every tab/menu/autocomplete is only a
4
- // BUILDER that rewrites tokens in it (GitHub-measured semantics).
5
-
6
- export const ISSUE_QUERY_DEFAULT = 'is:issue state:open'
7
- export const EVAL_QUERY_DEFAULT = 'is:eval'
8
- // the session doors' scoped-list address: the default view, scoped — the text shows exactly that.
9
- export const scopedEvalQuery = (sessionId) => setToken(EVAL_QUERY_DEFAULT, 'scope', sessionId)
10
- // the aggregate score/count doors' address ([[eval-score-badge]]): the default view, node-filtered.
11
- export const nodeEvalQuery = (nodeId) => setToken(EVAL_QUERY_DEFAULT, 'node', nodeId)
12
-
13
- const KEY_RE = /^([A-Za-z][A-Za-z0-9-]*):(.*)$/s
14
- const unquote = (v) => (v.length >= 2 && v.startsWith('"') && v.endsWith('"') ? v.slice(1, -1) : v)
15
- export const quoteValue = (v) => (/\s/.test(String(v)) ? `"${v}"` : String(v))
16
-
17
- // segment scan preserving EVERY character — whitespace runs and tokens (a `"` swallows spaces until it
18
- // closes), each with [start,end) offsets — so the aria-hidden highlight overlay mirrors the input
19
- // glyph-for-glyph and the autocomplete can find the token under the caret.
20
- export function scanQuery(text) {
21
- const s = String(text ?? '')
22
- const out = []
23
- let i = 0
24
- while (i < s.length) {
25
- let j = i
26
- if (/\s/.test(s[i])) {
27
- while (j < s.length && /\s/.test(s[j])) j++
28
- out.push({ ws: true, raw: s.slice(i, j), start: i, end: j })
29
- } else {
30
- while (j < s.length && !/\s/.test(s[j])) {
31
- if (s[j] === '"') {
32
- j++
33
- while (j < s.length && s[j] !== '"') j++
34
- if (j < s.length) j++
35
- } else j++
36
- }
37
- const raw = s.slice(i, j)
38
- const m = KEY_RE.exec(raw)
39
- out.push(m
40
- ? { ws: false, raw, start: i, end: j, key: m[1].toLowerCase(), value: unquote(m[2]) }
41
- : { ws: false, raw, start: i, end: j, key: null, value: unquote(raw) })
42
- }
43
- i = j
44
- }
45
- return out
46
- }
47
-
48
- export const tokenize = (text) => scanQuery(text).filter((seg) => !seg.ws)
49
- export const serialize = (tokens) => tokens.map((t) => t.raw).join(' ')
50
- export const normalizeQuery = (text) => serialize(tokenize(text))
51
- export const sameQuery = (a, b) => normalizeQuery(a) === normalizeQuery(b)
52
-
53
- // duplicate qualifiers: the LAST occurrence wins; bare words all apply.
54
- export const effectiveTokens = (tokens) => {
55
- const last = new Map()
56
- for (const t of tokens) if (t.key != null) last.set(t.key, t)
57
- return tokens.filter((t) => t.key == null || last.get(t.key) === t)
58
- }
59
-
60
- export const readToken = (text, key) => {
61
- const tokens = tokenize(text)
62
- for (let i = tokens.length - 1; i >= 0; i--) if (tokens[i].key === key) return tokens[i].value
63
- return ''
64
- }
65
-
66
- // token SURGERY: rewrite the key at its first position, drop later duplicates, append when absent,
67
- // remove the key entirely on an empty value. Every other token — known or not — survives verbatim.
68
- export function setToken(text, key, value) {
69
- const next = value == null || String(value) === ''
70
- ? null
71
- : { ws: false, raw: `${key}:${quoteValue(value)}`, key, value: String(value) }
72
- const out = []
73
- let placed = false
74
- for (const t of tokenize(text)) {
75
- if (t.key === key) {
76
- if (next && !placed) { out.push(next); placed = true }
77
- continue
78
- }
79
- out.push(t)
80
- }
81
- if (next && !placed) out.push(next)
82
- return serialize(out)
83
- }
84
-
85
- // MATCHING is deliberately NOT here: this module owns text — scan/serialize/surgery/suggestions and the
86
- // canonical-address discipline — while the conjunctive field matching (including the unknown-qualifier
87
- // IMPOSSIBLE state) lives in the ONE [[review-filters]] engine, reached through its tokenFilterState
88
- // bridge. A second predicate here would be the exact fork the fusion removed.
89
-
90
- // canonical address discipline: the default view is the BARE page address; any other state is exactly
91
- // ?q=<raw text>. An emptied submit falls back to the default (→ bare).
92
- export const queryParam = (text, defaultText) => {
93
- const trimmed = String(text ?? '').trim()
94
- if (!trimmed || sameQuery(trimmed, defaultText)) return null
95
- return { q: trimmed }
96
- }
97
-
98
- // A query/filter action resets by omitting page; a PAGINATION action passes a page (including 1) and
99
- // therefore records GitHub's explicit page=1 history form. The distinction is the action, not equivalence.
100
- export const reviewRouteQuery = (text, defaultText, page = null) => {
101
- const query = queryParam(text, defaultText) || {}
102
- if (page != null) query.page = String(page)
103
- return Object.keys(query).length ? query : null
104
- }
105
-
106
- // LEGACY structured params → token pairs. `kind=all` maps to nothing: the evidence default IS all.
107
- const LEGACY_PARAMS = {
108
- state: (v) => ['state', v],
109
- concluded: (v) => (v === '1' ? ['state', 'closed'] : null),
110
- ok: (v) => (v === '1' ? ['state', 'reviewed'] : null),
111
- verdict: (v) => ['verdict', v],
112
- freshness: (v) => ['freshness', v],
113
- kind: (v) => (v === 'all' ? null : ['evidence', v]),
114
- store: (v) => ['store', v],
115
- author: (v) => ['author', v],
116
- node: (v) => ['node', v],
117
- filer: (v) => ['filer', v],
118
- live: (v) => (v === '1' ? ['session', 'present'] : null),
119
- session: (v) => ['scope', v],
120
- }
121
-
122
- export const hasLegacyParams = (query) =>
123
- Object.keys(LEGACY_PARAMS).some((k) => query?.[k] != null && query[k] !== '')
124
-
125
- // the legacy free q was ONE substring search — it must replay as ONE text token. Quote it whenever the
126
- // tokenizer would read it as anything else: spaces (several words), a colon (q=drift:check would become
127
- // an unknown qualifier and match zero), or a stray quote (which would swallow neighbours).
128
- const freeTextToken = (v) => (/[\s:"]/.test(v) ? `"${v.replace(/"/g, '')}"` : v)
129
-
130
- // a legacy LIST address replays as the FULL visible state: the page's default tokens with each legacy
131
- // param surgically applied (live=1→session:present, session=<id>→scope:<id>, ok=1→state:reviewed,
132
- // kind→evidence:), the free-text q appended as ONE bare/phrase token preserving the old
133
- // single-substring search. Returns null when nothing legacy is present.
134
- export function legacyQueryText(defaultText, query) {
135
- if (!hasLegacyParams(query)) return null
136
- let text = defaultText
137
- for (const [param, toPair] of Object.entries(LEGACY_PARAMS)) {
138
- const v = query[param]
139
- if (v == null || v === '') continue
140
- const pair = toPair(String(v))
141
- if (pair) text = setToken(text, pair[0], pair[1])
142
- }
143
- const free = String(query.q ?? '').trim()
144
- if (free) text = `${text} ${freeTextToken(free)}`.trim()
145
- return text
146
- }
147
-
148
- // inline autocomplete at the caret — client-side and BOUNDED. A bare prefix completes qualifier KEYS
149
- // (insert `key:`, keep typing); a `key:prefix` completes VALUES from the page-supplied candidate list
150
- // only (data-derived; scope = sessions on the current board), capped at 8. Everything else stays
151
- // hand-typable and submits verbatim.
152
- export function suggestAt(text, caret, keys = [], values = {}) {
153
- const s = String(text ?? '')
154
- const at = Math.max(0, Math.min(caret ?? s.length, s.length))
155
- const seg = scanQuery(s).find((g) => !g.ws && g.start < at && at <= g.end)
156
- if (!seg) return { start: at, end: at, items: [] }
157
- const typed = s.slice(seg.start, at)
158
- const m = KEY_RE.exec(typed)
159
- if (!m) {
160
- const w = typed.toLowerCase()
161
- if (!w || w.includes('"')) return { start: seg.start, end: seg.end, items: [] }
162
- const items = keys.filter((k) => k.startsWith(w)).slice(0, 8)
163
- .map((k) => ({ type: 'key', key: k, insert: `${k}:` }))
164
- return { start: seg.start, end: seg.end, items }
165
- }
166
- const key = m[1].toLowerCase()
167
- const prefix = m[2].replace(/^"/, '').replace(/"$/, '').toLowerCase()
168
- const pool = values[key] || []
169
- const items = pool
170
- .filter((c) => String(c.value).toLowerCase().startsWith(prefix) && String(c.value).toLowerCase() !== prefix)
171
- .slice(0, 8)
172
- .map((c) => ({ type: 'value', key, value: String(c.value), label: c.label || null, insert: `${key}:${quoteValue(c.value)} ` }))
173
- return { start: seg.start, end: seg.end, items }
174
- }
@@ -1,13 +0,0 @@
1
- export const sessionPresent = (sessions, id) => {
2
- const session = id ? (sessions || []).find((item) => item.id === id) : null
3
- return session || null
4
- }
5
-
6
- export const sessionHeadline = (session) =>
7
- session?.title || session?.headline || session?.name || session?.activity || session?.note
8
- || session?.promptPreview || session?.node || session?.raw?.title || session?.branch || session?.id
9
-
10
- export const sessionHandle = (session) =>
11
- session?.label || session?.name || session?.node || session?.title || session?.branch || session?.id
12
-
13
- export const sessionTitle = sessionHeadline
@@ -1,28 +0,0 @@
1
- export type ReviewEvalNode = {
2
- id: string
3
- hue?: number
4
- scenarios: any[]
5
- evals: any[]
6
- readings: any[]
7
- }
8
-
9
- export type ReviewSnapshot = {
10
- issues: any[]
11
- evalNodes: ReviewEvalNode[]
12
- forgeRevision: number
13
- }
14
-
15
- let current: ReviewSnapshot | null = null
16
-
17
- export function publishReviewSnapshot(snapshot: ReviewSnapshot): void {
18
- current = snapshot
19
- }
20
-
21
- export function readReviewSnapshot(): ReviewSnapshot {
22
- if (!current) throw new Error('review snapshot is unavailable before the first successful graph build')
23
- return current
24
- }
25
-
26
- export function hasReviewSnapshot(): boolean {
27
- return current !== null
28
- }