Skip to content

Commit 5b5b82c

Browse files
authored
Merge pull request #4 from pashifika/feat/graph-context-and-agents
2 parents cd4c877 + 1880542 commit 5b5b82c

48 files changed

Lines changed: 8724 additions & 380 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 157 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,29 @@ on:
55
push:
66
branches: [main]
77
workflow_dispatch:
8+
# The `harvest` job's whole purpose is detecting that upstream shipped
9+
# different content, and that event produces no activity in this repository at
10+
# all -- so a gate firing only on push cannot detect a new upstream release,
11+
# which is the one case the drift requirement exists for. Weekly, off the hour.
12+
#
13+
# Known blind spot, and it cannot be closed from inside the repository: GitHub
14+
# disables scheduled workflows after prolonged repository inactivity, silently.
15+
# That is why the per-session tool-surface check on the operator's own machine
16+
# is the primary detector and this is the secondary net; see README.md.
17+
schedule:
18+
- cron: "17 4 * * 1"
819
# Reused by `release.yml`, so a tag passes the same gate a pull request does
920
# rather than a copy of it that can drift.
1021
workflow_call:
1122

1223
# Least privilege: nothing in this workflow writes to the repository.
24+
#
25+
# The scheduled `harvest` run inherits this and needs nothing more. Its failure
26+
# *is* the notification -- GitHub already delivers it -- so it opens no pull
27+
# request, pushes no branch, commits no regenerated artifact, and creates no
28+
# issue. Regenerating stays a reviewed human commit: buying that convenience
29+
# would cost write scopes on both contents and pull requests, and would make the
30+
# least-privilege posture depend on an automation nobody reviews per run.
1331
permissions:
1432
contents: read
1533

@@ -187,21 +205,123 @@ jobs:
187205
timeout-minutes: 5
188206
run: bun run test:packaging
189207

190-
# `test:packaging` rebuilds `dist/index.js` before loading it, so a green
191-
# packaging step proves that a fresh bundle builds and registers what it
192-
# claims -- never that the committed one matches the source beside it. The
193-
# README tells an operator to load the committed file directly after
194-
# `git clone` with no build step, so without this a pull request carrying
195-
# an innocuous source diff and a substituted bundle would merge green.
196-
- name: Verify the committed bundle matches the one built from source
208+
# `test:packaging` rebuilds both bundles before loading them, so a green
209+
# packaging step proves a fresh bundle builds and registers what it claims
210+
# -- never that the committed one matches the source beside it. The README
211+
# tells an operator to load the committed files directly after `git clone`
212+
# with no build step, so without this a pull request carrying an innocuous
213+
# source diff and a substituted bundle would merge green.
214+
- name: Verify the committed bundles match the ones built from source
197215
timeout-minutes: 5
198216
run: |
199217
set -euo pipefail
200218
201-
# A pathspec matching nothing makes `git diff` exit 0, so the path is
219+
# Read out of the manifest rather than listed again here: the entries
220+
# OMP loads are the ones that have to match, and a second list drifts.
221+
# The feature entry is included, so declining the feature at install
222+
# time cannot make an unverified bundle ship.
223+
mapfile -t bundles < <(
224+
jq -r '(.omp.extensions // []) + ([.omp.features // {} | .[].extensions // []] | add // [])
225+
| .[] | sub("^\\./"; "")' package.json | sort -u
226+
)
227+
228+
if [ "${#bundles[@]}" -eq 0 ]; then
229+
echo "::error::package.json declares no extension entries; this check would verify nothing"
230+
exit 1
231+
fi
232+
233+
printf 'checking %d bundle(s)\n' "${#bundles[@]}"
234+
printf ' %s\n' "${bundles[@]}"
235+
236+
# A pathspec matching nothing makes `git diff` exit 0, so every path is
202237
# confirmed tracked before its diff is trusted.
203-
git ls-files --error-unmatch dist/index.js
204-
git diff --exit-code -- dist/index.js
238+
for bundle in "${bundles[@]}"; do
239+
git ls-files --error-unmatch "$bundle"
240+
done
241+
git diff --exit-code -- "${bundles[@]}"
242+
243+
# Runtime job. Regenerates the shipped skill, rule, and agents from a real CBM
244+
# executable and fails when the committed copies differ. The source of truth is
245+
# embedded in the executable and changes with it, so a copy nobody regenerates
246+
# becomes a second, silently diverging statement of the same contract.
247+
#
248+
# Also the secondary drift detector, which is why the workflow carries a
249+
# `schedule` trigger: see the comment on `on:` for what a push-only gate cannot
250+
# see, and README.md for why the per-session check is the primary one.
251+
harvest:
252+
name: harvest
253+
runs-on: ubuntu-24.04
254+
timeout-minutes: 30
255+
steps:
256+
- name: Check out the source tree
257+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
258+
timeout-minutes: 5
259+
with:
260+
persist-credentials: false
261+
262+
- name: Install the pinned Bun toolchain
263+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
264+
timeout-minutes: 5
265+
with:
266+
bun-version: 1.3.14
267+
268+
- name: Install dependencies
269+
timeout-minutes: 5
270+
run: bun install --frozen-lockfile
271+
272+
- name: Acquire the newest CBM release through this package's own path
273+
timeout-minutes: 15
274+
# Not `curl … install.sh | bash`: that is an unpinned script run with the
275+
# runner's privileges. This goes through `src/acquire.ts`, which verifies
276+
# the asset against the release's own `checksums.txt`, refuses an archive
277+
# whose member list is not exactly the expected four, and runs the
278+
# candidate once before adopting it.
279+
run: bun run scripts/acquire-cbm.ts
280+
281+
- name: Regenerate the shipped context artifacts
282+
timeout-minutes: 10
283+
# `--stop-sessions` is a no-op here and load-bearing on a contributor's
284+
# machine: `install` is CBM's activation path and drains active CBM
285+
# sessions first, so the harvest refuses by default rather than closing
286+
# an editor's MCP connection as a side effect of regenerating docs. A
287+
# runner has no sessions to close.
288+
run: bun run harvest --stop-sessions
289+
290+
- name: Fail when a committed generated artifact differs
291+
timeout-minutes: 5
292+
run: |
293+
set -euo pipefail
294+
295+
# From the provenance record the pipeline just wrote, so the list is
296+
# the pipeline's own and cannot fall behind it.
297+
mapfile -t generated < <(jq -r '.generated[]' harvest.json | sort -u)
298+
299+
if [ "${#generated[@]}" -eq 0 ]; then
300+
echo "::error::harvest.json names no generated paths; this check would verify nothing"
301+
exit 1
302+
fi
303+
304+
printf 'checking %d generated path(s)\n' "${#generated[@]}"
305+
printf ' %s\n' "${generated[@]}"
306+
307+
for file in "${generated[@]}"; do
308+
git ls-files --error-unmatch "$file"
309+
done
310+
311+
if ! git diff --exit-code -- "${generated[@]}"; then
312+
echo "::error::the committed context artifacts differ from what this CBM release emits; run \`bun run harvest\` and commit the result"
313+
exit 1
314+
fi
315+
316+
# A path the pipeline stopped writing, or one it started writing, is
317+
# drift the diff above cannot see: the first is not in the new list and
318+
# the second is not yet tracked.
319+
strays="$(git status --porcelain --untracked-files=all -- skills rules agents harvest.json)"
320+
if [ -n "$strays" ]; then
321+
echo "::error::the owned directories hold changes outside the regenerated set:"
322+
echo "$strays"
323+
exit 1
324+
fi
205325
206326
# Runtime job. Runs the two install commands the README documents, through
207327
# OMP's own CLI, so the documented path and the verified path are one path.
@@ -218,6 +338,10 @@ jobs:
218338
# release, so the documented command is verified against the exact ref an
219339
# operator can install. It is deliberately absent from `ci`'s `needs`: a
220340
# skipped job would otherwise fail the required check on every pull request.
341+
#
342+
# `schedule` is deliberately absent from the condition too. A scheduled run
343+
# exists to re-run the drift gate, it reports no pull-request check, and
344+
# installing by ref on a timer would verify the same ref again for nothing.
221345
install-check:
222346
name: install check
223347
if: github.event_name == 'workflow_dispatch' || github.event_name == 'push'
@@ -292,6 +416,28 @@ jobs:
292416
cat "$HOME/plugins.txt"
293417
grep -q 'omp-codebase-memory' "$HOME/plugins.txt"
294418
419+
# A packaging change can drop a whole directory without changing a
420+
# single file in it, so the shipped context surfaces are asserted on
421+
# the *installed* tree rather than on the working tree the suite reads.
422+
# The root comes from OMP's own registry, not from a path guessed here.
423+
root="$(omp plugin list --json | jq -r '.npm[] | select(.name == "omp-codebase-memory") | .path')"
424+
if [ -z "$root" ] || [ ! -d "$root" ]; then
425+
echo "::error::omp plugin list --json reported no installed path for omp-codebase-memory"
426+
exit 1
427+
fi
428+
echo "installed at $root"
429+
430+
missing=0
431+
while read -r file; do
432+
if [ -f "$root/$file" ]; then
433+
echo " ok $file"
434+
else
435+
echo "::error::the installed tree is missing $file"
436+
missing=$((missing + 1))
437+
fi
438+
done <<< "$(jq -r '.generated[]' harvest.json)"
439+
[ "$missing" -eq 0 ]
440+
295441
- name: Link this checkout the way the development install documents
296442
timeout-minutes: 5
297443
run: |
@@ -317,7 +463,7 @@ jobs:
317463
# means editing this job's `needs` and nothing else.
318464
ci:
319465
name: ci
320-
needs: [hygiene, bun]
466+
needs: [hygiene, bun, harvest]
321467
# `always()` is load-bearing. Without it this job is skipped when a
322468
# dependency fails, and a skipped required check blocks a pull request
323469
# rather than failing it -- a stuck merge button instead of a red one.

CLAUDE.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ specification in the same change.
1313

1414
`omp-codebase-memory` distributes `codebase-memory-mcp` (CBM) as an installable
1515
OMP extension. It is TypeScript on Bun, has no npm runtime dependencies, and
16-
commits its bundled entry point at `dist/index.js`.
16+
commits its bundled entry points at `dist/index.js` and `dist/augment.js`.
1717

1818
The following boundaries are fixed:
1919

@@ -37,6 +37,11 @@ The following boundaries are fixed:
3737
indexing.
3838
- Never hand-edit generated context artifacts; regenerate them from the CBM
3939
executable.
40+
- Never place verification scratch inside a directory the operator owns, and
41+
never delete a directory this package or its verification did not create. A
42+
project-local plugin root belongs in a temporary directory, not under the
43+
repository's `.omp/`, which holds the operator's own project-local skills and
44+
configuration.
4045

4146
This package owns only the executable it downloaded and its MCP entry. CBM owns
4247
the graph, indexing, watcher, cache root, and updates to a system installation.
@@ -87,8 +92,9 @@ no jobs, and accepts only successful dependencies.
8792
- Run checks through package scripts and print toolchain versions with results.
8893
- Do not add a Node job; Node is not a supported runtime.
8994

90-
`dist/index.js` is committed. CI must build from source and compare the result
91-
byte-for-byte with that tracked bundle.
95+
`dist/index.js` and the feature entry `dist/augment.js` are committed. CI must
96+
read the bundle list from `package.json`'s extension entries, build from
97+
source, and compare each result byte-for-byte with its tracked bundle.
9298

9399
A release tag must match `package.json`'s version and both the version and source
94100
ref in `.omp-plugin/marketplace.json`. Create releases only from verified tags.

0 commit comments

Comments
 (0)