@tyroneross/build-loop 0.30.3 → 0.35.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.
Files changed (171) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +4 -4
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/AGENTS.md +11 -2
  6. package/README.md +8 -7
  7. package/agents/advisor.md +111 -0
  8. package/agents/alignment-checker.md +11 -0
  9. package/agents/assessment-orchestrator.md +1 -1
  10. package/agents/build-orchestrator.md +28 -33
  11. package/agents/fact-checker.md +1 -1
  12. package/agents/fix-critique.md +1 -1
  13. package/agents/independent-auditor.md +1 -1
  14. package/agents/overfitting-reviewer.md +1 -1
  15. package/agents/plan-critic.md +8 -6
  16. package/agents/promotion-reviewer.md +1 -1
  17. package/agents/retrospective-synthesizer.md +20 -3
  18. package/agents/scope-auditor.md +1 -1
  19. package/agents/security-reviewer.md +1 -1
  20. package/agents/self-improvement-architect.md +1 -1
  21. package/commands/{handoff.md → compose-handoff.md} +4 -4
  22. package/commands/{optimize.md → optimize-run.md} +1 -1
  23. package/commands/{agent-rally-point.md → rally-point.md} +3 -3
  24. package/commands/{research.md → research-run.md} +1 -1
  25. package/commands/setup-memory.md +32 -0
  26. package/commands/{plan-verify.md → verify-plan.md} +1 -1
  27. package/docs/agent-surface-policy.md +38 -20
  28. package/docs/memory-setup.md +19 -14
  29. package/hooks/_resolve_python.sh +28 -0
  30. package/hooks/closeout.sh +58 -0
  31. package/hooks/git/pre-push +69 -6
  32. package/hooks/hooks.json +34 -13
  33. package/hooks/post-push-closeout.sh +73 -0
  34. package/hooks/session-start-closeout.sh +69 -0
  35. package/hooks/session-start-memory.sh +27 -0
  36. package/hooks/session-start-plugin-heal.sh +33 -0
  37. package/hooks/test_closeout.sh +124 -0
  38. package/package.json +7 -4
  39. package/plugin-artifacts/codex/.codex-plugin/plugin.json +41 -0
  40. package/plugin-artifacts/codex/AGENTS.md +507 -0
  41. package/plugin-artifacts/codex/BUILD-ARTIFACT.md +5 -0
  42. package/plugin-artifacts/codex/LICENSE +202 -0
  43. package/plugin-artifacts/codex/README.md +404 -0
  44. package/plugin-artifacts/codex/docs/agent-surface-policy.md +63 -0
  45. package/plugin-artifacts/codex/references/advisor-dispatch-ladder.md +62 -0
  46. package/plugin-artifacts/codex/references/agent-role-taxonomy.md +135 -0
  47. package/plugin-artifacts/codex/references/autonomous-and-per-commit-modes.md +161 -0
  48. package/plugin-artifacts/codex/references/autonomy-config.md +231 -0
  49. package/plugin-artifacts/codex/references/capability-routing.md +182 -0
  50. package/plugin-artifacts/codex/references/codex-subagents.md +106 -0
  51. package/plugin-artifacts/codex/references/coordination-file-template.md +181 -0
  52. package/plugin-artifacts/codex/references/coordination-rules.md +283 -0
  53. package/plugin-artifacts/codex/references/dogfood-reload-checkpoint.md +112 -0
  54. package/plugin-artifacts/codex/references/halt-and-ask-protocol.md +102 -0
  55. package/plugin-artifacts/codex/references/implementer-envelope-schema.md +302 -0
  56. package/plugin-artifacts/codex/references/intent-capability-pack.md +257 -0
  57. package/plugin-artifacts/codex/references/intent-exploration-prompts.md +96 -0
  58. package/plugin-artifacts/codex/references/leadership.md +72 -0
  59. package/plugin-artifacts/codex/references/memory-systems.md +238 -0
  60. package/plugin-artifacts/codex/references/memory.md +299 -0
  61. package/plugin-artifacts/codex/references/model-tier-mapping.md +257 -0
  62. package/plugin-artifacts/codex/references/modular-systems-pack.md +96 -0
  63. package/plugin-artifacts/codex/references/phase-1-assess.md +231 -0
  64. package/plugin-artifacts/codex/references/phase-2-plan.md +78 -0
  65. package/plugin-artifacts/codex/references/phase-3-execute.md +49 -0
  66. package/plugin-artifacts/codex/references/phase-4-review.md +322 -0
  67. package/plugin-artifacts/codex/references/phase-5-iterate.md +71 -0
  68. package/plugin-artifacts/codex/references/phase-6-learn.md +56 -0
  69. package/plugin-artifacts/codex/references/recent-design-structures.md +274 -0
  70. package/plugin-artifacts/codex/references/research-trigger-policy.md +140 -0
  71. package/plugin-artifacts/codex/references/runtime-smoke-triggers.md +42 -0
  72. package/plugin-artifacts/codex/references/self-review.md +234 -0
  73. package/plugin-artifacts/codex/references/single-writer-commit-protocol.md +90 -0
  74. package/plugin-artifacts/codex/references/task-capture-policy.md +68 -0
  75. package/plugin-artifacts/codex/references/ui-io-contract.md +116 -0
  76. package/plugin-artifacts/codex/references/ui-spotcheck-protocol.md +65 -0
  77. package/plugin-artifacts/codex/skills/build-loop/SKILL.md +372 -0
  78. package/plugin-artifacts/codex/skills/build-loop/detect-plugins.mjs +82 -0
  79. package/plugin-artifacts/codex/skills/build-loop/eval-guide.md +65 -0
  80. package/plugin-artifacts/codex/skills/build-loop/fallbacks.md +549 -0
  81. package/plugin-artifacts/codex/skills/build-loop/phases/fact-check.md +42 -0
  82. package/plugin-artifacts/codex/skills/build-loop/phases/ui-validation.md +267 -0
  83. package/plugin-artifacts/codex/skills/build-loop/references/advisor-dispatch-ladder.md +62 -0
  84. package/plugin-artifacts/codex/skills/build-loop/references/apple-native-planning.md +439 -0
  85. package/plugin-artifacts/codex/skills/build-loop/references/autonomous-and-per-commit-modes.md +161 -0
  86. package/plugin-artifacts/codex/skills/build-loop/references/capability-routing.md +182 -0
  87. package/plugin-artifacts/codex/skills/build-loop/references/codex-subagents.md +106 -0
  88. package/plugin-artifacts/codex/skills/build-loop/references/coordination.md +161 -0
  89. package/plugin-artifacts/codex/skills/build-loop/references/correction-aware-capture.md +177 -0
  90. package/plugin-artifacts/codex/skills/build-loop/references/independent-auditor.md +72 -0
  91. package/plugin-artifacts/codex/skills/build-loop/references/intent-capability-pack.md +257 -0
  92. package/plugin-artifacts/codex/skills/build-loop/references/intent-exploration-prompts.md +96 -0
  93. package/plugin-artifacts/codex/skills/build-loop/references/leadership.md +72 -0
  94. package/plugin-artifacts/codex/skills/build-loop/references/memory.md +299 -0
  95. package/plugin-artifacts/codex/skills/build-loop/references/modular-systems-pack.md +96 -0
  96. package/plugin-artifacts/codex/skills/build-loop/references/output-style.md +129 -0
  97. package/plugin-artifacts/codex/skills/build-loop/references/pay-it-forward-arch.md +98 -0
  98. package/plugin-artifacts/codex/skills/build-loop/references/phase-1-assess.md +231 -0
  99. package/plugin-artifacts/codex/skills/build-loop/references/phase-2-plan.md +78 -0
  100. package/plugin-artifacts/codex/skills/build-loop/references/phase-3-execute.md +49 -0
  101. package/plugin-artifacts/codex/skills/build-loop/references/phase-4-review.md +322 -0
  102. package/plugin-artifacts/codex/skills/build-loop/references/phase-5-iterate.md +71 -0
  103. package/plugin-artifacts/codex/skills/build-loop/references/phase-6-learn.md +56 -0
  104. package/plugin-artifacts/codex/skills/build-loop/references/recent-design-structures.md +274 -0
  105. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
  106. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
  107. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
  108. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
  109. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
  110. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
  111. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
  112. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
  113. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
  114. package/plugin-artifacts/codex/skills/build-loop/references/self-recursive-dev.md +77 -0
  115. package/plugin-artifacts/codex/skills/build-loop/references/self-review.md +234 -0
  116. package/plugin-artifacts/codex/skills/build-loop/references/ui-io-contract.md +116 -0
  117. package/plugin-artifacts/codex/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
  118. package/plugin-artifacts/codex/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
  119. package/plugin-artifacts/codex/skills/build-loop/templates/backlog-item.md +35 -0
  120. package/plugin-artifacts/codex/skills/build-loop/templates/codex-worker-prompt.md +100 -0
  121. package/plugin-artifacts/codex/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
  122. package/plugin-artifacts/codex/skills/build-loop/templates/ux-fix-plan.md +40 -0
  123. package/scripts/_paths.py +59 -13
  124. package/scripts/build_codex_plugin_artifact.py +314 -0
  125. package/scripts/check_cache_sync.py +1 -1
  126. package/scripts/install_memory.py +11 -3
  127. package/scripts/prune_plugin_cache.py +225 -11
  128. package/scripts/sync_navgator_lessons.py +25 -0
  129. package/scripts/sync_plugin_cache.py +66 -36
  130. package/skills/agent-rally-point/SKILL.md +1 -1
  131. package/skills/agent-rally-watcher/SKILL.md +1 -1
  132. package/skills/build-loop/SKILL.md +11 -155
  133. package/skills/build-loop/references/advisor-dispatch-ladder.md +62 -0
  134. package/skills/build-loop/references/autonomous-and-per-commit-modes.md +161 -0
  135. package/skills/build-loop/references/capability-routing.md +4 -3
  136. package/skills/build-loop/references/codex-subagents.md +8 -0
  137. package/skills/build-loop/references/coordination.md +1 -1
  138. package/skills/build-loop/references/intent-capability-pack.md +88 -1
  139. package/skills/build-loop/references/memory.md +17 -2
  140. package/skills/build-loop/references/phase-1-assess.md +13 -0
  141. package/skills/build-loop/references/phase-2-plan.md +2 -1
  142. package/skills/build-loop/references/phase-3-execute.md +4 -2
  143. package/skills/build-loop/references/phase-4-review.md +12 -0
  144. package/skills/build-loop/references/phase-6-learn.md +1 -1
  145. package/skills/build-loop/references/self-review.md +4 -2
  146. package/skills/build-loop/templates/codex-worker-prompt.md +45 -34
  147. package/skills/handoff/SKILL.md +3 -3
  148. package/skills/loop-builder/SKILL.md +98 -0
  149. package/skills/loop-builder/presets/active-project-evidence.yaml +97 -0
  150. package/skills/loop-builder/presets/generic-artifact-loop.yaml +95 -0
  151. package/skills/loop-builder/presets/presentation-audit.yaml +96 -0
  152. package/skills/loop-builder/presets/research-synthesis.yaml +96 -0
  153. package/skills/loop-builder/presets/source-ingestion-raw-data-audit.yaml +96 -0
  154. package/skills/loop-builder/references/spec-format.md +80 -0
  155. package/skills/loop-builder/scripts/loop_builder.py +346 -0
  156. package/skills/model-tiering/SKILL.md +65 -40
  157. package/skills/optimize/SKILL.md +2 -2
  158. package/skills/plan-verify/SKILL.md +1 -1
  159. package/skills/plugin-builder/references/distribution.md +8 -0
  160. package/skills/research/SKILL.md +33 -6
  161. package/skills/runtime-parity-verification/SKILL.md +51 -0
  162. package/skills/spec-writing/SKILL.md +19 -0
  163. package/templates/memory/README.md +65 -0
  164. package/templates/memory/charter.md.template +46 -0
  165. package/templates/memory/manifest.json +24 -4
  166. package/codex-skills/debug-loop/SKILL.md +0 -18
  167. package/codex-skills/handoff/SKILL.md +0 -22
  168. package/codex-skills/knowledge/SKILL.md +0 -21
  169. package/codex-skills/optimize/SKILL.md +0 -18
  170. package/codex-skills/research/SKILL.md +0 -18
  171. /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
- Defaults to ``~/dev/git-folder/build-loop-memory``.
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
- DEFAULT_MEMORY_STORE_ROOT = "~/dev/git-folder/build-loop-memory"
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, not `~/.build-loop/memory`.
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
- Reads, in order, ``$BUILD_LOOP_MEMORY_STORE_ROOT``,
65
- ``$BUILD_LOOP_MEMORY_ROOT``, and ``$AGENT_MEMORY_ROOT`` (expanded for
66
- ``~``). Falls back to ``~/dev/git-folder/build-loop-memory``. Path is
67
- not required to exist; callers that need the directory should create it.
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
- raw = (
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
- return Path(os.path.expanduser(raw))
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
- # Per the memory-consolidation PR series (2026-05-13):
267
- # - ~/.build-loop/memory/ global root (existing)
268
- # - ~/.build-loop/memory/projects/<slug>/ project-scoped lessons (NEW)
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/agent-rally-point.md",
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 (default:
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
- DEFAULT_DEST = memory_store_root() if memory_store_root is not None else Path.home() / "dev" / "git-folder" / "build-loop-memory"
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 = [