@tyroneross/build-loop 0.30.3 → 0.34.0
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/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +4 -4
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/AGENTS.md +9 -0
- package/README.md +8 -7
- package/agents/advisor.md +111 -0
- package/agents/alignment-checker.md +11 -0
- package/agents/assessment-orchestrator.md +1 -1
- package/agents/build-orchestrator.md +28 -33
- package/agents/fact-checker.md +1 -1
- package/agents/fix-critique.md +1 -1
- package/agents/independent-auditor.md +1 -1
- package/agents/overfitting-reviewer.md +1 -1
- package/agents/plan-critic.md +5 -4
- package/agents/promotion-reviewer.md +1 -1
- package/agents/retrospective-synthesizer.md +20 -3
- package/agents/scope-auditor.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/self-improvement-architect.md +1 -1
- package/commands/{handoff.md → compose-handoff.md} +4 -4
- package/commands/{optimize.md → optimize-run.md} +1 -1
- package/commands/{agent-rally-point.md → rally-point.md} +3 -3
- package/commands/{research.md → research-run.md} +1 -1
- package/commands/setup-memory.md +32 -0
- package/commands/{plan-verify.md → verify-plan.md} +1 -1
- package/docs/agent-surface-policy.md +38 -20
- package/docs/memory-setup.md +19 -14
- package/hooks/_resolve_python.sh +28 -0
- package/hooks/closeout.sh +58 -0
- package/hooks/git/pre-push +69 -6
- package/hooks/hooks.json +34 -13
- package/hooks/post-push-closeout.sh +73 -0
- package/hooks/session-start-closeout.sh +69 -0
- package/hooks/session-start-memory.sh +27 -0
- package/hooks/session-start-plugin-heal.sh +33 -0
- package/hooks/test_closeout.sh +124 -0
- package/package.json +7 -4
- package/plugin-artifacts/codex/.codex-plugin/plugin.json +41 -0
- package/plugin-artifacts/codex/AGENTS.md +499 -0
- package/plugin-artifacts/codex/BUILD-ARTIFACT.md +5 -0
- package/plugin-artifacts/codex/LICENSE +202 -0
- package/plugin-artifacts/codex/README.md +404 -0
- package/plugin-artifacts/codex/docs/agent-surface-policy.md +63 -0
- package/plugin-artifacts/codex/references/advisor-dispatch-ladder.md +62 -0
- package/plugin-artifacts/codex/references/agent-role-taxonomy.md +101 -0
- package/plugin-artifacts/codex/references/autonomous-and-per-commit-modes.md +161 -0
- package/plugin-artifacts/codex/references/autonomy-config.md +231 -0
- package/plugin-artifacts/codex/references/capability-routing.md +182 -0
- package/plugin-artifacts/codex/references/codex-subagents.md +106 -0
- package/plugin-artifacts/codex/references/coordination-file-template.md +181 -0
- package/plugin-artifacts/codex/references/coordination-rules.md +283 -0
- package/plugin-artifacts/codex/references/dogfood-reload-checkpoint.md +112 -0
- package/plugin-artifacts/codex/references/halt-and-ask-protocol.md +102 -0
- package/plugin-artifacts/codex/references/implementer-envelope-schema.md +302 -0
- package/plugin-artifacts/codex/references/intent-capability-pack.md +257 -0
- package/plugin-artifacts/codex/references/intent-exploration-prompts.md +96 -0
- package/plugin-artifacts/codex/references/leadership.md +72 -0
- package/plugin-artifacts/codex/references/memory-systems.md +238 -0
- package/plugin-artifacts/codex/references/memory.md +297 -0
- package/plugin-artifacts/codex/references/model-tier-mapping.md +257 -0
- package/plugin-artifacts/codex/references/modular-systems-pack.md +96 -0
- package/plugin-artifacts/codex/references/phase-1-assess.md +231 -0
- package/plugin-artifacts/codex/references/phase-2-plan.md +77 -0
- package/plugin-artifacts/codex/references/phase-3-execute.md +47 -0
- package/plugin-artifacts/codex/references/phase-4-review.md +320 -0
- package/plugin-artifacts/codex/references/phase-5-iterate.md +71 -0
- package/plugin-artifacts/codex/references/phase-6-learn.md +56 -0
- package/plugin-artifacts/codex/references/recent-design-structures.md +274 -0
- package/plugin-artifacts/codex/references/research-trigger-policy.md +140 -0
- package/plugin-artifacts/codex/references/runtime-smoke-triggers.md +42 -0
- package/plugin-artifacts/codex/references/self-review.md +234 -0
- package/plugin-artifacts/codex/references/single-writer-commit-protocol.md +90 -0
- package/plugin-artifacts/codex/references/task-capture-policy.md +68 -0
- package/plugin-artifacts/codex/references/ui-io-contract.md +116 -0
- package/plugin-artifacts/codex/references/ui-spotcheck-protocol.md +65 -0
- package/plugin-artifacts/codex/skills/build-loop/SKILL.md +372 -0
- package/plugin-artifacts/codex/skills/build-loop/detect-plugins.mjs +82 -0
- package/plugin-artifacts/codex/skills/build-loop/eval-guide.md +65 -0
- package/plugin-artifacts/codex/skills/build-loop/fallbacks.md +549 -0
- package/plugin-artifacts/codex/skills/build-loop/phases/fact-check.md +42 -0
- package/plugin-artifacts/codex/skills/build-loop/phases/ui-validation.md +267 -0
- package/plugin-artifacts/codex/skills/build-loop/references/advisor-dispatch-ladder.md +62 -0
- package/plugin-artifacts/codex/skills/build-loop/references/apple-native-planning.md +439 -0
- package/plugin-artifacts/codex/skills/build-loop/references/autonomous-and-per-commit-modes.md +161 -0
- package/plugin-artifacts/codex/skills/build-loop/references/capability-routing.md +182 -0
- package/plugin-artifacts/codex/skills/build-loop/references/codex-subagents.md +106 -0
- package/plugin-artifacts/codex/skills/build-loop/references/coordination.md +161 -0
- package/plugin-artifacts/codex/skills/build-loop/references/correction-aware-capture.md +177 -0
- package/plugin-artifacts/codex/skills/build-loop/references/independent-auditor.md +72 -0
- package/plugin-artifacts/codex/skills/build-loop/references/intent-capability-pack.md +257 -0
- package/plugin-artifacts/codex/skills/build-loop/references/intent-exploration-prompts.md +96 -0
- package/plugin-artifacts/codex/skills/build-loop/references/leadership.md +72 -0
- package/plugin-artifacts/codex/skills/build-loop/references/memory.md +297 -0
- package/plugin-artifacts/codex/skills/build-loop/references/modular-systems-pack.md +96 -0
- package/plugin-artifacts/codex/skills/build-loop/references/output-style.md +129 -0
- package/plugin-artifacts/codex/skills/build-loop/references/pay-it-forward-arch.md +98 -0
- package/plugin-artifacts/codex/skills/build-loop/references/phase-1-assess.md +231 -0
- package/plugin-artifacts/codex/skills/build-loop/references/phase-2-plan.md +77 -0
- package/plugin-artifacts/codex/skills/build-loop/references/phase-3-execute.md +47 -0
- package/plugin-artifacts/codex/skills/build-loop/references/phase-4-review.md +320 -0
- package/plugin-artifacts/codex/skills/build-loop/references/phase-5-iterate.md +71 -0
- package/plugin-artifacts/codex/skills/build-loop/references/phase-6-learn.md +56 -0
- package/plugin-artifacts/codex/skills/build-loop/references/recent-design-structures.md +274 -0
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
- package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
- package/plugin-artifacts/codex/skills/build-loop/references/self-recursive-dev.md +77 -0
- package/plugin-artifacts/codex/skills/build-loop/references/self-review.md +234 -0
- package/plugin-artifacts/codex/skills/build-loop/references/ui-io-contract.md +116 -0
- package/plugin-artifacts/codex/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
- package/plugin-artifacts/codex/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
- package/plugin-artifacts/codex/skills/build-loop/templates/backlog-item.md +35 -0
- package/plugin-artifacts/codex/skills/build-loop/templates/codex-worker-prompt.md +96 -0
- package/plugin-artifacts/codex/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
- package/plugin-artifacts/codex/skills/build-loop/templates/ux-fix-plan.md +40 -0
- package/scripts/_paths.py +59 -13
- package/scripts/build_codex_plugin_artifact.py +314 -0
- package/scripts/check_cache_sync.py +1 -1
- package/scripts/install_memory.py +11 -3
- package/scripts/prune_plugin_cache.py +225 -11
- package/scripts/sync_navgator_lessons.py +25 -0
- package/scripts/sync_plugin_cache.py +66 -36
- package/skills/agent-rally-point/SKILL.md +1 -1
- package/skills/agent-rally-watcher/SKILL.md +1 -1
- package/skills/build-loop/SKILL.md +11 -155
- package/skills/build-loop/references/advisor-dispatch-ladder.md +62 -0
- package/skills/build-loop/references/autonomous-and-per-commit-modes.md +161 -0
- package/skills/build-loop/references/capability-routing.md +4 -3
- package/skills/build-loop/references/codex-subagents.md +8 -0
- package/skills/build-loop/references/coordination.md +1 -1
- package/skills/build-loop/references/intent-capability-pack.md +88 -1
- package/skills/build-loop/references/memory.md +17 -2
- package/skills/build-loop/references/phase-1-assess.md +13 -0
- package/skills/build-loop/references/phase-2-plan.md +1 -1
- package/skills/build-loop/references/phase-4-review.md +12 -0
- package/skills/build-loop/references/phase-6-learn.md +1 -1
- package/skills/build-loop/references/self-review.md +4 -2
- package/skills/build-loop/templates/codex-worker-prompt.md +41 -34
- package/skills/handoff/SKILL.md +3 -3
- package/skills/loop-builder/SKILL.md +98 -0
- package/skills/loop-builder/presets/active-project-evidence.yaml +97 -0
- package/skills/loop-builder/presets/generic-artifact-loop.yaml +95 -0
- package/skills/loop-builder/presets/presentation-audit.yaml +96 -0
- package/skills/loop-builder/presets/research-synthesis.yaml +96 -0
- package/skills/loop-builder/presets/source-ingestion-raw-data-audit.yaml +96 -0
- package/skills/loop-builder/references/spec-format.md +80 -0
- package/skills/loop-builder/scripts/loop_builder.py +346 -0
- package/skills/model-tiering/SKILL.md +65 -40
- package/skills/optimize/SKILL.md +2 -2
- package/skills/plan-verify/SKILL.md +1 -1
- package/skills/plugin-builder/references/distribution.md +8 -0
- package/skills/research/SKILL.md +33 -6
- package/skills/runtime-parity-verification/SKILL.md +51 -0
- package/skills/spec-writing/SKILL.md +19 -0
- package/templates/memory/README.md +65 -0
- package/templates/memory/charter.md.template +46 -0
- package/templates/memory/manifest.json +24 -4
- package/codex-skills/debug-loop/SKILL.md +0 -18
- package/codex-skills/handoff/SKILL.md +0 -22
- package/codex-skills/knowledge/SKILL.md +0 -21
- package/codex-skills/optimize/SKILL.md +0 -18
- package/codex-skills/research/SKILL.md +0 -18
- /package/commands/{knowledge-review.md → review-knowledge.md} +0 -0
package/scripts/_paths.py
CHANGED
|
@@ -11,7 +11,14 @@ Environment variable contract:
|
|
|
11
11
|
- ``$BUILD_LOOP_MEMORY_STORE_ROOT``: override the build-loop-memory root.
|
|
12
12
|
- ``$BUILD_LOOP_MEMORY_ROOT``: compatibility override for the same root.
|
|
13
13
|
- ``$AGENT_MEMORY_ROOT`` : compatibility override for the same root.
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
Default resolution (when no env override is set), in order:
|
|
16
|
+
1. If the legacy personal root ``~/dev/git-folder/build-loop-memory``
|
|
17
|
+
EXISTS on disk, keep using it. This makes machines that predate the
|
|
18
|
+
neutral default keep working with zero configuration.
|
|
19
|
+
2. Otherwise use the neutral per-user default ``~/.build-loop-memory``
|
|
20
|
+
(``.<toolname>`` storage convention). Fresh installs land here — no
|
|
21
|
+
author-specific directory layout is ever encoded.
|
|
15
22
|
- ``$AGENT_MEMORY_SCHEMA`` : override the default Postgres schema.
|
|
16
23
|
Defaults to ``personal_memory``.
|
|
17
24
|
- ``$AGENT_MEMORY_DUAL_WRITE``: when set to ``"1"``, writers must produce
|
|
@@ -42,14 +49,27 @@ from pathlib import Path
|
|
|
42
49
|
LEGACY_SCHEMA = "build_loop_memory"
|
|
43
50
|
LEGACY_DECISIONS_REL = ".episodic/decisions"
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
# Neutral per-user default for fresh installs. Matches the `.<toolname>`
|
|
53
|
+
# storage convention. NOTE: this is the HYPHENATED `~/.build-loop-memory`,
|
|
54
|
+
# deliberately NOT the retired `~/.build-loop/memory` (see the legacy-root
|
|
55
|
+
# note further down — that exact path has migration tooling that would fight
|
|
56
|
+
# a new write there).
|
|
57
|
+
NEUTRAL_MEMORY_STORE_ROOT = "~/.build-loop-memory"
|
|
58
|
+
# Legacy personal default. Predates the neutral default; kept as the resolved
|
|
59
|
+
# root when it already exists on disk so existing machines need zero config.
|
|
60
|
+
LEGACY_PERSONAL_MEMORY_STORE_ROOT = "~/dev/git-folder/build-loop-memory"
|
|
61
|
+
# Back-compat constant name. `DEFAULT_MEMORY_STORE_ROOT` now names the NEUTRAL
|
|
62
|
+
# default (what a fresh install gets). Resolution at call time still prefers an
|
|
63
|
+
# existing legacy root — see `memory_store_root()`.
|
|
64
|
+
DEFAULT_MEMORY_STORE_ROOT = NEUTRAL_MEMORY_STORE_ROOT
|
|
46
65
|
DEFAULT_AGENT_MEMORY_ROOT = DEFAULT_MEMORY_STORE_ROOT
|
|
47
66
|
DEFAULT_SCHEMA = "personal_memory"
|
|
48
67
|
|
|
49
68
|
CUTOVER_LOCK_PATH = "/tmp/agent-memory-cutover.lock"
|
|
50
69
|
|
|
51
70
|
# Historical name retained as a compatibility alias. The active root is now
|
|
52
|
-
# the sibling `build-loop-memory` repository
|
|
71
|
+
# the sibling `build-loop-memory` repository (or the neutral fresh-install
|
|
72
|
+
# default), not `~/.build-loop/memory`.
|
|
53
73
|
DEFAULT_BUILD_LOOP_MEMORY_ROOT = DEFAULT_MEMORY_STORE_ROOT
|
|
54
74
|
|
|
55
75
|
# Sub-component patterns: paths under a project that count as a distinct
|
|
@@ -61,18 +81,31 @@ SUBCOMPONENT_PATTERNS: tuple[str, ...] = ("workers",)
|
|
|
61
81
|
def memory_store_root() -> Path:
|
|
62
82
|
"""Return the canonical build-loop-memory root.
|
|
63
83
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
Resolution order:
|
|
85
|
+
1. ``$BUILD_LOOP_MEMORY_STORE_ROOT`` / ``$BUILD_LOOP_MEMORY_ROOT`` /
|
|
86
|
+
``$AGENT_MEMORY_ROOT`` env override (first set wins, ``~`` expanded).
|
|
87
|
+
2. The legacy personal default ``~/dev/git-folder/build-loop-memory``
|
|
88
|
+
when it EXISTS on disk — keeps pre-neutral-default machines working
|
|
89
|
+
with zero config.
|
|
90
|
+
3. The neutral per-user default ``~/.build-loop-memory`` for fresh
|
|
91
|
+
installs.
|
|
92
|
+
|
|
93
|
+
The returned path is not required to exist (only the legacy branch checks
|
|
94
|
+
existence); callers that need the directory should create it.
|
|
68
95
|
"""
|
|
69
|
-
|
|
96
|
+
env_raw = (
|
|
70
97
|
os.environ.get("BUILD_LOOP_MEMORY_STORE_ROOT")
|
|
71
98
|
or os.environ.get("BUILD_LOOP_MEMORY_ROOT")
|
|
72
99
|
or os.environ.get("AGENT_MEMORY_ROOT")
|
|
73
|
-
or DEFAULT_MEMORY_STORE_ROOT
|
|
74
100
|
)
|
|
75
|
-
|
|
101
|
+
if env_raw:
|
|
102
|
+
return Path(os.path.expanduser(env_raw))
|
|
103
|
+
|
|
104
|
+
legacy = Path(os.path.expanduser(LEGACY_PERSONAL_MEMORY_STORE_ROOT))
|
|
105
|
+
if legacy.exists():
|
|
106
|
+
return legacy
|
|
107
|
+
|
|
108
|
+
return Path(os.path.expanduser(NEUTRAL_MEMORY_STORE_ROOT))
|
|
76
109
|
|
|
77
110
|
|
|
78
111
|
def agent_memory_root() -> Path:
|
|
@@ -149,6 +182,11 @@ def project_lessons_dir(project: str) -> Path:
|
|
|
149
182
|
return project_root(project) / "lessons"
|
|
150
183
|
|
|
151
184
|
|
|
185
|
+
def project_research_dir(project: str) -> Path:
|
|
186
|
+
"""Return ``<project_root(project)>/research`` for pre-decision research packets."""
|
|
187
|
+
return project_root(project) / "research"
|
|
188
|
+
|
|
189
|
+
|
|
152
190
|
def project_raw_dir(project: str) -> Path:
|
|
153
191
|
"""Return ``<project_root(project)>/raw`` for verbatim source material."""
|
|
154
192
|
return project_root(project) / "raw"
|
|
@@ -263,9 +301,11 @@ def cutover_lock_active() -> bool:
|
|
|
263
301
|
# ---------------------------------------------------------------------------
|
|
264
302
|
# Build-loop memory paths (global + per-project segmentation).
|
|
265
303
|
#
|
|
266
|
-
#
|
|
267
|
-
#
|
|
268
|
-
#
|
|
304
|
+
# HISTORICAL (the `~/.build-loop/memory/` paths below are RETIRED — do not
|
|
305
|
+
# reintroduce them as a default). The memory-consolidation PR series
|
|
306
|
+
# (2026-05-13) originally rooted the store at `~/.build-loop/memory/`:
|
|
307
|
+
# - ~/.build-loop/memory/ global root
|
|
308
|
+
# - ~/.build-loop/memory/projects/<slug>/ project-scoped lessons
|
|
269
309
|
# - ~/.build-loop/memory/projects/_archive/<slug>/ retired projects
|
|
270
310
|
#
|
|
271
311
|
# PR 1 (read-path tolerance, 2026-05-13) added the projects/<slug>/ tier
|
|
@@ -274,6 +314,12 @@ def cutover_lock_active() -> bool:
|
|
|
274
314
|
# is now the only path read. Slug derivation reuses _safe_project_tag so
|
|
275
315
|
# the same normalization applies to filesystem dirs AND the Postgres
|
|
276
316
|
# semantic_facts.project column (no split-brain by construction).
|
|
317
|
+
#
|
|
318
|
+
# The ACTIVE root is whatever `memory_store_root()` returns: an env override,
|
|
319
|
+
# else the legacy personal `~/dev/git-folder/build-loop-memory` if present,
|
|
320
|
+
# else the neutral fresh-install default `~/.build-loop-memory` (hyphenated,
|
|
321
|
+
# distinct from the retired `~/.build-loop/memory`). All helpers below build
|
|
322
|
+
# on `memory_store_root()`, so they follow that resolution automatically.
|
|
277
323
|
# ---------------------------------------------------------------------------
|
|
278
324
|
|
|
279
325
|
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
"""Build the slim Codex plugin artifact.
|
|
5
|
+
|
|
6
|
+
The source repo keeps Claude's full internal skill tree at ``skills/``. Codex
|
|
7
|
+
auto-discovers root ``skills/*/SKILL.md`` entries, so the Codex marketplace must
|
|
8
|
+
install from an artifact that exposes exactly one public skill.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import filecmp
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import shutil
|
|
18
|
+
import tempfile
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Iterable
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
DEFAULT_TARGET = Path("plugin-artifacts/codex")
|
|
24
|
+
IGNORED_NAMES = {".DS_Store", "__pycache__", ".pytest_cache"}
|
|
25
|
+
IGNORED_SUFFIXES = {".pyc"}
|
|
26
|
+
TOP_LEVEL_FILES = ("AGENTS.md", "README.md", "LICENSE")
|
|
27
|
+
|
|
28
|
+
# Bundle markdown points readers at ``references/<file>.md`` (root-relative).
|
|
29
|
+
# The Claude source resolves that logical namespace across the repo's top-level
|
|
30
|
+
# ``references/`` plus each skill's own ``references/``. The slim Codex bundle
|
|
31
|
+
# ships only the ``build-loop`` skill, so those pointers dangle unless we mirror
|
|
32
|
+
# every cited reference into ONE top-level ``references/`` dir at the bundle
|
|
33
|
+
# root — the natural resolution point for both AGENTS.md (at root) and any
|
|
34
|
+
# deeper file an LLM reads. Search order resolves drift deterministically: the
|
|
35
|
+
# skill's own copy wins (it's the one already shipped in the skill tree), then
|
|
36
|
+
# the repo root, then the ui-design skill.
|
|
37
|
+
REFERENCE_SOURCE_DIRS = (
|
|
38
|
+
Path("skills") / "build-loop" / "references",
|
|
39
|
+
Path("references"),
|
|
40
|
+
Path("skills") / "ui-design" / "references",
|
|
41
|
+
)
|
|
42
|
+
# A bare ``references/X.md`` pointer. Anchored to a path boundary so we don't
|
|
43
|
+
# match foreign-skill prose like ``build-loop:deepagents`` references — those
|
|
44
|
+
# are always written with the skill name in front (``The skill's
|
|
45
|
+
# `references/anti-patterns.md```) and resolve inside that other skill, which
|
|
46
|
+
# the slim bundle does not ship.
|
|
47
|
+
_REFERENCE_POINTER_RE = re.compile(r"(?:^|[\s`(\[])references/([A-Za-z0-9_.-]+\.md)")
|
|
48
|
+
# Pointers that name another skill's references dir in prose, not a build-loop
|
|
49
|
+
# bundle file. They have no build-loop source and must not fail the check.
|
|
50
|
+
FOREIGN_SKILL_REFERENCES = frozenset(
|
|
51
|
+
{
|
|
52
|
+
"anti-patterns.md", # build-loop:building-with-deepagents
|
|
53
|
+
"stack-templates.md", # build-loop:logging-tracer
|
|
54
|
+
"ios-notification-alarm-playbook.md", # build-loop:debugging-memory
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
# Pre-existing, deliberate forward-reference placeholders in the source tree
|
|
58
|
+
# (marked TBD in prose). They have no file anywhere — in the Claude tree either.
|
|
59
|
+
# Not codex-introduced; fixing them is out of scope for the artifact builder, so
|
|
60
|
+
# they are tolerated rather than blocking every bundle build. Keep this list
|
|
61
|
+
# tight: a NEW unresolvable pointer on the primary surface must still fail.
|
|
62
|
+
KNOWN_TBD_REFERENCES = frozenset(
|
|
63
|
+
{
|
|
64
|
+
"brief-filters.md", # references/implementer-envelope-schema.md: "(TBD)"
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cited_reference_basenames(*roots: Path) -> set[str]:
|
|
70
|
+
"""Every bare ``references/<file>.md`` pointer across the given markdown roots."""
|
|
71
|
+
cited: set[str] = set()
|
|
72
|
+
for root in roots:
|
|
73
|
+
for md in root.rglob("*.md"):
|
|
74
|
+
cited.update(
|
|
75
|
+
_REFERENCE_POINTER_RE.findall(
|
|
76
|
+
md.read_text(encoding="utf-8", errors="ignore")
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
return cited
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def resolve_reference_source(source: Path, basename: str) -> Path | None:
|
|
83
|
+
"""Locate a cited reference file in the build-loop source search paths."""
|
|
84
|
+
for rel_dir in REFERENCE_SOURCE_DIRS:
|
|
85
|
+
candidate = source / rel_dir / basename
|
|
86
|
+
if candidate.is_file():
|
|
87
|
+
return candidate
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def mirror_references(source: Path, bundle_root: Path) -> None:
|
|
92
|
+
"""Copy the transitive closure of cited references into ``<bundle>/references/``.
|
|
93
|
+
|
|
94
|
+
Iterates to a fixpoint: a mirrored reference file may itself cite further
|
|
95
|
+
``references/X.md``, so re-scan after each copy until no new resolvable
|
|
96
|
+
pointer appears. Makes every root-relative ``references/X.md`` pointer in the
|
|
97
|
+
bundle resolve at the bundle root. Foreign-skill and known-TBD pointers are
|
|
98
|
+
skipped (no build-loop source); a genuinely unexpected unresolvable pointer
|
|
99
|
+
is left for ``check_reference_pointers`` to adjudicate by surface.
|
|
100
|
+
"""
|
|
101
|
+
out_dir = bundle_root / "references"
|
|
102
|
+
copied: set[str] = set()
|
|
103
|
+
while True:
|
|
104
|
+
cited = cited_reference_basenames(bundle_root)
|
|
105
|
+
new_resolvable = False
|
|
106
|
+
for basename in sorted(cited - copied):
|
|
107
|
+
copied.add(basename)
|
|
108
|
+
if basename in FOREIGN_SKILL_REFERENCES or basename in KNOWN_TBD_REFERENCES:
|
|
109
|
+
continue
|
|
110
|
+
src_file = resolve_reference_source(source, basename)
|
|
111
|
+
if src_file is not None:
|
|
112
|
+
copy_file(src_file, out_dir / basename)
|
|
113
|
+
new_resolvable = True
|
|
114
|
+
if not new_resolvable:
|
|
115
|
+
break
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def check_reference_pointers(bundle_root: Path) -> None:
|
|
119
|
+
"""Assert every ``references/X.md`` pointer on the bundle's PRIMARY surface resolves.
|
|
120
|
+
|
|
121
|
+
Primary surface = the files a Codex user actually lands on: top-level
|
|
122
|
+
AGENTS.md / README.md and the whole ``skills/build-loop`` tree. Resolution
|
|
123
|
+
target is the mirrored top-level ``<bundle>/references/``. Foreign-skill and
|
|
124
|
+
known-TBD pointers are allowlisted. A dangling primary-surface pointer is a
|
|
125
|
+
real regression and fails the build. Pointers that appear ONLY inside
|
|
126
|
+
transitively-mirrored reference files (often forward TBDs) are tolerated —
|
|
127
|
+
fixing unrelated source dead-links is not the artifact builder's job.
|
|
128
|
+
"""
|
|
129
|
+
refs_dir = bundle_root / "references"
|
|
130
|
+
surface_roots = [bundle_root / "skills" / "build-loop"]
|
|
131
|
+
surface_roots += [
|
|
132
|
+
bundle_root / name for name in ("AGENTS.md", "README.md") if (bundle_root / name).is_file()
|
|
133
|
+
]
|
|
134
|
+
# rglob on a file path yields nothing; scan top-level files explicitly.
|
|
135
|
+
cited: set[str] = set()
|
|
136
|
+
for root in surface_roots:
|
|
137
|
+
if root.is_dir():
|
|
138
|
+
cited |= cited_reference_basenames(root)
|
|
139
|
+
elif root.is_file():
|
|
140
|
+
cited |= set(
|
|
141
|
+
_REFERENCE_POINTER_RE.findall(root.read_text(encoding="utf-8", errors="ignore"))
|
|
142
|
+
)
|
|
143
|
+
dangling = sorted(
|
|
144
|
+
b
|
|
145
|
+
for b in cited
|
|
146
|
+
if b not in FOREIGN_SKILL_REFERENCES
|
|
147
|
+
and b not in KNOWN_TBD_REFERENCES
|
|
148
|
+
and not (refs_dir / b).is_file()
|
|
149
|
+
)
|
|
150
|
+
if dangling:
|
|
151
|
+
raise ArtifactError(
|
|
152
|
+
"dangling reference pointers on the primary surface (no file at "
|
|
153
|
+
"bundle references/): " + ", ".join(dangling)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ArtifactError(RuntimeError):
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def ignore_generated(_dir: str, names: list[str]) -> set[str]:
|
|
162
|
+
ignored = set()
|
|
163
|
+
for name in names:
|
|
164
|
+
if name in IGNORED_NAMES:
|
|
165
|
+
ignored.add(name)
|
|
166
|
+
continue
|
|
167
|
+
if any(name.endswith(suffix) for suffix in IGNORED_SUFFIXES):
|
|
168
|
+
ignored.add(name)
|
|
169
|
+
return ignored
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def copy_file(source: Path, target: Path) -> None:
|
|
173
|
+
if not source.is_file():
|
|
174
|
+
raise ArtifactError(f"missing required file: {source}")
|
|
175
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
176
|
+
shutil.copy2(source, target)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def copy_tree(source: Path, target: Path) -> None:
|
|
180
|
+
if not source.is_dir():
|
|
181
|
+
raise ArtifactError(f"missing required directory: {source}")
|
|
182
|
+
shutil.copytree(source, target, ignore=ignore_generated)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def write_codex_manifest(source: Path, target: Path) -> None:
|
|
186
|
+
manifest_path = source / ".codex-plugin" / "plugin.json"
|
|
187
|
+
if not manifest_path.is_file():
|
|
188
|
+
raise ArtifactError(f"missing required file: {manifest_path}")
|
|
189
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
190
|
+
manifest["skills"] = "./skills"
|
|
191
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
192
|
+
target.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def write_notice(target: Path) -> None:
|
|
196
|
+
target.write_text(
|
|
197
|
+
"\n".join(
|
|
198
|
+
[
|
|
199
|
+
"# Codex Build Loop Artifact",
|
|
200
|
+
"",
|
|
201
|
+
"Generated by `python3 scripts/build_codex_plugin_artifact.py`.",
|
|
202
|
+
"This artifact is the Codex marketplace install surface.",
|
|
203
|
+
"It exposes exactly one public skill: `build-loop`.",
|
|
204
|
+
"",
|
|
205
|
+
]
|
|
206
|
+
),
|
|
207
|
+
encoding="utf-8",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def skill_files(root: Path) -> list[Path]:
|
|
212
|
+
return sorted(root.rglob("SKILL.md"))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def validate_artifact(target: Path) -> None:
|
|
216
|
+
manifest_path = target / ".codex-plugin" / "plugin.json"
|
|
217
|
+
skills_root = target / "skills"
|
|
218
|
+
if not manifest_path.is_file():
|
|
219
|
+
raise ArtifactError(f"artifact missing {manifest_path}")
|
|
220
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
221
|
+
if manifest.get("skills") != "./skills":
|
|
222
|
+
raise ArtifactError("artifact Codex manifest must use skills=./skills")
|
|
223
|
+
|
|
224
|
+
files = skill_files(skills_root)
|
|
225
|
+
rel_files = [str(path.relative_to(target)) for path in files]
|
|
226
|
+
if rel_files != ["skills/build-loop/SKILL.md"]:
|
|
227
|
+
raise ArtifactError(f"artifact must expose exactly one SKILL.md, got {rel_files}")
|
|
228
|
+
|
|
229
|
+
check_reference_pointers(target)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def build_artifact(source: Path, target: Path) -> None:
|
|
233
|
+
source = source.resolve()
|
|
234
|
+
target = target.resolve()
|
|
235
|
+
if source == target or target in source.parents:
|
|
236
|
+
raise ArtifactError(f"refusing to write artifact above source: {target}")
|
|
237
|
+
|
|
238
|
+
parent = target.parent
|
|
239
|
+
parent.mkdir(parents=True, exist_ok=True)
|
|
240
|
+
with tempfile.TemporaryDirectory(prefix=".codex-artifact-", dir=str(parent)) as tmp_raw:
|
|
241
|
+
tmp = Path(tmp_raw)
|
|
242
|
+
write_codex_manifest(source, tmp / ".codex-plugin" / "plugin.json")
|
|
243
|
+
for name in TOP_LEVEL_FILES:
|
|
244
|
+
copy_file(source / name, tmp / name)
|
|
245
|
+
copy_tree(source / "skills" / "build-loop", tmp / "skills" / "build-loop")
|
|
246
|
+
copy_file(source / "docs" / "agent-surface-policy.md", tmp / "docs" / "agent-surface-policy.md")
|
|
247
|
+
write_notice(tmp / "BUILD-ARTIFACT.md")
|
|
248
|
+
# Mirror cited references AFTER the skill tree + top-level files exist so
|
|
249
|
+
# the pointer scan sees every citing file.
|
|
250
|
+
mirror_references(source, tmp)
|
|
251
|
+
validate_artifact(tmp)
|
|
252
|
+
|
|
253
|
+
if target.exists():
|
|
254
|
+
shutil.rmtree(target)
|
|
255
|
+
os.replace(tmp, target)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def iter_files(root: Path) -> Iterable[Path]:
|
|
259
|
+
for path in sorted(root.rglob("*")):
|
|
260
|
+
if path.is_file():
|
|
261
|
+
yield path
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def assert_same_tree(expected: Path, actual: Path) -> None:
|
|
265
|
+
expected_files = {path.relative_to(expected) for path in iter_files(expected)}
|
|
266
|
+
actual_files = {path.relative_to(actual) for path in iter_files(actual)}
|
|
267
|
+
if expected_files != actual_files:
|
|
268
|
+
missing = sorted(str(path) for path in expected_files - actual_files)
|
|
269
|
+
extra = sorted(str(path) for path in actual_files - expected_files)
|
|
270
|
+
raise ArtifactError(f"artifact file set differs; missing={missing}, extra={extra}")
|
|
271
|
+
for rel in sorted(expected_files):
|
|
272
|
+
if not filecmp.cmp(expected / rel, actual / rel, shallow=False):
|
|
273
|
+
raise ArtifactError(f"artifact file differs: {rel}")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def check_artifact(source: Path, target: Path) -> None:
|
|
277
|
+
parent = target.resolve().parent
|
|
278
|
+
parent.mkdir(parents=True, exist_ok=True)
|
|
279
|
+
with tempfile.TemporaryDirectory(prefix=".codex-artifact-check-", dir=str(parent)) as tmp_raw:
|
|
280
|
+
tmp = Path(tmp_raw) / "codex"
|
|
281
|
+
build_artifact(source, tmp)
|
|
282
|
+
assert_same_tree(tmp, target.resolve())
|
|
283
|
+
validate_artifact(target.resolve())
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
287
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
288
|
+
parser.add_argument("--source", default=".", help="Build Loop source repo root.")
|
|
289
|
+
parser.add_argument("--target", default=str(DEFAULT_TARGET), help="Codex artifact output directory.")
|
|
290
|
+
parser.add_argument("--check", action="store_true", help="Fail if the artifact is missing or stale.")
|
|
291
|
+
return parser.parse_args(argv)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def main(argv: list[str] | None = None) -> int:
|
|
295
|
+
args = parse_args(argv)
|
|
296
|
+
source = Path(args.source).expanduser().resolve()
|
|
297
|
+
target = Path(args.target).expanduser()
|
|
298
|
+
if not target.is_absolute():
|
|
299
|
+
target = source / target
|
|
300
|
+
try:
|
|
301
|
+
if args.check:
|
|
302
|
+
check_artifact(source, target)
|
|
303
|
+
print(f"codex artifact up to date: {target}")
|
|
304
|
+
else:
|
|
305
|
+
build_artifact(source, target)
|
|
306
|
+
print(f"codex artifact written: {target}")
|
|
307
|
+
return 0
|
|
308
|
+
except ArtifactError as exc:
|
|
309
|
+
print(f"error: {exc}")
|
|
310
|
+
return 1
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
if __name__ == "__main__":
|
|
314
|
+
raise SystemExit(main())
|
|
@@ -44,7 +44,7 @@ CODEX_DEFAULT_MARKETPLACE = os.environ.get(
|
|
|
44
44
|
"local-marketplace",
|
|
45
45
|
)
|
|
46
46
|
COORDINATION_EXACT_REFS = (
|
|
47
|
-
"commands/
|
|
47
|
+
"commands/rally-point.md",
|
|
48
48
|
"references/rally-point-protocol.md",
|
|
49
49
|
"references/coordination-rules.md",
|
|
50
50
|
"references/multi-session-coordination.md",
|
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
"""install_memory.py — bootstrap the build-loop memory directory.
|
|
5
5
|
|
|
6
|
-
Creates the canonical build-loop-memory root
|
|
7
|
-
`~/dev/git-folder/build-loop-memory`) and seeds it with template
|
|
6
|
+
Creates the canonical build-loop-memory root and seeds it with template
|
|
8
7
|
`constitution.md` + `MEMORY.md` if either is missing. Idempotent — never
|
|
9
8
|
overwrites existing files.
|
|
10
9
|
|
|
10
|
+
The root is resolved by `_paths.memory_store_root()`: an env override
|
|
11
|
+
(`$BUILD_LOOP_MEMORY_STORE_ROOT` / `$BUILD_LOOP_MEMORY_ROOT` /
|
|
12
|
+
`$AGENT_MEMORY_ROOT`), else a pre-existing legacy
|
|
13
|
+
`~/dev/git-folder/build-loop-memory` on disk, else the neutral fresh-install
|
|
14
|
+
default `~/.build-loop-memory`.
|
|
15
|
+
|
|
11
16
|
Optional: link the directory to a private git repo so user-specific memory
|
|
12
17
|
content is versioned. The build-loop public repo ships ONLY the templates;
|
|
13
18
|
the user's actual lessons / patterns / decisions belong in a separate private
|
|
@@ -47,7 +52,10 @@ except Exception: # noqa: BLE001
|
|
|
47
52
|
memory_store_root = None # type: ignore[assignment]
|
|
48
53
|
_safe_project_relpath = None # type: ignore[assignment]
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
# Resolve via memory_store_root() (env → legacy-if-present → neutral default).
|
|
56
|
+
# The import-failure fallback uses the neutral fresh-install default so a
|
|
57
|
+
# degraded run never re-encodes a personal directory layout.
|
|
58
|
+
DEFAULT_DEST = memory_store_root() if memory_store_root is not None else Path.home() / ".build-loop-memory"
|
|
51
59
|
TEMPLATE_DIR_RELATIVE = "templates/memory"
|
|
52
60
|
SEED_MANIFEST_FILENAME = "manifest.json"
|
|
53
61
|
TEMPLATES = [
|