create-egregore 0.20.0 → 0.20.1
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/assets/egregore-launcher.js +25 -3
- package/lib/setup.js +21 -2
- package/package.json +1 -1
- package/runtime/codex/.codex/config.toml +3 -0
- package/runtime/codex/.codex/skills/archive/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/branch/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/checkup/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/commit/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/contribute/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/delete-user/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/ingest/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/ingest-corpus/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/ingest-notion/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/issue/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/me/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/meeting/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/note/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/notion-connect/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/onboarding/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/pr/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/pull/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/push/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/reflect/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/review-pr/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/save/SKILL.md +3 -1
- package/runtime/codex/.codex/skills/slack-connect/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/summon/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/sync-repos/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/teams-connect/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/telemetry/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/test/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/tutorial/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/update/SKILL.md +1 -1
- package/runtime/codex/.codex/spec-manifest.json +3 -3
- package/runtime/codex/AGENTS.md +30 -31
- package/runtime/codex/bin/agent.sh +17 -5
- package/runtime/codex/bin/codex-render-spec.mjs +27 -32
- package/runtime/codex/bin/codex-sync-skills.sh +6 -1
- package/runtime/codex/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/codex/bin/handoff-run.sh +8 -3
- package/runtime/codex/bin/handoff-save-egregore.sh +19 -7
- package/runtime/codex/bin/ingest.py +260 -5
- package/runtime/codex/bin/lib/git-message.sh +37 -0
- package/runtime/codex/bin/lib/git-sync.sh +10 -2
- package/runtime/codex/bin/session-autosave.sh +7 -1
- package/runtime/codex/bin/settings.sh +1 -1
- package/runtime/codex/manifest.json +4 -1
- package/runtime/pi/.codex/config.toml +3 -0
- package/runtime/pi/.codex/skills/archive/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/branch/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/checkup/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/commit/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/contribute/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/delete-user/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/ingest/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/ingest-corpus/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/ingest-notion/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/issue/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/me/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/meeting/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/note/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/notion-connect/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/onboarding/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/pr/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/pull/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/push/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/reflect/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/review-pr/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/save/SKILL.md +3 -1
- package/runtime/pi/.codex/skills/slack-connect/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/summon/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/sync-repos/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/teams-connect/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/telemetry/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/test/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/tutorial/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/update/SKILL.md +1 -1
- package/runtime/pi/.codex/spec-manifest.json +3 -3
- package/runtime/pi/.pi/APPEND_SYSTEM.md +54 -31
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +30 -31
- package/runtime/pi/bin/agent.sh +17 -5
- package/runtime/pi/bin/codex-render-spec.mjs +27 -32
- package/runtime/pi/bin/codex-sync-skills.sh +6 -1
- package/runtime/pi/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/pi/bin/handoff-run.sh +8 -3
- package/runtime/pi/bin/handoff-save-egregore.sh +19 -7
- package/runtime/pi/bin/ingest.py +260 -5
- package/runtime/pi/bin/lib/git-message.sh +37 -0
- package/runtime/pi/bin/lib/git-sync.sh +10 -2
- package/runtime/pi/bin/pi-render-spec.mjs +52 -26
- package/runtime/pi/bin/session-autosave.sh +7 -1
- package/runtime/pi/bin/settings.sh +1 -1
- package/runtime/pi/manifest.json +4 -1
- package/runtime/prime/.codex/config.toml +3 -0
- package/runtime/prime/.codex/skills/archive/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/branch/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/checkup/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/commit/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/contribute/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/delete-user/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/ingest/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/ingest-corpus/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/ingest-notion/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/ingest-user-interview/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/issue/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/me/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/meeting/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/note/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/notion-connect/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/onboarding/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/pr/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/pull/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/push/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/reflect/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/review-pr/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/save/SKILL.md +3 -1
- package/runtime/prime/.codex/skills/slack-connect/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/summon/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/sync-repos/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/teams-connect/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/telegram-connect/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/telemetry/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/test/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/tutorial/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/update/SKILL.md +1 -1
- package/runtime/prime/.codex/spec-manifest.json +3 -3
- package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +54 -31
- package/runtime/prime/.prime/agent/spec-manifest.json +2 -2
- package/runtime/prime/AGENTS.md +30 -31
- package/runtime/prime/bin/agent.sh +17 -5
- package/runtime/prime/bin/codex-render-spec.mjs +27 -32
- package/runtime/prime/bin/codex-sync-skills.sh +6 -1
- package/runtime/prime/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/prime/bin/handoff-run.sh +8 -3
- package/runtime/prime/bin/handoff-save-egregore.sh +19 -7
- package/runtime/prime/bin/ingest.py +260 -5
- package/runtime/prime/bin/lib/git-message.sh +37 -0
- package/runtime/prime/bin/lib/git-sync.sh +10 -2
- package/runtime/prime/bin/prime-render-spec.mjs +51 -27
- package/runtime/prime/bin/session-autosave.sh +7 -1
- package/runtime/prime/bin/settings.sh +1 -1
- package/runtime/prime/manifest.json +4 -1
|
@@ -36,6 +36,11 @@ set -euo pipefail
|
|
|
36
36
|
# 1 on unrecoverable failure (file write, memory push, bad args).
|
|
37
37
|
|
|
38
38
|
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
39
|
+
# shellcheck source=bin/lib/git-message.sh
|
|
40
|
+
. "$SCRIPT_DIR/bin/lib/git-message.sh" 2>/dev/null || true
|
|
41
|
+
type egregore_commit >/dev/null 2>&1 || egregore_commit() {
|
|
42
|
+
local gd="$1" m="$3"; shift 3; git -C "$gd" commit -m "$m" "$@"
|
|
43
|
+
}
|
|
39
44
|
CONFIG="$SCRIPT_DIR/egregore.json"
|
|
40
45
|
|
|
41
46
|
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
@@ -326,12 +331,12 @@ PID_ARTIFACTS=$!
|
|
|
326
331
|
git add "$REL_FILE" handoffs/index.md >/dev/null 2>&1
|
|
327
332
|
|
|
328
333
|
if [ -n "$RECIPIENT" ]; then
|
|
329
|
-
MSG_COMMIT="
|
|
334
|
+
MSG_COMMIT="chore(handoff): record ${TOPIC} (to ${RECIPIENT})"
|
|
330
335
|
else
|
|
331
|
-
MSG_COMMIT="
|
|
336
|
+
MSG_COMMIT="chore(handoff): record ${TOPIC}"
|
|
332
337
|
fi
|
|
333
338
|
|
|
334
|
-
|
|
339
|
+
egregore_commit . "$SCRIPT_DIR" "$MSG_COMMIT" >/dev/null 2>&1 || true
|
|
335
340
|
|
|
336
341
|
STASHED=0
|
|
337
342
|
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
|
|
@@ -47,19 +47,16 @@ done
|
|
|
47
47
|
|
|
48
48
|
case "$KIND" in
|
|
49
49
|
autosave)
|
|
50
|
-
MSG_PREFIX="
|
|
50
|
+
MSG_PREFIX="chore(autosave): save"
|
|
51
51
|
PR_WHAT="Save session content captured by the opted-in autosave workflow."
|
|
52
52
|
PR_WHY="This preserves unattended non-coding work without requiring an explicit /save."
|
|
53
53
|
;;
|
|
54
54
|
*)
|
|
55
|
-
MSG_PREFIX="
|
|
55
|
+
MSG_PREFIX="chore(handoff): record"
|
|
56
56
|
PR_WHAT="Save core-repository session work captured when the handoff was created."
|
|
57
57
|
PR_WHY="The handoff records shared context immediately; this companion PR keeps the originating checkout's work from remaining only local."
|
|
58
58
|
;;
|
|
59
59
|
esac
|
|
60
|
-
PR_BODY=$(printf '## What\n\n- %s\n\n## Why\n\n%s Topic: `%s`.\n\n## Verification\n\nNot verified — background %s save; review code or configuration changes before merging.\n\n🤖 Saved via `bin/handoff-save-egregore.sh`\n' \
|
|
61
|
-
"$PR_WHAT" "$PR_WHY" "$TOPIC" "$KIND")
|
|
62
|
-
|
|
63
60
|
[ -n "$AUTHOR" ] || { echo "handoff-save-egregore: missing author" >&2; exit 1; }
|
|
64
61
|
|
|
65
62
|
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
@@ -88,6 +85,21 @@ BASE_REF="origin/$BASE_BRANCH"
|
|
|
88
85
|
. "$SCRIPT_DIR/bin/lib/git-safe.sh" 2>/dev/null || true
|
|
89
86
|
type git_add_guarded >/dev/null 2>&1 || git_add_guarded() { git add -A 2>/dev/null || true; }
|
|
90
87
|
|
|
88
|
+
# Shared commit/PR message mechanics (trailers + skeleton body shape).
|
|
89
|
+
# shellcheck source=bin/lib/git-message.sh
|
|
90
|
+
. "$SCRIPT_DIR/bin/lib/git-message.sh" 2>/dev/null || true
|
|
91
|
+
type egregore_commit >/dev/null 2>&1 || egregore_commit() {
|
|
92
|
+
local gd="$1" m="$3"; shift 3; git -C "$gd" commit -m "$m" "$@"
|
|
93
|
+
}
|
|
94
|
+
type egregore_pr_skeleton >/dev/null 2>&1 || egregore_pr_skeleton() {
|
|
95
|
+
printf '## What\n%s\n\n## Why\n%s\n\n## Verification\n%s\n\n%s\n' "$1" "$2" "$3" "$4"
|
|
96
|
+
}
|
|
97
|
+
PR_BODY=$(egregore_pr_skeleton \
|
|
98
|
+
"- $PR_WHAT" \
|
|
99
|
+
"$PR_WHY Topic: \`$TOPIC\`." \
|
|
100
|
+
"Not verified — background $KIND save; review code or configuration changes before merging." \
|
|
101
|
+
"🤖 Saved via \`bin/handoff-save-egregore.sh\`")
|
|
102
|
+
|
|
91
103
|
# Shared non-coding classifier (auto-merge policy lives in one place).
|
|
92
104
|
# shellcheck source=bin/lib/noncode.sh
|
|
93
105
|
. "$SCRIPT_DIR/bin/lib/noncode.sh" 2>/dev/null || true
|
|
@@ -131,7 +143,7 @@ fi
|
|
|
131
143
|
# --- Commit whatever is uncommitted --------------------------------------
|
|
132
144
|
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
|
|
133
145
|
git_add_guarded
|
|
134
|
-
|
|
146
|
+
egregore_commit . "$REPO_DIR" "${MSG_PREFIX} ${TOPIC}" --quiet 2>/dev/null || true
|
|
135
147
|
fi
|
|
136
148
|
|
|
137
149
|
# --- Rebase onto the configured base, fall back to merge on conflicts ----
|
|
@@ -182,7 +194,7 @@ else
|
|
|
182
194
|
PR_URL=$(gh pr create \
|
|
183
195
|
--base "$BASE_BRANCH" \
|
|
184
196
|
--head "$CURRENT_BRANCH" \
|
|
185
|
-
--title "${MSG_PREFIX}
|
|
197
|
+
--title "${MSG_PREFIX} ${TOPIC}" \
|
|
186
198
|
--body "$PR_BODY" \
|
|
187
199
|
2>/dev/null || echo "")
|
|
188
200
|
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$' || echo "")
|
package/runtime/pi/bin/ingest.py
CHANGED
|
@@ -154,6 +154,36 @@ def chunks(text: str, limit: int = 7000) -> list[str]:
|
|
|
154
154
|
return result
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
# Catalogue columns that carry provenance, by what they mean rather than by
|
|
158
|
+
# what any one organisation calls them. A profile's `map` already renames its
|
|
159
|
+
# own columns to these, so no further configuration is needed.
|
|
160
|
+
CATALOGUE_URL_FIELDS = ("url", "source_url", "link")
|
|
161
|
+
CATALOGUE_INSTITUTION_FIELDS = ("institution", "publisher", "source_type", "source")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def catalogue_provenance(catalogued: dict) -> tuple:
|
|
165
|
+
"""The URL and institution a catalogue records for a document.
|
|
166
|
+
|
|
167
|
+
The resolver has always accepted these and ranks both above a filename,
|
|
168
|
+
because a filename is what someone happened to save a file as. Ingest
|
|
169
|
+
passed only the filename, so the stronger evidence sitting in the row it
|
|
170
|
+
had just read went unused.
|
|
171
|
+
|
|
172
|
+
Measured on a real archive that was not a marginal loss. Seventy articles
|
|
173
|
+
downloaded from two journal indexes were saved under bare article titles,
|
|
174
|
+
carrying no publisher token to match. The profile had rules for both
|
|
175
|
+
indexes and the catalogue named the index for every one of them, and still
|
|
176
|
+
nothing matched — the one input that could have identified them was the one
|
|
177
|
+
input never passed.
|
|
178
|
+
"""
|
|
179
|
+
if not catalogued:
|
|
180
|
+
return "", ""
|
|
181
|
+
url = next((str(catalogued[f]) for f in CATALOGUE_URL_FIELDS if catalogued.get(f)), "")
|
|
182
|
+
institution = next(
|
|
183
|
+
(str(catalogued[f]) for f in CATALOGUE_INSTITUTION_FIELDS if catalogued.get(f)), "")
|
|
184
|
+
return url, institution
|
|
185
|
+
|
|
186
|
+
|
|
157
187
|
def find_publisher_profile() -> Path | None:
|
|
158
188
|
"""Where this instance's publisher profile is, if it has one.
|
|
159
189
|
|
|
@@ -267,6 +297,17 @@ def add_path(args: argparse.Namespace) -> dict:
|
|
|
267
297
|
)
|
|
268
298
|
repair_map = text_repair.load_mapping(profile or {})
|
|
269
299
|
ingested = unchanged = metadata_updated = skipped = pruned = 0
|
|
300
|
+
# How often progress is written. The manifest used to be saved once, after
|
|
301
|
+
# every file, so an interrupted run lost everything it had extracted — on a
|
|
302
|
+
# 921-document folder that was two and a half hours of OCR with nothing to
|
|
303
|
+
# show. Documents already on disk were re-extracted because nothing recorded
|
|
304
|
+
# them. Writing periodically means an interruption costs at most this many.
|
|
305
|
+
checkpoint_every = int(os.environ.get("EGREGORE_INGEST_CHECKPOINT", "25") or 0)
|
|
306
|
+
since_checkpoint = 0
|
|
307
|
+
# Read before the loop rather than after it, so a checkpoint writes the real
|
|
308
|
+
# deletion records instead of dropping them. Pruning still happens later; a
|
|
309
|
+
# mid-run save simply carries forward what the previous run recorded.
|
|
310
|
+
tombstones = existing_manifest.get("tombstones", [])
|
|
270
311
|
errors: list[dict] = []
|
|
271
312
|
seen_paths: set[str] = set()
|
|
272
313
|
|
|
@@ -390,7 +431,10 @@ def add_path(args: argparse.Namespace) -> dict:
|
|
|
390
431
|
provenance["confidence"] = "high"
|
|
391
432
|
|
|
392
433
|
if profile:
|
|
393
|
-
|
|
434
|
+
catalogue_url, catalogue_institution = catalogue_provenance(catalogued)
|
|
435
|
+
resolved = source_profile.resolve(
|
|
436
|
+
profile, url=catalogue_url, institution=catalogue_institution,
|
|
437
|
+
filename=relative)
|
|
394
438
|
if resolved.publisher_class or resolved.notes:
|
|
395
439
|
inferred = resolved.as_dict()
|
|
396
440
|
# Keep the inference visible even when the catalogue answered,
|
|
@@ -438,12 +482,18 @@ def add_path(args: argparse.Namespace) -> dict:
|
|
|
438
482
|
"status": "unverified",
|
|
439
483
|
"ingested_at": now,
|
|
440
484
|
}
|
|
485
|
+
since_checkpoint += 1
|
|
486
|
+
if checkpoint_every and since_checkpoint >= checkpoint_every:
|
|
487
|
+
# A crash between the document write and this one costs re-extraction
|
|
488
|
+
# of at most `checkpoint_every` files, never the whole run.
|
|
489
|
+
write_manifest(directory, source_id, org, args, boundaries, base, now,
|
|
490
|
+
previous, tombstones, errors)
|
|
491
|
+
since_checkpoint = 0
|
|
441
492
|
if existing and existing.get("content_hash") == content_hash:
|
|
442
493
|
metadata_updated += 1
|
|
443
494
|
else:
|
|
444
495
|
ingested += 1
|
|
445
496
|
|
|
446
|
-
tombstones = existing_manifest.get("tombstones", [])
|
|
447
497
|
if args.prune:
|
|
448
498
|
if not input_path.is_dir():
|
|
449
499
|
raise ValueError("--prune requires a directory snapshot")
|
|
@@ -465,11 +515,23 @@ def add_path(args: argparse.Namespace) -> dict:
|
|
|
465
515
|
# Current documents always win over historical deletion records.
|
|
466
516
|
tombstones = [row for row in tombstones if row["id"] not in previous]
|
|
467
517
|
|
|
518
|
+
write_manifest(directory, source_id, org, args, boundaries, base, now,
|
|
519
|
+
previous, tombstones, errors)
|
|
520
|
+
return {"org": org, "source": source_id, "ingested": ingested, "unchanged": unchanged, "metadata_updated": metadata_updated, "skipped": skipped, "pruned": pruned, "documents": len(previous), "metadata_root": str(directory), "data_root": str(docs_dir)}
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def write_manifest(directory, source_id, org, args, boundaries, base, now,
|
|
524
|
+
previous, tombstones, errors) -> None:
|
|
525
|
+
"""Persist progress. Called periodically during a run and once at the end.
|
|
526
|
+
|
|
527
|
+
One writer for both so a checkpoint and a completed run cannot record the
|
|
528
|
+
corpus differently.
|
|
529
|
+
"""
|
|
468
530
|
source_record = {
|
|
469
531
|
"id": source_id,
|
|
470
532
|
"org": org,
|
|
471
|
-
"name": args
|
|
472
|
-
"kind": args
|
|
533
|
+
"name": getattr(args, "name", None) or source_id,
|
|
534
|
+
"kind": getattr(args, "kind", None),
|
|
473
535
|
"boundaries": boundaries,
|
|
474
536
|
"root_hint": base.name,
|
|
475
537
|
"updated_at": now,
|
|
@@ -486,7 +548,6 @@ def add_path(args: argparse.Namespace) -> dict:
|
|
|
486
548
|
"errors": errors,
|
|
487
549
|
},
|
|
488
550
|
)
|
|
489
|
-
return {"org": org, "source": source_id, "ingested": ingested, "unchanged": unchanged, "metadata_updated": metadata_updated, "skipped": skipped, "pruned": pruned, "documents": len(previous), "metadata_root": str(directory), "data_root": str(docs_dir)}
|
|
490
551
|
|
|
491
552
|
|
|
492
553
|
def cmd_add(args: argparse.Namespace) -> int:
|
|
@@ -494,6 +555,191 @@ def cmd_add(args: argparse.Namespace) -> int:
|
|
|
494
555
|
return 0
|
|
495
556
|
|
|
496
557
|
|
|
558
|
+
def rewrite_frontmatter(path: Path, updates: dict) -> bool:
|
|
559
|
+
"""Replace named frontmatter fields in place, leaving the text untouched.
|
|
560
|
+
|
|
561
|
+
A document's placement lives in two files — the manifest record and this
|
|
562
|
+
document's own frontmatter — and a reader may consult either. Updating one
|
|
563
|
+
and not the other produces a corpus that disagrees with itself, which is
|
|
564
|
+
worse than the stale value it replaced.
|
|
565
|
+
|
|
566
|
+
Only the named lines are rewritten. The body is never reparsed, so nothing
|
|
567
|
+
that survived extraction can be lost by a maintenance pass.
|
|
568
|
+
"""
|
|
569
|
+
try:
|
|
570
|
+
text = path.read_text(encoding="utf-8")
|
|
571
|
+
except OSError:
|
|
572
|
+
return False
|
|
573
|
+
lines = text.split("\n")
|
|
574
|
+
if not lines or lines[0].strip() != "---":
|
|
575
|
+
return False
|
|
576
|
+
try:
|
|
577
|
+
close = next(i for i in range(1, len(lines)) if lines[i].strip() == "---")
|
|
578
|
+
except StopIteration:
|
|
579
|
+
return False
|
|
580
|
+
remaining = dict(updates)
|
|
581
|
+
for i in range(1, close):
|
|
582
|
+
key = lines[i].split(":", 1)[0].strip()
|
|
583
|
+
if key in remaining:
|
|
584
|
+
lines[i] = f"{key}: {json.dumps(remaining.pop(key), ensure_ascii=False)}"
|
|
585
|
+
# A field the original write omitted is appended rather than dropped, so a
|
|
586
|
+
# document ingested before a field existed still gains it.
|
|
587
|
+
for key, value in remaining.items():
|
|
588
|
+
lines.insert(close, f"{key}: {json.dumps(value, ensure_ascii=False)}")
|
|
589
|
+
close += 1
|
|
590
|
+
atomic_text(path, "\n".join(lines))
|
|
591
|
+
return True
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def reresolve_source(args: argparse.Namespace) -> dict:
|
|
595
|
+
"""Re-derive placement for documents already ingested.
|
|
596
|
+
|
|
597
|
+
Placement is written once, at ingest, and never revisited. That is right
|
|
598
|
+
for content — the text of a document does not change because a rule did —
|
|
599
|
+
but wrong for the rules themselves. Correcting a publisher profile changed
|
|
600
|
+
nothing about the corpus it governs: `add` skips on the content hash and
|
|
601
|
+
returns before it ever consults the profile, so it reported every document
|
|
602
|
+
unchanged while the corrected rule went unapplied. The only way to apply a
|
|
603
|
+
fix was to re-extract thousands of documents, redoing OCR to recompute a
|
|
604
|
+
field that depends on nothing but the filename.
|
|
605
|
+
|
|
606
|
+
So this re-runs the resolver alone. No file is reopened, no text is
|
|
607
|
+
re-extracted; the stored path is all the resolver ever needed.
|
|
608
|
+
|
|
609
|
+
Evidence precedence is the same as at ingest, and is the reason this is
|
|
610
|
+
safe to run at any time: a value the operator stated, or one the
|
|
611
|
+
organisation's own catalogue recorded, is never overwritten by an
|
|
612
|
+
inference. Only a placement this resolver derived — or the absence of one —
|
|
613
|
+
is open to revision.
|
|
614
|
+
"""
|
|
615
|
+
config = load_config()
|
|
616
|
+
source_id = slug(args.source)
|
|
617
|
+
directory = source_dir(source_id)
|
|
618
|
+
manifest = load_manifest(directory)
|
|
619
|
+
records = manifest.get("documents") or []
|
|
620
|
+
if not records:
|
|
621
|
+
return {"source": source_id, "error": "no manifest, or no documents in it",
|
|
622
|
+
"metadata_root": str(directory)}
|
|
623
|
+
|
|
624
|
+
profile = publisher_profile()
|
|
625
|
+
if profile is None:
|
|
626
|
+
return {"source": source_id,
|
|
627
|
+
"error": "no publisher-profile.json found — nothing to resolve against"}
|
|
628
|
+
key = source_profile.boundary_key(profile)
|
|
629
|
+
if not key:
|
|
630
|
+
return {"source": source_id, "error": "profile declares no boundary key"}
|
|
631
|
+
|
|
632
|
+
docs_dir = data_root() / "sources" / source_id / "documents"
|
|
633
|
+
placed = rezoned = identified = unchanged = protected = missing = 0
|
|
634
|
+
changes: list = []
|
|
635
|
+
|
|
636
|
+
for record in records:
|
|
637
|
+
relative = record.get("source_path")
|
|
638
|
+
if not relative:
|
|
639
|
+
unchanged += 1
|
|
640
|
+
continue
|
|
641
|
+
boundaries = dict(record.get("boundaries") or {})
|
|
642
|
+
provenance = dict(record.get("provenance") or {})
|
|
643
|
+
catalogued = provenance.get("from_catalogue") or {}
|
|
644
|
+
|
|
645
|
+
# The same evidence the original ingest had, re-read from the record so
|
|
646
|
+
# a document identified only by its catalogue row resolves here too.
|
|
647
|
+
catalogue_url, catalogue_institution = catalogue_provenance(catalogued)
|
|
648
|
+
resolved = source_profile.resolve(
|
|
649
|
+
profile, url=catalogue_url, institution=catalogue_institution,
|
|
650
|
+
filename=relative)
|
|
651
|
+
|
|
652
|
+
# Placement and identification are decided separately. A document can
|
|
653
|
+
# become identifiable — a journal index naming its publisher — without
|
|
654
|
+
# that saying anything about which region it applies to, and reporting
|
|
655
|
+
# only the placement would call that document unchanged when the thing
|
|
656
|
+
# that decides how far to trust it has just been settled.
|
|
657
|
+
current = boundaries.get(key)
|
|
658
|
+
# Anything the operator stated or the catalogue recorded outranks an
|
|
659
|
+
# inference and is left exactly as it is.
|
|
660
|
+
derived_here = (provenance.get("derived") is True
|
|
661
|
+
and provenance.get("applied_boundary") == key)
|
|
662
|
+
zone_open = not ((current and not derived_here) or catalogued.get(key))
|
|
663
|
+
zone_changes = bool(zone_open and resolved.zone and resolved.zone != current)
|
|
664
|
+
|
|
665
|
+
# A tier the catalogue stated is likewise not ours to revise.
|
|
666
|
+
class_open = not catalogued.get("tier")
|
|
667
|
+
class_changes = bool(
|
|
668
|
+
class_open and resolved.publisher_class
|
|
669
|
+
and (resolved.publisher_class != provenance.get("publisher_class")
|
|
670
|
+
or resolved.tier != provenance.get("tier")))
|
|
671
|
+
|
|
672
|
+
if not zone_changes and not class_changes:
|
|
673
|
+
if not zone_open and not class_open:
|
|
674
|
+
protected += 1
|
|
675
|
+
elif current and not zone_open:
|
|
676
|
+
protected += 1
|
|
677
|
+
else:
|
|
678
|
+
unchanged += 1
|
|
679
|
+
continue
|
|
680
|
+
|
|
681
|
+
# The fresh resolution replaces the recorded inference whenever either
|
|
682
|
+
# half moved. Refreshing only on a class change once left a re-zoned
|
|
683
|
+
# document carrying the confidence and notes of the resolution it no
|
|
684
|
+
# longer had — a record that reported an inferred national zone with
|
|
685
|
+
# the confidence of a precise one.
|
|
686
|
+
updated_provenance = dict(provenance)
|
|
687
|
+
updated_provenance.update(resolved.as_dict())
|
|
688
|
+
if class_changes:
|
|
689
|
+
identified += 1
|
|
690
|
+
if catalogued:
|
|
691
|
+
updated_provenance["from_catalogue"] = catalogued
|
|
692
|
+
if zone_changes:
|
|
693
|
+
updated_provenance["applied_boundary"] = key
|
|
694
|
+
updated_provenance["derived"] = True
|
|
695
|
+
boundaries[key] = resolved.zone
|
|
696
|
+
if current:
|
|
697
|
+
rezoned += 1
|
|
698
|
+
else:
|
|
699
|
+
placed += 1
|
|
700
|
+
|
|
701
|
+
changes.append({"id": record["id"], "source_path": relative,
|
|
702
|
+
"zone": {"from": current, "to": boundaries.get(key)},
|
|
703
|
+
"class": {"from": provenance.get("publisher_class"),
|
|
704
|
+
"to": updated_provenance.get("publisher_class")},
|
|
705
|
+
"matched_by": resolved.matched_by})
|
|
706
|
+
|
|
707
|
+
if args.dry_run:
|
|
708
|
+
continue
|
|
709
|
+
record["boundaries"] = boundaries
|
|
710
|
+
record["provenance"] = updated_provenance
|
|
711
|
+
file_path = record.get("file_path")
|
|
712
|
+
target = (data_root() / file_path) if file_path else (docs_dir / f"{record['id']}.md")
|
|
713
|
+
if not rewrite_frontmatter(target, {"boundaries": boundaries,
|
|
714
|
+
"provenance": updated_provenance}):
|
|
715
|
+
missing += 1
|
|
716
|
+
|
|
717
|
+
if not args.dry_run and (placed or rezoned or identified):
|
|
718
|
+
manifest["documents"] = sorted(records, key=lambda d: d["id"])
|
|
719
|
+
atomic_json(directory / "manifest.json", manifest)
|
|
720
|
+
|
|
721
|
+
return {
|
|
722
|
+
"org": slug(config.get("slug") or "egregore"),
|
|
723
|
+
"source": source_id,
|
|
724
|
+
"dry_run": bool(args.dry_run),
|
|
725
|
+
"documents": len(records),
|
|
726
|
+
"placed": placed,
|
|
727
|
+
"rezoned": rezoned,
|
|
728
|
+
"identified": identified,
|
|
729
|
+
"unchanged": unchanged,
|
|
730
|
+
"protected": protected,
|
|
731
|
+
"frontmatter_not_found": missing,
|
|
732
|
+
"examples": changes[: max(0, args.examples)],
|
|
733
|
+
"metadata_root": str(directory),
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def cmd_reresolve(args: argparse.Namespace) -> int:
|
|
738
|
+
result = reresolve_source(args)
|
|
739
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
740
|
+
return 1 if result.get("error") else 0
|
|
741
|
+
|
|
742
|
+
|
|
497
743
|
def selection_relative_path(value: object) -> str:
|
|
498
744
|
if not isinstance(value, str) or not value or "\x00" in value or "\\" in value:
|
|
499
745
|
raise ValueError("selection contains an invalid relative path")
|
|
@@ -719,6 +965,15 @@ def parser() -> argparse.ArgumentParser:
|
|
|
719
965
|
add.add_argument("--boundary", action="append", default=[])
|
|
720
966
|
add.add_argument("--prune", action="store_true", help="tombstone files absent from this authoritative directory snapshot")
|
|
721
967
|
add.set_defaults(func=cmd_add)
|
|
968
|
+
reresolve = sub.add_parser(
|
|
969
|
+
"reresolve",
|
|
970
|
+
help="re-derive placement from the current profile, without re-reading any document")
|
|
971
|
+
reresolve.add_argument("--source", required=True, help="source id to re-resolve")
|
|
972
|
+
reresolve.add_argument("--dry-run", action="store_true",
|
|
973
|
+
help="report what would change and write nothing")
|
|
974
|
+
reresolve.add_argument("--examples", type=int, default=10,
|
|
975
|
+
help="how many changed documents to list (default 10)")
|
|
976
|
+
reresolve.set_defaults(func=cmd_reresolve)
|
|
722
977
|
add_selection = sub.add_parser("add-selection")
|
|
723
978
|
add_selection.add_argument("manifest")
|
|
724
979
|
add_selection.set_defaults(func=cmd_add_selection)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# git-message.sh — shared commit/PR message mechanics, sourced by composers.
|
|
3
|
+
# Spec: .claude/context/commit-format.md + .claude/context/pr-format.md.
|
|
4
|
+
#
|
|
5
|
+
# Provides:
|
|
6
|
+
# egregore_session_trailer <hub_dir>
|
|
7
|
+
# Print "Egregore-Session: <id>" when <hub_dir>/.egregore-session-id
|
|
8
|
+
# exists; print nothing otherwise. Never fails.
|
|
9
|
+
# egregore_commit <git_dir> <hub_dir> <message> [git-commit flags…]
|
|
10
|
+
# git -C <git_dir> commit with the session trailer appended as the
|
|
11
|
+
# final paragraph (a valid git trailer block) when an id exists.
|
|
12
|
+
# egregore_pr_skeleton <what> <why> <verification> <footer>
|
|
13
|
+
# The one source of the skeleton PR body shape.
|
|
14
|
+
|
|
15
|
+
egregore_session_trailer() {
|
|
16
|
+
local sid
|
|
17
|
+
sid="$(cat "${1:-.}/.egregore-session-id" 2>/dev/null || true)"
|
|
18
|
+
[ -n "$sid" ] && printf 'Egregore-Session: %s' "$sid"
|
|
19
|
+
return 0
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
egregore_commit() {
|
|
23
|
+
local git_dir="$1" hub_dir="$2" msg="$3"
|
|
24
|
+
shift 3
|
|
25
|
+
local trailer
|
|
26
|
+
trailer="$(egregore_session_trailer "$hub_dir")"
|
|
27
|
+
if [ -n "$trailer" ]; then
|
|
28
|
+
git -C "$git_dir" commit -m "$msg" -m "$trailer" "$@"
|
|
29
|
+
else
|
|
30
|
+
git -C "$git_dir" commit -m "$msg" "$@"
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
egregore_pr_skeleton() {
|
|
35
|
+
printf '## What\n%s\n\n## Why\n%s\n\n## Verification\n%s\n\n%s\n' \
|
|
36
|
+
"$1" "$2" "$3" "$4"
|
|
37
|
+
}
|
|
@@ -185,7 +185,7 @@ _apply_framework_update() {
|
|
|
185
185
|
FRAMEWORK_UPDATED_COUNT=$(printf '%s\n' "$_changed" | grep -c . || echo 0)
|
|
186
186
|
FRAMEWORK_UPDATED_FILES=$(printf '%s\n' "$_changed" | awk '{print $NF}' | head -3 | tr '\n' ' ')
|
|
187
187
|
git add bin/ .claude/ loom/ CLAUDE.md skills/ 2>/dev/null
|
|
188
|
-
EGREGORE_FRAMEWORK_UPDATE=1 git commit -m "
|
|
188
|
+
EGREGORE_FRAMEWORK_UPDATE=1 git commit -m "chore(sync): update framework from upstream" --quiet 2>/dev/null || true
|
|
189
189
|
FRAMEWORK_UPDATED="true"
|
|
190
190
|
fi
|
|
191
191
|
}
|
|
@@ -214,6 +214,14 @@ fi
|
|
|
214
214
|
# Named setup_develop for continuity; it operates on $BASE_BRANCH, which is
|
|
215
215
|
# "develop" by default and whatever egregore.json's base_branch says otherwise.
|
|
216
216
|
setup_develop() {
|
|
217
|
+
# Detached HEAD (CI merge-ref checkouts, bisects): never create or switch
|
|
218
|
+
# branches here — `git checkout -b $BASE_BRANCH origin/$BASE_BRANCH` would
|
|
219
|
+
# silently replace the checked-out tree with the base branch. This is how
|
|
220
|
+
# CI jobs that invoke session-start machinery ended up testing develop
|
|
221
|
+
# instead of the PR under test. Syncing means updating the base-branch ref,
|
|
222
|
+
# not claiming HEAD; on a detached HEAD there is nothing safe to do.
|
|
223
|
+
[ -n "$(git branch --show-current 2>/dev/null)" ] || return 0
|
|
224
|
+
|
|
217
225
|
# Ensure the base branch exists locally
|
|
218
226
|
if ! git show-ref --verify --quiet "refs/heads/$BASE_BRANCH" 2>/dev/null; then
|
|
219
227
|
if git show-ref --verify --quiet "refs/remotes/origin/$BASE_BRANCH" 2>/dev/null; then
|
|
@@ -267,7 +275,7 @@ setup_develop() {
|
|
|
267
275
|
# Save uncommitted work so nothing is lost (guarded: never auto-commit
|
|
268
276
|
# stray submodule pointers from sibling repos in the checkout)
|
|
269
277
|
git_add_guarded
|
|
270
|
-
git commit -m "
|
|
278
|
+
git commit -m "chore(autosave): save uncommitted work from $CURRENT_BRANCH" --quiet 2>/dev/null || true
|
|
271
279
|
SAVED_BRANCH="$CURRENT_BRANCH"
|
|
272
280
|
fi
|
|
273
281
|
|
|
@@ -40,24 +40,57 @@ function replaceSection(markdown, heading, body) {
|
|
|
40
40
|
return `${markdown.slice(0, start)}${marker}\n\n${body}${markdown.slice(end)}`;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Keep an inherited section and put runtime-specific framing in front of it.
|
|
44
|
+
// Command Awareness is the case that matters: the canonical category rows and
|
|
45
|
+
// disambiguation map live in CLAUDE.md, and a full replace here silently
|
|
46
|
+
// shipped a truncated copy that drifted for months. The Codex spec opens the
|
|
47
|
+
// section with its own skills-not-slash-commands paragraph, which is wrong for
|
|
48
|
+
// a runtime that does have slash commands, so that one paragraph is dropped.
|
|
49
|
+
function prependToSection(markdown, heading, body, dropLeadParagraphStartingWith) {
|
|
50
|
+
const marker = `## ${heading}`;
|
|
51
|
+
const start = markdown.indexOf(marker);
|
|
52
|
+
if (start < 0) throw new Error(`missing section in AGENTS.md: ${heading}`);
|
|
53
|
+
const rest = markdown.slice(start + marker.length);
|
|
54
|
+
const boundary = rest.search(/\n\n---\n\n## /);
|
|
55
|
+
const end = boundary < 0 ? markdown.length : start + marker.length + boundary;
|
|
56
|
+
let inherited = markdown.slice(start + marker.length, end).trim();
|
|
57
|
+
if (dropLeadParagraphStartingWith) {
|
|
58
|
+
if (!inherited.startsWith(dropLeadParagraphStartingWith)) {
|
|
59
|
+
throw new Error(`pi/prime-render-spec: "${heading}" no longer opens with "${dropLeadParagraphStartingWith}" — the Codex wording moved; update this adaptation`);
|
|
60
|
+
}
|
|
61
|
+
inherited = inherited.slice(inherited.indexOf("\n\n") + 2).trim();
|
|
62
|
+
}
|
|
63
|
+
return replaceSection(markdown, heading, `${body}\n\n${inherited}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const CODEX_COMMAND_LEAD = "Codex reserves leading";
|
|
67
|
+
|
|
43
68
|
function adapt(sourceSpec) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.
|
|
56
|
-
.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
// Anchored replacements: every `from` string MUST exist in the generated
|
|
70
|
+
// Codex spec. A missing anchor once no-opped silently (the tool_call gate
|
|
71
|
+
// wording vanished from this file for a whole PR cycle before a test caught
|
|
72
|
+
// it) — now it fails the render loudly so the anchor gets updated alongside
|
|
73
|
+
// the Codex text that moved.
|
|
74
|
+
const ANCHORED = [
|
|
75
|
+
["Egregore on Codex", "Egregore on Pi"],
|
|
76
|
+
["Codex-native", "Pi-native"],
|
|
77
|
+
["Codex sessions", "Pi sessions"],
|
|
78
|
+
["On Codex", "On Pi"],
|
|
79
|
+
["Codex has no", "Pi has no built-in"],
|
|
80
|
+
["bin/codex-session-start.sh", "bin/pi-session-start.sh"],
|
|
81
|
+
[".codex/hooks/branch-guard.js", ".pi/extensions/egregore.ts"],
|
|
82
|
+
["PreToolUse hook (launcher `--enable hooks`)", "`tool_call` gate (loaded after Pi project trust)"],
|
|
83
|
+
["If this Codex build does not support hooks", "If Pi project resources are disabled"],
|
|
84
|
+
["change permissions in `.claude/settings.json` anytime", "review or disable project resources in `.pi/` anytime"],
|
|
85
|
+
];
|
|
86
|
+
let text = sourceSpec;
|
|
87
|
+
for (const [from, to] of ANCHORED) {
|
|
88
|
+
if (!text.includes(from)) {
|
|
89
|
+
throw new Error(`pi-render-spec: anchor not found in generated Codex spec: "${from}" — the Codex wording moved; update this replacement`);
|
|
90
|
+
}
|
|
91
|
+
text = text.replaceAll(from, to);
|
|
92
|
+
}
|
|
93
|
+
text = text
|
|
61
94
|
.replace(/`\$([a-z][a-z0-9-]*)/g, "`/$1")
|
|
62
95
|
.replace(/\$([a-z][a-z0-9-]*)/g, "/$1");
|
|
63
96
|
|
|
@@ -67,16 +100,9 @@ This appendix is generated from the reviewed shell-runtime translation of \`CLAU
|
|
|
67
100
|
|
|
68
101
|
text = replaceSection(text, "On Launch — MANDATORY FIRST ACTION", `The project-local Pi extension renders the Egregore startup card (identity, handoffs, team activity) inside Pi via \`bin/pi-session-start.sh\`. This works for direct \`pi\` launches and launcher-managed sessions. Do not rerun startup checks and do not narrate startup. The card ends with **"What are you working on?"** — that question is already on screen; treat the user's first message as the answer to it. To re-show the card outside Pi, run \`bash bin/pi-session-start.sh --card\`.`);
|
|
69
102
|
|
|
70
|
-
text =
|
|
71
|
-
|
|
72
|
-
Invoke commands from user intent — don't wait for the slash. Read the selected skill's \`SKILL.md\` completely before acting. Codex wording inside the shared portable skills means the shell-capable runtime adapter; in Pi, run referenced \`bin/\` scripts directly, use ordinary Pi shell/network access, ignore Codex approval-channel wording, and render compact numbered questions when no Pi UI question surface is available.
|
|
103
|
+
text = prependToSection(text, "Command Awareness", `Egregore workflows are available as Pi slash commands such as \`/activity\`, \`/handoff\`, \`/save\`, and \`/view\`. They are registered from the shared skill inventory by \`.pi/extensions/egregore.ts\`. Pi also exposes the underlying Agent Skills as \`/skill:name\`, and natural-language requests work normally. \`/save\` is the user-facing abstraction for committing, pushing, opening or reusing pull requests, and syncing memory — never make users manage the git workflow by hand.
|
|
73
104
|
|
|
74
|
-
|
|
75
|
-
**Knowledge** — \`/search\` \`/deep-reflect\` \`/archive\` \`/note\` \`/add\` \`/meeting\` \`/ingest\` \`/scroll\`
|
|
76
|
-
**Identity** — \`/me\`
|
|
77
|
-
**Coordination** — \`/ask\` \`/quest\` \`/issue\` \`/invite\` \`/delete-user\` \`/announce\`
|
|
78
|
-
**Git** — \`/branch\` \`/commit\` \`/push\` \`/pr\` \`/save\` \`/review-pr\` \`/contribute\`
|
|
79
|
-
**Infra** — \`/setup\` \`/update\` \`/pull\` \`/env\` \`/infra\` \`/sync-repos\` \`/release\` \`/checkup\``);
|
|
105
|
+
Read the selected skill's \`SKILL.md\` completely before acting. Codex wording inside the shared portable skills means the shell-capable runtime adapter; in Pi, run referenced \`bin/\` scripts directly, use ordinary Pi shell/network access, ignore Codex approval-channel wording, and render compact numbered questions when no Pi UI question surface is available.`, CODEX_COMMAND_LEAD);
|
|
80
106
|
|
|
81
107
|
return `# Egregore on Pi\n\nThis file is generated by \`bin/pi-render-spec.mjs\`; do not edit it by hand.\n\n${text}\n`;
|
|
82
108
|
}
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
|
|
30
30
|
set -uo pipefail
|
|
31
31
|
|
|
32
|
+
# shellcheck source=bin/lib/git-message.sh
|
|
33
|
+
. "$(cd "$(dirname "$0")" && pwd)/lib/git-message.sh" 2>/dev/null || true
|
|
34
|
+
type egregore_commit >/dev/null 2>&1 || egregore_commit() {
|
|
35
|
+
local gd="$1" m="$3"; shift 3; git -C "$gd" commit -m "$m" "$@"
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
DIR=""
|
|
33
39
|
AUTHOR=""
|
|
34
40
|
DEBOUNCE=0
|
|
@@ -118,7 +124,7 @@ fi
|
|
|
118
124
|
if git -C "$DIR/memory" rev-parse --git-dir >/dev/null 2>&1; then
|
|
119
125
|
if [ -n "$(git -C "$DIR/memory" status --porcelain 2>/dev/null)" ]; then
|
|
120
126
|
git -C "$DIR/memory" add -A >/dev/null 2>&1 || true
|
|
121
|
-
|
|
127
|
+
egregore_commit "$DIR/memory" "$DIR" "chore(autosave): save session work" --quiet 2>/dev/null || true
|
|
122
128
|
fi
|
|
123
129
|
if [ -n "$(git -C "$DIR/memory" log origin/main..HEAD --oneline 2>/dev/null)" ]; then
|
|
124
130
|
for _try in 1 2 3; do
|
|
@@ -232,7 +232,7 @@ _people_file() {
|
|
|
232
232
|
rm -f "$f"
|
|
233
233
|
fi
|
|
234
234
|
( cd "$SCRIPT_DIR/memory" 2>/dev/null && git add -A people/ 2>/dev/null \
|
|
235
|
-
&& git commit -q -m "settings:
|
|
235
|
+
&& git commit -q -m "chore(settings): $op $user in people" 2>/dev/null \
|
|
236
236
|
&& git push -q 2>/dev/null ) || true
|
|
237
237
|
}
|
|
238
238
|
|
package/runtime/pi/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "egregore-pi-meta-harness",
|
|
3
3
|
"policy": "Pi-native lifecycle layer over the available OSS Codex skill corpus",
|
|
4
|
-
"skillCount":
|
|
4
|
+
"skillCount": 47,
|
|
5
5
|
"skills": [
|
|
6
6
|
"activity",
|
|
7
7
|
"announce",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"handoff",
|
|
20
20
|
"harvest",
|
|
21
21
|
"ingest",
|
|
22
|
+
"ingest-corpus",
|
|
22
23
|
"ingest-notion",
|
|
23
24
|
"ingest-user-interview",
|
|
24
25
|
"invite",
|
|
@@ -73,6 +74,7 @@
|
|
|
73
74
|
".codex/skills/handoff/SKILL.md",
|
|
74
75
|
".codex/skills/harvest/QUESTION_PALETTE.md",
|
|
75
76
|
".codex/skills/harvest/SKILL.md",
|
|
77
|
+
".codex/skills/ingest-corpus/SKILL.md",
|
|
76
78
|
".codex/skills/ingest-notion/SKILL.md",
|
|
77
79
|
".codex/skills/ingest-user-interview/SKILL.md",
|
|
78
80
|
".codex/skills/ingest/SKILL.md",
|
|
@@ -144,6 +146,7 @@
|
|
|
144
146
|
"bin/lib/context.sh",
|
|
145
147
|
"bin/lib/dashboard-artifact.sh",
|
|
146
148
|
"bin/lib/ensure-develop.sh",
|
|
149
|
+
"bin/lib/git-message.sh",
|
|
147
150
|
"bin/lib/git-sync.sh",
|
|
148
151
|
"bin/lib/greeting.sh",
|
|
149
152
|
"bin/lib/hash.sh",
|