dsh-context-guardian 0.1.0-alpha.10
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/CHANGELOG.md +555 -0
- package/LICENSE +21 -0
- package/README.md +412 -0
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/cg_anchors.js +203 -0
- package/cg_autorecall.js +215 -0
- package/cg_configure.py +205 -0
- package/cg_doctor.py +449 -0
- package/cg_handoff.js +253 -0
- package/cg_memory.js +509 -0
- package/cg_postcheck.js +173 -0
- package/cg_provenance.js +206 -0
- package/cg_recall.js +559 -0
- package/cg_recall_cli.py +124 -0
- package/configure.py +10 -0
- package/context_guardian.py +1991 -0
- package/cordis.patch.yml +42 -0
- package/docs/dsh-integration.md +229 -0
- package/docs/dsh-upstream-append-only.md +37 -0
- package/engine.js +1407 -0
- package/index.js +255 -0
- package/modules/cg_bridge.py +251 -0
- package/package.json +88 -0
- package/requirements.txt +4 -0
- package/setup.mjs +629 -0
- package/update_check.js +184 -0
- package/vendor/LICENSE.dsh-compaction-instant +28 -0
- package/vendor/compiler.js +851 -0
- package/vendor/region.js +455 -0
package/cg_provenance.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url'
|
|
2
|
+
|
|
3
|
+
export const ATTRIBUTES = Object.freeze(['codename', 'code', 'owner', 'version', 'port', 'path', 'name'])
|
|
4
|
+
|
|
5
|
+
// "codename" is listed before "code" so the alternation never reads "codename" as "code".
|
|
6
|
+
const ATTR = '(' + ATTRIBUTES.join('|') + ')'
|
|
7
|
+
const SUBJ = '([A-Za-z][A-Za-z0-9_-]*)'
|
|
8
|
+
const VAL = '([A-Za-z0-9][A-Za-z0-9_./:-]*)'
|
|
9
|
+
|
|
10
|
+
// P1: "the release codename for project Heron is MAPLE-123" -> attribute, subject, value
|
|
11
|
+
const P1 = new RegExp('\\b(?:the\\s+)?(?:release\\s+)?' + ATTR + '\\s+for\\s+(?:project\\s+)?' + SUBJ + '\\s+is\\s+(?:now\\s+)?' + VAL, 'gi')
|
|
12
|
+
// P2: "Update: project Heron's release codename is now CEDAR-789" -> subject, attribute, value
|
|
13
|
+
const P2 = new RegExp('\\b(?:update:\\s*)?(?:project\\s+)?' + SUBJ + "'s\\s+(?:release\\s+)?" + ATTR + '\\s+is\\s+(?:now\\s+)?' + VAL, 'gi')
|
|
14
|
+
|
|
15
|
+
function makeFact(m, subjectIdx, attributeIdx) {
|
|
16
|
+
const subject = m[subjectIdx]
|
|
17
|
+
const attribute = m[attributeIdx]
|
|
18
|
+
if (!subject || !attribute) return null
|
|
19
|
+
return {
|
|
20
|
+
key: subject.toLowerCase() + ' ' + attribute.toLowerCase(),
|
|
21
|
+
// A trailing '.' or ':' is sentence punctuation, not part of the value.
|
|
22
|
+
value: m[3].replace(/[.:]+$/, ''),
|
|
23
|
+
index: m.index,
|
|
24
|
+
length: m[0].length,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function extractFacts(text) {
|
|
29
|
+
const str = String(text ?? '')
|
|
30
|
+
const facts = []
|
|
31
|
+
|
|
32
|
+
// P1 captures (attribute, subject, value); P2 captures (subject, attribute, value).
|
|
33
|
+
for (const m of str.matchAll(P1)) {
|
|
34
|
+
const f = makeFact(m, 2, 1)
|
|
35
|
+
if (f) facts.push(f)
|
|
36
|
+
}
|
|
37
|
+
for (const m of str.matchAll(P2)) {
|
|
38
|
+
const f = makeFact(m, 1, 2)
|
|
39
|
+
if (f) facts.push(f)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Sort by index; keep a match only if it does not start before the end of the
|
|
43
|
+
// previously kept one, so overlapping reads collapse to the first one.
|
|
44
|
+
facts.sort((a, b) => a.index - b.index)
|
|
45
|
+
const kept = []
|
|
46
|
+
let lastEnd = -1
|
|
47
|
+
for (const fact of facts) {
|
|
48
|
+
if (fact.index >= lastEnd) {
|
|
49
|
+
kept.push(fact)
|
|
50
|
+
lastEnd = fact.index + fact.length
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return kept
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function pinKey(text) {
|
|
57
|
+
const facts = extractFacts(text)
|
|
58
|
+
if (facts.length === 0) return { key: null, value: null }
|
|
59
|
+
return { key: facts[0].key, value: facts[0].value }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function applyProvenance(pins) {
|
|
63
|
+
if (!Array.isArray(pins)) return []
|
|
64
|
+
|
|
65
|
+
// Work on shallow copies: the caller's array and its objects are never touched.
|
|
66
|
+
const entries = pins.map((pin) => {
|
|
67
|
+
const source = pin && typeof pin === 'object' ? pin : {}
|
|
68
|
+
return {
|
|
69
|
+
pin: source,
|
|
70
|
+
seq: Number(source.seq) || 0,
|
|
71
|
+
facts: extractFacts(source.text),
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < entries.length; i++) {
|
|
76
|
+
const mine = entries[i]
|
|
77
|
+
for (const fact of mine.facts) {
|
|
78
|
+
// Any OTHER pin, wherever it sits in the array, supersedes only if its seq is
|
|
79
|
+
// strictly higher and it states the same key with a different value. The
|
|
80
|
+
// reported seq is the lowest such one, not the first one found.
|
|
81
|
+
let by = null
|
|
82
|
+
for (let j = 0; j < entries.length; j++) {
|
|
83
|
+
if (j === i) continue
|
|
84
|
+
const other = entries[j]
|
|
85
|
+
if (other.seq <= mine.seq) continue
|
|
86
|
+
for (const of_ of other.facts) {
|
|
87
|
+
if (of_.key !== fact.key) continue
|
|
88
|
+
if (of_.value.toLowerCase() === fact.value.toLowerCase()) continue
|
|
89
|
+
if (by === null || other.seq < by) by = other.seq
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
fact.superseded = by !== null
|
|
93
|
+
fact.supersededBy = by
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return entries.map(({ pin, facts }) => {
|
|
98
|
+
const first = facts.length > 0 ? facts[0] : null
|
|
99
|
+
// A pin with no facts has nothing to keep and is therefore never retired.
|
|
100
|
+
const allSuperseded = facts.length > 0 && facts.every((f) => f.superseded)
|
|
101
|
+
const mapFact = (f) => ({
|
|
102
|
+
key: f.key,
|
|
103
|
+
value: f.value,
|
|
104
|
+
index: f.index,
|
|
105
|
+
length: f.length,
|
|
106
|
+
superseded: f.superseded,
|
|
107
|
+
supersededBy: f.supersededBy,
|
|
108
|
+
})
|
|
109
|
+
return Object.assign({}, pin, {
|
|
110
|
+
key: first ? first.key : null,
|
|
111
|
+
value: first ? first.value : null,
|
|
112
|
+
facts: facts.map(mapFact),
|
|
113
|
+
superseded: allSuperseded,
|
|
114
|
+
supersededBy: allSuperseded
|
|
115
|
+
? Math.max(...facts.map((f) => f.supersededBy))
|
|
116
|
+
: null,
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function renderPinText(pin) {
|
|
122
|
+
const text = String((pin && pin.text) ?? '')
|
|
123
|
+
const facts = pin && Array.isArray(pin.facts) ? pin.facts : []
|
|
124
|
+
if (facts.length === 0) return text
|
|
125
|
+
|
|
126
|
+
// Replace the last span first so the remaining indexes stay valid.
|
|
127
|
+
const spans = facts
|
|
128
|
+
.filter((f) => f && f.superseded)
|
|
129
|
+
.slice()
|
|
130
|
+
.sort((a, b) => b.index - a.index)
|
|
131
|
+
|
|
132
|
+
let out = text
|
|
133
|
+
for (const f of spans) {
|
|
134
|
+
const start = f.index
|
|
135
|
+
const end = f.index + f.length
|
|
136
|
+
if (!(Number.isInteger(start) && Number.isInteger(end)) || start < 0 || end > out.length || end < start) continue
|
|
137
|
+
out = out.slice(0, start) + '[' + f.key + ': superseded by seq ' + f.supersededBy + ']' + out.slice(end)
|
|
138
|
+
}
|
|
139
|
+
return out
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function selftest() {
|
|
143
|
+
const plant = 'Important, keep this for later in the conversation: the release codename for project Heron is MAPLE-123; and the release codename for project Kite is RIVER-456.'
|
|
144
|
+
const update = 'Important, keep this for later in the conversation: the release codename for project Heron is now CEDAR-789.'
|
|
145
|
+
const checks = [
|
|
146
|
+
['p1_two_facts', () => {
|
|
147
|
+
const f = extractFacts(plant)
|
|
148
|
+
return f.length === 2 && f[0].key === 'heron codename' && f[0].value === 'MAPLE-123' && f[1].key === 'kite codename' && f[1].value === 'RIVER-456'
|
|
149
|
+
}],
|
|
150
|
+
['p2_possessive_update', () => {
|
|
151
|
+
const f = extractFacts("Update: project Heron's release codename is now CEDAR-789.")
|
|
152
|
+
return f.length === 1 && f[0].key === 'heron codename' && f[0].value === 'CEDAR-789'
|
|
153
|
+
}],
|
|
154
|
+
['code_is_not_codename', () => extractFacts('the code for Kite is ABC123')[0].key === 'kite code'],
|
|
155
|
+
['no_match_null_key', () => {
|
|
156
|
+
const k = pinKey('please remember that I like tea')
|
|
157
|
+
return k.key === null && k.value === null
|
|
158
|
+
}],
|
|
159
|
+
['partial_supersede_keeps_pin', () => {
|
|
160
|
+
const r = applyProvenance([{ text: plant, seq: 3 }, { text: update, seq: 30 }])
|
|
161
|
+
return r[0].superseded === false && r[0].facts[0].superseded === true && r[0].facts[0].supersededBy === 30 && r[0].facts[1].superseded === false && r[1].superseded === false
|
|
162
|
+
}],
|
|
163
|
+
['full_supersede', () => {
|
|
164
|
+
const r = applyProvenance([{ text: 'For reference: the release codename for project Owl is ONE-1.', seq: 2 }, { text: 'For reference: the release codename for project Owl is TWO-2.', seq: 9 }])
|
|
165
|
+
return r[0].superseded === true && r[0].supersededBy === 9 && r[0].key === 'owl codename'
|
|
166
|
+
}],
|
|
167
|
+
['same_value_restated_not_superseded', () => {
|
|
168
|
+
const r = applyProvenance([{ text: 'the release codename for project Owl is ONE-1', seq: 2 }, { text: 'the release codename for project Owl is one-1', seq: 9 }])
|
|
169
|
+
return r[0].superseded === false && r[0].facts[0].superseded === false
|
|
170
|
+
}],
|
|
171
|
+
['older_pin_never_supersedes_newer', () => {
|
|
172
|
+
const r = applyProvenance([{ text: 'the release codename for project Owl is TWO-2', seq: 9 }, { text: 'the release codename for project Owl is ONE-1', seq: 2 }])
|
|
173
|
+
return r[0].superseded === false && r[1].superseded === true
|
|
174
|
+
}],
|
|
175
|
+
['render_strikes_only_stale_fact', () => {
|
|
176
|
+
const r = applyProvenance([{ text: plant, seq: 3 }, { text: update, seq: 30 }])
|
|
177
|
+
const t = renderPinText(r[0])
|
|
178
|
+
return !t.includes('MAPLE-123') && t.includes('RIVER-456') && t.includes('[heron codename: superseded by seq 30]')
|
|
179
|
+
}],
|
|
180
|
+
['input_not_mutated', () => {
|
|
181
|
+
const pins = [{ text: plant, seq: 3 }, { text: update, seq: 30 }]
|
|
182
|
+
applyProvenance(pins)
|
|
183
|
+
return !('facts' in pins[0]) && !('superseded' in pins[0])
|
|
184
|
+
}],
|
|
185
|
+
]
|
|
186
|
+
let passed = 0
|
|
187
|
+
for (const [name, fn] of checks) {
|
|
188
|
+
let ok = false
|
|
189
|
+
let label = name
|
|
190
|
+
try {
|
|
191
|
+
ok = Boolean(fn())
|
|
192
|
+
} catch (err) {
|
|
193
|
+
label = name + ' (raised ' + (err && err.name) + ': ' + (err && err.message) + ')'
|
|
194
|
+
}
|
|
195
|
+
if (ok) passed += 1
|
|
196
|
+
console.log((ok ? 'ok ' : 'FAIL ') + label)
|
|
197
|
+
}
|
|
198
|
+
const failed = checks.length - passed
|
|
199
|
+
console.log('cg_provenance selftest: ' + checks.length + ' checks, ' + passed + ' passed, ' + failed + ' failed')
|
|
200
|
+
return { checks: checks.length, passed, failed }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href && process.argv.includes('--selftest')) {
|
|
204
|
+
const r = selftest()
|
|
205
|
+
process.exit(r.failed === 0 && r.checks > 0 ? 0 : 1)
|
|
206
|
+
}
|