@arbiterforge/ca-pi 0.6.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 (206) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +558 -0
  3. package/package.json +35 -0
  4. package/plugins/ca-pi/CHANGELOG.md +1030 -0
  5. package/plugins/ca-pi/COMMANDS.md +90 -0
  6. package/plugins/ca-pi/ORCHESTRATOR.md +159 -0
  7. package/plugins/ca-pi/SKILLS.md +47 -0
  8. package/plugins/ca-pi/SPRINT.md +142 -0
  9. package/plugins/ca-pi/agents/INDEX.md +31 -0
  10. package/plugins/ca-pi/agents/architecture-drift-reviewer.md +86 -0
  11. package/plugins/ca-pi/agents/auth-crypto-reviewer.md +60 -0
  12. package/plugins/ca-pi/agents/backend-author.md +60 -0
  13. package/plugins/ca-pi/agents/checkpoint-aggregator.md +111 -0
  14. package/plugins/ca-pi/agents/coverage-auditor.md +71 -0
  15. package/plugins/ca-pi/agents/decision-challenger.md +116 -0
  16. package/plugins/ca-pi/agents/dependency-reviewer.md +79 -0
  17. package/plugins/ca-pi/agents/design-quality-reviewer.md +80 -0
  18. package/plugins/ca-pi/agents/finding-triage.md +86 -0
  19. package/plugins/ca-pi/agents/frontend-author.md +64 -0
  20. package/plugins/ca-pi/agents/grader.md +173 -0
  21. package/plugins/ca-pi/agents/infra-author.md +64 -0
  22. package/plugins/ca-pi/agents/map-deps.md +35 -0
  23. package/plugins/ca-pi/agents/map-structure.md +37 -0
  24. package/plugins/ca-pi/agents/migration-reviewer.md +65 -0
  25. package/plugins/ca-pi/agents/scout.md +127 -0
  26. package/plugins/ca-pi/agents/security-reviewer.md +72 -0
  27. package/plugins/ca-pi/agents/tribunal-lens-reviewer.md +65 -0
  28. package/plugins/ca-pi/extensions/codearbiter-child.js +1885 -0
  29. package/plugins/ca-pi/extensions/codearbiter.js +9802 -0
  30. package/plugins/ca-pi/generated/command-catalog.json +197 -0
  31. package/plugins/ca-pi/generated/roles.json +213 -0
  32. package/plugins/ca-pi/helpers/windows-supervisor.js +205 -0
  33. package/plugins/ca-pi/hooks/_activationlib.py +196 -0
  34. package/plugins/ca-pi/hooks/_arbiterstatelib.py +208 -0
  35. package/plugins/ca-pi/hooks/_babysitlib.py +76 -0
  36. package/plugins/ca-pi/hooks/_bashguardlib.py +1667 -0
  37. package/plugins/ca-pi/hooks/_boxlib.py +131 -0
  38. package/plugins/ca-pi/hooks/_colorlib.py +304 -0
  39. package/plugins/ca-pi/hooks/_durabilitylib.py +186 -0
  40. package/plugins/ca-pi/hooks/_entrylib.py +41 -0
  41. package/plugins/ca-pi/hooks/_fmtlib.py +161 -0
  42. package/plugins/ca-pi/hooks/_gitexec.py +45 -0
  43. package/plugins/ca-pi/hooks/_githooks.py +920 -0
  44. package/plugins/ca-pi/hooks/_gitlib.py +110 -0
  45. package/plugins/ca-pi/hooks/_hooklib.py +595 -0
  46. package/plugins/ca-pi/hooks/_host.py +115 -0
  47. package/plugins/ca-pi/hooks/_intentlib.py +242 -0
  48. package/plugins/ca-pi/hooks/_ledgerlib.py +1035 -0
  49. package/plugins/ca-pi/hooks/_metricslib.py +709 -0
  50. package/plugins/ca-pi/hooks/_pathnorm.py +74 -0
  51. package/plugins/ca-pi/hooks/_planfilelib.py +664 -0
  52. package/plugins/ca-pi/hooks/_previewlib.py +193 -0
  53. package/plugins/ca-pi/hooks/_protectedlib.py +312 -0
  54. package/plugins/ca-pi/hooks/_protectedstatelib.py +411 -0
  55. package/plugins/ca-pi/hooks/_provenancelib.py +971 -0
  56. package/plugins/ca-pi/hooks/_prunelib.py +1398 -0
  57. package/plugins/ca-pi/hooks/_prunepolicy.py +235 -0
  58. package/plugins/ca-pi/hooks/_readinjectlib.py +1080 -0
  59. package/plugins/ca-pi/hooks/_releaselib.py +2657 -0
  60. package/plugins/ca-pi/hooks/_scopelib.py +262 -0
  61. package/plugins/ca-pi/hooks/_segmentslib.py +278 -0
  62. package/plugins/ca-pi/hooks/_sensitivelib.py +270 -0
  63. package/plugins/ca-pi/hooks/_sessionlib.py +78 -0
  64. package/plugins/ca-pi/hooks/_sloplib.py +244 -0
  65. package/plugins/ca-pi/hooks/_standuplib.py +214 -0
  66. package/plugins/ca-pi/hooks/_subagentslib.py +219 -0
  67. package/plugins/ca-pi/hooks/_taskboardlib.py +1088 -0
  68. package/plugins/ca-pi/hooks/_updatelib.py +278 -0
  69. package/plugins/ca-pi/hooks/babysit.py +47 -0
  70. package/plugins/ca-pi/hooks/boardsync.py +129 -0
  71. package/plugins/ca-pi/hooks/doctor.py +420 -0
  72. package/plugins/ca-pi/hooks/git-enforce.py +325 -0
  73. package/plugins/ca-pi/hooks/hostapi.py +460 -0
  74. package/plugins/ca-pi/hooks/init-codearbiter.py +225 -0
  75. package/plugins/ca-pi/hooks/metrics.py +62 -0
  76. package/plugins/ca-pi/hooks/migration-pass.py +129 -0
  77. package/plugins/ca-pi/hooks/pi-bridge.py +543 -0
  78. package/plugins/ca-pi/hooks/post-write-edit.py +231 -0
  79. package/plugins/ca-pi/hooks/pre-bash.py +90 -0
  80. package/plugins/ca-pi/hooks/pre-edit.py +284 -0
  81. package/plugins/ca-pi/hooks/pre-read.py +81 -0
  82. package/plugins/ca-pi/hooks/pre-write.py +217 -0
  83. package/plugins/ca-pi/hooks/preview.py +69 -0
  84. package/plugins/ca-pi/hooks/prune-transcript.py +232 -0
  85. package/plugins/ca-pi/hooks/releasehash.py +216 -0
  86. package/plugins/ca-pi/hooks/security-pass.py +139 -0
  87. package/plugins/ca-pi/hooks/session-start.py +1218 -0
  88. package/plugins/ca-pi/hooks/statusline.py +736 -0
  89. package/plugins/ca-pi/hooks/taskwrite.py +351 -0
  90. package/plugins/ca-pi/hooks/update-refresh.py +51 -0
  91. package/plugins/ca-pi/hooks/wire-statusline.py +435 -0
  92. package/plugins/ca-pi/includes/anti-slop-design/INDEX.md +55 -0
  93. package/plugins/ca-pi/includes/anti-slop-design/color.md +43 -0
  94. package/plugins/ca-pi/includes/anti-slop-design/core.md +244 -0
  95. package/plugins/ca-pi/includes/anti-slop-design/images.md +32 -0
  96. package/plugins/ca-pi/includes/anti-slop-design/layout.md +45 -0
  97. package/plugins/ca-pi/includes/anti-slop-design/medium-cli.md +39 -0
  98. package/plugins/ca-pi/includes/anti-slop-design/medium-dataviz.md +43 -0
  99. package/plugins/ca-pi/includes/anti-slop-design/medium-diagram.md +35 -0
  100. package/plugins/ca-pi/includes/anti-slop-design/medium-documents.md +70 -0
  101. package/plugins/ca-pi/includes/anti-slop-design/medium-slides.md +30 -0
  102. package/plugins/ca-pi/includes/anti-slop-design/medium-web.md +39 -0
  103. package/plugins/ca-pi/includes/anti-slop-design/typography.md +51 -0
  104. package/plugins/ca-pi/includes/author-tdd-workflow.md +14 -0
  105. package/plugins/ca-pi/includes/compaction-charter.md +16 -0
  106. package/plugins/ca-pi/includes/cut-docs.md +16 -0
  107. package/plugins/ca-pi/includes/dev-mode.md +30 -0
  108. package/plugins/ca-pi/includes/farm.md +237 -0
  109. package/plugins/ca-pi/includes/fresh-verification.md +14 -0
  110. package/plugins/ca-pi/includes/harvest.md +69 -0
  111. package/plugins/ca-pi/includes/maturity-coverage.md +102 -0
  112. package/plugins/ca-pi/includes/pi-host-notes.md +69 -0
  113. package/plugins/ca-pi/includes/redirect.md +69 -0
  114. package/plugins/ca-pi/includes/reference-map.md +22 -0
  115. package/plugins/ca-pi/includes/review-matrix.md +14 -0
  116. package/plugins/ca-pi/includes/reviewer-contract.md +53 -0
  117. package/plugins/ca-pi/includes/routing-table.md +47 -0
  118. package/plugins/ca-pi/includes/security-gate-record.md +22 -0
  119. package/plugins/ca-pi/includes/smarts/core.md +90 -0
  120. package/plugins/ca-pi/includes/smarts/decision-log-format.md +56 -0
  121. package/plugins/ca-pi/routines/INDEX.md +32 -0
  122. package/plugins/ca-pi/routines/brainstorming/SKILL.md +122 -0
  123. package/plugins/ca-pi/routines/commit-gate/SKILL.md +151 -0
  124. package/plugins/ca-pi/routines/context-check/SKILL.md +85 -0
  125. package/plugins/ca-pi/routines/context-creation/SKILL.md +171 -0
  126. package/plugins/ca-pi/routines/crypto-compliance/SKILL.md +41 -0
  127. package/plugins/ca-pi/routines/debug/SKILL.md +99 -0
  128. package/plugins/ca-pi/routines/decision-lifecycle/SKILL.md +104 -0
  129. package/plugins/ca-pi/routines/decision-lifecycle/references/adr-template.md +74 -0
  130. package/plugins/ca-pi/routines/decision-variance/SKILL.md +147 -0
  131. package/plugins/ca-pi/routines/decompose/SKILL.md +168 -0
  132. package/plugins/ca-pi/routines/dispatching-parallel-agents/SKILL.md +76 -0
  133. package/plugins/ca-pi/routines/executing-plans/SKILL.md +83 -0
  134. package/plugins/ca-pi/routines/finishing-a-development-branch/SKILL.md +91 -0
  135. package/plugins/ca-pi/routines/post-merge-cleanup/SKILL.md +233 -0
  136. package/plugins/ca-pi/routines/refactor/SKILL.md +91 -0
  137. package/plugins/ca-pi/routines/release/SKILL.md +315 -0
  138. package/plugins/ca-pi/routines/secret-handling/SKILL.md +67 -0
  139. package/plugins/ca-pi/routines/security-architecture/SKILL.md +63 -0
  140. package/plugins/ca-pi/routines/skill-author/SKILL.md +108 -0
  141. package/plugins/ca-pi/routines/skill-author/references/skill-template.md +58 -0
  142. package/plugins/ca-pi/routines/subagent-driven-development/SKILL.md +149 -0
  143. package/plugins/ca-pi/routines/subagent-driven-development/references/farm-dispatch.md +145 -0
  144. package/plugins/ca-pi/routines/tdd/SKILL.md +139 -0
  145. package/plugins/ca-pi/routines/tribunal/SKILL.md +109 -0
  146. package/plugins/ca-pi/routines/tribunal/references/ai-markers.md +29 -0
  147. package/plugins/ca-pi/routines/tribunal/references/cost-and-models.md +64 -0
  148. package/plugins/ca-pi/routines/tribunal/references/finding-record.md +27 -0
  149. package/plugins/ca-pi/routines/tribunal/references/issue-filing.md +47 -0
  150. package/plugins/ca-pi/routines/tribunal/references/lenses/appsec.md +22 -0
  151. package/plugins/ca-pi/routines/tribunal/references/lenses/architecture.md +23 -0
  152. package/plugins/ca-pi/routines/tribunal/references/lenses/coverage.md +20 -0
  153. package/plugins/ca-pi/routines/tribunal/references/lenses/infra.md +24 -0
  154. package/plugins/ca-pi/routines/tribunal/references/lenses/migration.md +22 -0
  155. package/plugins/ca-pi/routines/tribunal/references/lenses/observability.md +21 -0
  156. package/plugins/ca-pi/routines/tribunal/references/lenses/performance.md +22 -0
  157. package/plugins/ca-pi/routines/tribunal/references/lenses/reliability.md +23 -0
  158. package/plugins/ca-pi/routines/tribunal/references/lenses/secrets-supply.md +22 -0
  159. package/plugins/ca-pi/routines/tribunal/references/lenses/test-fidelity.md +24 -0
  160. package/plugins/ca-pi/routines/tribunal/references/lenses/typesafety.md +21 -0
  161. package/plugins/ca-pi/routines/tribunal/references/report.md +19 -0
  162. package/plugins/ca-pi/routines/tribunal/references/schemas.md +58 -0
  163. package/plugins/ca-pi/routines/tribunal/references/telemetry.md +28 -0
  164. package/plugins/ca-pi/routines/tribunal/references/triage.md +53 -0
  165. package/plugins/ca-pi/routines/using-git-worktrees/SKILL.md +85 -0
  166. package/plugins/ca-pi/routines/writing-plans/SKILL.md +129 -0
  167. package/plugins/ca-pi/routines/writing-plans/references/farm-plan.md +50 -0
  168. package/plugins/ca-pi/skills/ca-add-dep/SKILL.md +88 -0
  169. package/plugins/ca-pi/skills/ca-adr/SKILL.md +30 -0
  170. package/plugins/ca-pi/skills/ca-adr-status/SKILL.md +30 -0
  171. package/plugins/ca-pi/skills/ca-arbiter/SKILL.md +36 -0
  172. package/plugins/ca-pi/skills/ca-audit/SKILL.md +51 -0
  173. package/plugins/ca-pi/skills/ca-btw/SKILL.md +23 -0
  174. package/plugins/ca-pi/skills/ca-checkpoint/SKILL.md +50 -0
  175. package/plugins/ca-pi/skills/ca-chore/SKILL.md +58 -0
  176. package/plugins/ca-pi/skills/ca-cleanup/SKILL.md +55 -0
  177. package/plugins/ca-pi/skills/ca-commands/SKILL.md +21 -0
  178. package/plugins/ca-pi/skills/ca-commit/SKILL.md +27 -0
  179. package/plugins/ca-pi/skills/ca-conflict/SKILL.md +61 -0
  180. package/plugins/ca-pi/skills/ca-context-check/SKILL.md +32 -0
  181. package/plugins/ca-pi/skills/ca-create-context/SKILL.md +32 -0
  182. package/plugins/ca-pi/skills/ca-debug/SKILL.md +42 -0
  183. package/plugins/ca-pi/skills/ca-decompose/SKILL.md +30 -0
  184. package/plugins/ca-pi/skills/ca-dev/SKILL.md +42 -0
  185. package/plugins/ca-pi/skills/ca-doctor/SKILL.md +44 -0
  186. package/plugins/ca-pi/skills/ca-feature/SKILL.md +105 -0
  187. package/plugins/ca-pi/skills/ca-fix/SKILL.md +42 -0
  188. package/plugins/ca-pi/skills/ca-init/SKILL.md +56 -0
  189. package/plugins/ca-pi/skills/ca-metrics/SKILL.md +80 -0
  190. package/plugins/ca-pi/skills/ca-new-skill/SKILL.md +34 -0
  191. package/plugins/ca-pi/skills/ca-override/SKILL.md +72 -0
  192. package/plugins/ca-pi/skills/ca-pr/SKILL.md +61 -0
  193. package/plugins/ca-pi/skills/ca-preview/SKILL.md +86 -0
  194. package/plugins/ca-pi/skills/ca-prune/SKILL.md +100 -0
  195. package/plugins/ca-pi/skills/ca-reconcile/SKILL.md +43 -0
  196. package/plugins/ca-pi/skills/ca-refactor/SKILL.md +43 -0
  197. package/plugins/ca-pi/skills/ca-release/SKILL.md +57 -0
  198. package/plugins/ca-pi/skills/ca-review/SKILL.md +63 -0
  199. package/plugins/ca-pi/skills/ca-spike/SKILL.md +41 -0
  200. package/plugins/ca-pi/skills/ca-sprint/SKILL.md +44 -0
  201. package/plugins/ca-pi/skills/ca-standup/SKILL.md +112 -0
  202. package/plugins/ca-pi/skills/ca-status/SKILL.md +56 -0
  203. package/plugins/ca-pi/skills/ca-task/SKILL.md +61 -0
  204. package/plugins/ca-pi/skills/ca-threat-model/SKILL.md +45 -0
  205. package/plugins/ca-pi/skills/ca-tribunal/SKILL.md +44 -0
  206. package/plugins/ca-pi/skills/ca-watch/SKILL.md +78 -0
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env python3
2
+ # codeArbiter — first-run scaffolder. Creates the root-level .codearbiter/ project
3
+ # state directory that opts a repo into arbiter management. Idempotent: it never
4
+ # overwrites a file that already exists, so it is safe to re-run.
5
+ #
6
+ # The `arbiter: enabled` frontmatter in CONTEXT.md is the single activation flag —
7
+ # it gates both the SessionStart persona injection and the arbiter statusline
8
+ # segments. CONTEXT.md is scaffolded WITHOUT the <!--INITIALIZED--> body sentinel,
9
+ # so the next session routes to /ca:create-context (source exists) or /ca:decompose
10
+ # (greenfield) to populate it.
11
+ #
12
+ # Usage:
13
+ # python init-codearbiter.py [--root PATH] [--stage N]
14
+ # python init-codearbiter.py --check # report state, create nothing
15
+
16
+ import argparse
17
+ import subprocess
18
+ import os
19
+ import sys
20
+
21
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
22
+ from _gitexec import git_executable # noqa: E402
23
+ import hostapi # noqa: E402 — host seam (ADR-0011)
24
+ import _hooklib # noqa: E402 — set_host DI seam (#257)
25
+ import _entrylib # noqa: E402 — shared run() dispatch (jscpd dedup)
26
+
27
+ # NOTE: this stub deliberately does NOT contain the initialization sentinel
28
+ # (an HTML comment wrapping the word INITIALIZED). The SessionStart hook greps
29
+ # for that exact token to decide whether the project is populated, so emitting it
30
+ # here — even inside a comment — would falsely mark an empty stub as initialized.
31
+ # The populator flow (/ca:create-context or /ca:decompose) adds it on its own line.
32
+ CONTEXT = """\
33
+ ---
34
+ arbiter: enabled
35
+ stage: {stage}
36
+ ---
37
+
38
+ # Project: {name}
39
+
40
+ <!-- This .codearbiter/ directory is the root-level project-state store. It lives
41
+ outside .claude/ so it survives even if the codeArbiter plugin is uninstalled.
42
+ The `arbiter: enabled` frontmatter above is the activation flag.
43
+
44
+ This CONTEXT.md is a stub: it carries no initialization sentinel yet, so the
45
+ orchestrator routes you to {create_context} (if source already exists) or
46
+ {decompose} (greenfield) to populate the real project context. That flow writes
47
+ the sentinel and normal operation begins. -->
48
+
49
+ _Not yet initialized. Run {create_context} or {decompose} to populate._
50
+ """
51
+
52
+ OPEN_TASKS = """\
53
+ # Open tasks
54
+
55
+ In-flight and queued work. One top-level `- ` bullet per task; the statusline and
56
+ SessionStart hook count these, EXCLUDING done (`- [x]`).
57
+
58
+ Schema (one task = a lifecycle line + indented content sub-bullets):
59
+
60
+ ```
61
+ - [~] poc.auth.0001 - Validate session tokens (started 2026-06-18)
62
+ - Desc: reject expired/forged tokens at the auth middleware
63
+ - Done when: an expired token returns 401; a valid one passes
64
+ - Boundaries: auth, secrets
65
+ ```
66
+
67
+ - Marker: `[ ]` queued | `[~]` in-progress | `[x]` done. In-progress and done
68
+ carry a dated `(started YYYY-MM-DD)` / `(done YYYY-MM-DD)` parenthetical; a
69
+ stale `[~]` is surfaced at SessionStart.
70
+ - ID `<group>.<type>.<seq>`: `group` = build phase (poc/mvp1/v1...), `type` =
71
+ domain (auth/api/ui/infra...), `seq` = >=4-digit, numbered within each
72
+ `group.type`. ID + title + marker are required.
73
+ - `Desc` / `Done when` / `Boundaries` are filled when known (`TBD` until then).
74
+ Keep `Done when` to one coarse sentence — per-step verification belongs to a
75
+ plan, not here. `Boundaries` names the security/trust boundary the task is
76
+ expected to touch (auth, crypto, secrets, ...).
77
+ """
78
+
79
+ OPEN_QUESTIONS = """\
80
+ # Open questions
81
+
82
+ Unresolved `[CONFIRM-NN]` items. Each blocks dependent work until resolved.
83
+ The SessionStart hook and statusline count `CONFIRM-NN` occurrences here.
84
+
85
+ _None open._
86
+ """
87
+
88
+ OVERRIDES = """\
89
+ # codeArbiter override log - append-only audit artifact. Never edit or delete prior lines.
90
+ # Format: [ISO-8601] | BY: <name> <<email>> | GATE: <gate bypassed> | REASON: <reason>
91
+ # Written only by /override. The statusline counts non-comment lines after the
92
+ # last-checkpoint marker as "overrides since last checkpoint."
93
+ """
94
+
95
+ DONE_TASKS = """\
96
+ # Done tasks
97
+
98
+ Completed work swept off the board by `taskwrite archive`, newest last.
99
+ APPEND-ONLY: entries are added here and never edited or removed, so a
100
+ finished task has exactly one permanent record.
101
+
102
+ Each line is the task's original lifecycle line, moved verbatim from
103
+ `open-tasks.md` with its `(done YYYY-MM-DD)` stamp intact — the stamp is
104
+ what makes an entry ageable, and what `archive` refuses to invent.
105
+
106
+ Written only by `taskwrite archive`. `/ca:standup` proposes the sweep with
107
+ per-item confirmation; nothing sweeps automatically.
108
+ """
109
+
110
+ FILES = {
111
+ "open-tasks.md": OPEN_TASKS,
112
+ # B-23: scaffolded at init rather than created on first archive. An
113
+ # append-only file that springs into existence mid-sweep has no
114
+ # reviewed initial content and no header explaining what it is — and
115
+ # `archive`'s first write would be the thing that defines the format.
116
+ "done-tasks.md": DONE_TASKS,
117
+ "open-questions.md": OPEN_QUESTIONS,
118
+ "overrides.log": OVERRIDES,
119
+ "last-checkpoint": "0\n",
120
+ }
121
+
122
+
123
+ def project_root(opt):
124
+ if opt:
125
+ return os.path.abspath(opt)
126
+ # prefer git toplevel; fall back to cwd
127
+ try:
128
+ out = subprocess.run([git_executable(), "rev-parse", "--show-toplevel"],
129
+ capture_output=True, text=True, timeout=2)
130
+ top = out.stdout.strip()
131
+ if out.returncode == 0 and top:
132
+ return os.path.abspath(top)
133
+ except Exception:
134
+ pass
135
+ return os.path.abspath(os.getcwd())
136
+
137
+
138
+ def main(argv=None):
139
+ ap = argparse.ArgumentParser(add_help=True)
140
+ ap.add_argument("--root")
141
+ ap.add_argument("--stage", type=int, default=1)
142
+ ap.add_argument("--check", action="store_true")
143
+ args = ap.parse_args(argv)
144
+
145
+ root = project_root(args.root)
146
+ cad = os.path.join(root, ".codearbiter")
147
+ ctx = os.path.join(cad, "CONTEXT.md")
148
+ # Host-native command spellings for every populate pointer this scaffold
149
+ # emits or writes into the stub (codex-support M3): the same text must
150
+ # read /ca:create-context under Claude Code and $ca-create-context under
151
+ # Codex, or the very first thing a Codex-only user sees is a dead pointer.
152
+ _host = _hooklib.get_host()
153
+ _cc = _host.cmd_ref("create-context")
154
+ _dc = _host.cmd_ref("decompose")
155
+ name = os.path.basename(root.rstrip("/\\")) or "project"
156
+
157
+ if args.check:
158
+ if os.path.exists(ctx):
159
+ print(f"ALREADY SCAFFOLDED: {ctx} exists.")
160
+ with open(ctx, encoding="utf-8", errors="replace") as f:
161
+ text = f.read()
162
+ import re
163
+ initd = bool(re.search(r"(?m)^\s*<!--\s*INITIALIZED\s*-->\s*$", text))
164
+ print("arbiter: " + ("enabled" if re.search(r"(?m)^arbiter:\s*enabled\s*$", text) else "not enabled"))
165
+ print("initialized body: " + ("yes" if initd else
166
+ "no (run " + _cc + " or " + _dc + ")"))
167
+ else:
168
+ print(f"NOT SCAFFOLDED: {cad} would be created. Run without --check to scaffold.")
169
+ return
170
+
171
+ if os.path.exists(ctx):
172
+ raise SystemExit(
173
+ f"REFUSING: {ctx} already exists. .codearbiter/ is already scaffolded here. "
174
+ f"To populate it, run {_cc} or {_dc}; to repair, edit by hand.")
175
+
176
+ os.makedirs(cad, exist_ok=True)
177
+ created = []
178
+ if not os.path.exists(ctx):
179
+ with open(ctx, "w", encoding="utf-8") as f:
180
+ f.write(CONTEXT.format(stage=args.stage, name=name,
181
+ create_context=_cc, decompose=_dc))
182
+ created.append("CONTEXT.md")
183
+ for fname, content in FILES.items():
184
+ fp = os.path.join(cad, fname)
185
+ if not os.path.exists(fp):
186
+ with open(fp, "w", encoding="utf-8") as f:
187
+ f.write(content)
188
+ created.append(fname)
189
+
190
+ # #161: install the git-level enforcement backstop (pre-commit/pre-push) so
191
+ # git mutations are gated at the operation itself, not only at the literal
192
+ # Bash command string. Best-effort — a repo without a git dir, or a foreign
193
+ # existing hook, is reported by install(), never fatal to scaffolding.
194
+ try:
195
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
196
+ from _githooks import install as _install_git_hooks
197
+ gh = _install_git_hooks(root)
198
+ if gh:
199
+ print("git hooks: " + ", ".join(gh))
200
+ except Exception as e: # noqa: BLE001
201
+ print(f"git hooks: install skipped ({e})", file=sys.stderr)
202
+
203
+ print(f"SCAFFOLDED .codearbiter/ at {cad}")
204
+ print("created: " + ", ".join(created))
205
+ print(f"arbiter: enabled (stage {args.stage}); CONTEXT.md is a stub (no <!--INITIALIZED--> sentinel).")
206
+ print(f"Next: run {_cc} (source exists) or {_dc} (greenfield) to populate.")
207
+
208
+
209
+ def run(host, argv=None):
210
+ """Host-seam entry point (ADR-0011): the __main__ guard calls this with the
211
+ plugin's loaded Host. Wraps main(argv) unchanged — main()'s return value
212
+ stays discarded exactly as the old bare `main()` guard discarded it (so
213
+ the process still exits 0 on a normal fall-through).
214
+
215
+ Wires `host` live (#257): primes `_hooklib`'s process-cached Host via
216
+ `set_host()` BEFORE main() runs, so any `get_host()` call downstream
217
+ resolves to the SAME instance the caller passed here — no second
218
+ `hostapi.load_host()`, and `run(fake_host)` genuinely exercises
219
+ `fake_host`."""
220
+ return _entrylib.dispatch(host, argv, main, _hooklib.set_host,
221
+ pass_argv=True, propagate_result=False)
222
+
223
+
224
+ if __name__ == "__main__":
225
+ sys.exit(run(hostapi.load_host()) or 0)
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env python3
2
+ # codeArbiter — thin entry point for /ca:metrics (architecture-006/#179).
3
+ #
4
+ # Wraps _metricslib.compute and prints its JSON-serialized result, so the
5
+ # field-access/serialization step /ca:metrics needs is import-covered
6
+ # (py_compile + unit tests) instead of living as an inline `python -c`
7
+ # multi-statement block in command prose. Mirrors doctor.py / taskwrite.py:
8
+ # entry point stays thin, all computation lives in _metricslib.
9
+ #
10
+ # Invoked by commands/metrics.md as (interpreter resolved once by presence,
11
+ # never a `python3 X || python X` fold -- #577):
12
+ # "$PY" "<plugin>/hooks/metrics.py" --root "<dir>" [--window N]
13
+ #
14
+ # Read-only: _metricslib.compute() never writes; this wrapper only prints its
15
+ # return value. json.dumps is called with its default ensure_ascii=True so the
16
+ # arrow glyphs (up/down/right arrows) are ASCII-escaped on the way out — this
17
+ # avoids a UnicodeEncodeError on Windows cp1252 consoles. Do NOT pass
18
+ # ensure_ascii=False here; the command prose renders the real glyphs itself
19
+ # from the parsed JSON, not from this process's raw stdout.
20
+
21
+ import argparse
22
+ import json
23
+ import os
24
+ import sys
25
+
26
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
27
+ import hostapi # noqa: E402 — host seam (ADR-0011)
28
+ import _hooklib # noqa: E402 — set_host DI seam (#257)
29
+ import _entrylib # noqa: E402 — shared run() dispatch (jscpd dedup)
30
+ import _metricslib # noqa: E402
31
+
32
+
33
+ def main(argv=None):
34
+ parser = argparse.ArgumentParser(prog="metrics", add_help=True)
35
+ parser.add_argument("--root", default=os.getcwd())
36
+ parser.add_argument("--window", type=int, default=None)
37
+ args = parser.parse_args(argv)
38
+
39
+ if args.window is not None:
40
+ result = _metricslib.compute(args.root, window_size=args.window)
41
+ else:
42
+ result = _metricslib.compute(args.root)
43
+ print(json.dumps(result))
44
+ return 0
45
+
46
+
47
+ def run(host, argv=None):
48
+ """Host-seam entry point (ADR-0011): the __main__ guard calls this with the
49
+ plugin's loaded Host. Delegates to main(argv) and returns its exit code,
50
+ exactly as the old `sys.exit(main())` guard propagated it.
51
+
52
+ Wires `host` live (#257): primes `_hooklib`'s process-cached Host via
53
+ `set_host()` BEFORE main() runs, so any `get_host()` call downstream
54
+ resolves to the SAME instance the caller passed here — no second
55
+ `hostapi.load_host()`, and `run(fake_host)` genuinely exercises
56
+ `fake_host`."""
57
+ return _entrylib.dispatch(host, argv, main, _hooklib.set_host,
58
+ pass_argv=True, propagate_result=True)
59
+
60
+
61
+ if __name__ == "__main__":
62
+ sys.exit(run(hostapi.load_host()) or 0)
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env python3
2
+ # codeArbiter — record a migration-review pass BOUND to the migration files it
3
+ # approved (H-14, issue #77).
4
+ #
5
+ # There was no commit-time migration gate before #77: a migration committed via
6
+ # bare /commit or the /feature small lane never reached migration-reviewer (the
7
+ # /review, /pr, /checkpoint, and sprint lanes did dispatch it). commit-gate now
8
+ # dispatches migration-reviewer when it classifies a staged migration and runs
9
+ # THIS script on PASS. It mirrors security-pass.py: it hashes the content of
10
+ # every staged/worktree/untracked migration file (detected by is_migration_path)
11
+ # and writes the digests to .codearbiter/.markers/migration-gate-passed;
12
+ # pre-bash.py's H-14 then admits a commit only when every migration file being
13
+ # committed is in the recorded set.
14
+ #
15
+ # Binding is by content digest with no freshness window: a migration is
16
+ # immutable, so a recorded pass stays valid while the content is unchanged, and
17
+ # any edit changes the digest -> the backstop re-blocks (closing TOCTOU and
18
+ # enforcing immutability at commit time).
19
+ #
20
+ # Invoked by skill prose as (interpreter resolved once by presence, never a
21
+ # `python3 X || python X` fold -- #577):
22
+ # "$PY" "<plugin>/hooks/migration-pass.py"
23
+
24
+ import os
25
+ import subprocess
26
+
27
+ from _gitexec import git_executable
28
+ import sys
29
+
30
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
31
+ from _hooklib import ( # noqa: E402
32
+ content_digest, is_migration_path, marker_root, project_root, set_host,
33
+ utf8_stdio, warn, write_text_atomic,
34
+ )
35
+
36
+ MAX_FILE_BYTES = 1_000_000 # a blob bigger than this is not a reviewable migration
37
+
38
+
39
+ def run_git(args, cwd):
40
+ return subprocess.run(
41
+ [git_executable()] + args, cwd=cwd, capture_output=True, text=True,
42
+ encoding="utf-8", errors="replace", timeout=30,
43
+ )
44
+
45
+
46
+ def candidate_paths(root):
47
+ """Every path the next commit could introduce a migration through: tracked
48
+ changes vs HEAD (staged and unstaged), plus untracked files. On an unborn
49
+ branch (no HEAD) every tracked file is new content."""
50
+ paths = set()
51
+ for args in (["diff", "--cached", "--name-only"], ["diff", "--name-only"]):
52
+ r = run_git(args, root)
53
+ if r.returncode == 0:
54
+ paths.update(p for p in r.stdout.splitlines() if p.strip())
55
+ if not paths:
56
+ r = run_git(["ls-files"], root)
57
+ if r.returncode == 0:
58
+ paths.update(p for p in r.stdout.splitlines() if p.strip())
59
+ u = run_git(["ls-files", "--others", "--exclude-standard"], root)
60
+ if u.returncode == 0:
61
+ paths.update(p for p in u.stdout.splitlines() if p.strip())
62
+ return paths
63
+
64
+
65
+ def read_text(root, rel):
66
+ p = os.path.join(root, rel)
67
+ try:
68
+ if os.path.getsize(p) > MAX_FILE_BYTES:
69
+ return None
70
+ with open(p, encoding="utf-8", errors="replace") as f:
71
+ return f.read()
72
+ except Exception: # noqa: BLE001
73
+ return None
74
+
75
+
76
+ def main():
77
+ utf8_stdio()
78
+ root = project_root()
79
+ if not os.path.isdir(os.path.join(root, ".codearbiter")):
80
+ warn("no .codearbiter/ here — migration-pass.py records nothing outside "
81
+ "an initialized repo")
82
+ sys.exit(1)
83
+ migs = sorted(rel for rel in candidate_paths(root) if is_migration_path(rel, root))
84
+ digests = set()
85
+ for rel in migs:
86
+ text = read_text(root, rel)
87
+ if text is not None:
88
+ digests.add(content_digest(text))
89
+ # #604: same split as security-pass.py — candidate_paths(root)/read_text(root, …)
90
+ # above must stay bound to wherever this process is actually running (a
91
+ # linked worktree's own tree), but the MARKER write goes through
92
+ # marker_root(), which agrees with the H-14 guard's main-checkout-anchored
93
+ # read (D-2) even when this process has no CLAUDE_PROJECT_DIR set.
94
+ write_root = marker_root()
95
+ marker_dir = os.path.join(write_root, ".codearbiter", ".markers")
96
+ os.makedirs(marker_dir, exist_ok=True)
97
+ marker = os.path.join(marker_dir, "migration-gate-passed")
98
+ digests = sorted(digests)
99
+ # Atomic write (migration-002): a crash mid-write never leaves a half-written
100
+ # marker, which the backstop would read as an unrecognized digest and force a
101
+ # spurious gate re-run.
102
+ write_text_atomic(marker, "\n".join(digests) + ("\n" if digests else ""))
103
+ print(f"migration-gate pass recorded: {len(digests)} migration file(s) "
104
+ f"bound to {os.path.relpath(marker, write_root)}")
105
+
106
+
107
+ def run(host, argv=None):
108
+ """Host-seam entry point (ADR-0011): the __main__ guard calls this with the
109
+ plugin's loaded Host. Wraps main() unchanged — main() still communicates
110
+ via sys.exit/stdout/stderr, and its return value stays discarded exactly
111
+ as the old bare `main()` guard discarded it (so the process still exits 0
112
+ on a normal fall-through).
113
+
114
+ Wires `host` live (#257): primes `_hooklib`'s process-cached Host via
115
+ `set_host()` BEFORE main() runs, so any `get_host()` call downstream
116
+ resolves to the SAME instance the caller passed here — no second
117
+ `hostapi.load_host()`, and `run(fake_host)` genuinely exercises
118
+ `fake_host`."""
119
+ set_host(host)
120
+ main()
121
+ return 0
122
+
123
+
124
+ if __name__ == "__main__":
125
+ # hostapi is imported here (not at module top) so OB-S1's stdlib-import
126
+ # scan of this producer keeps its exact allowlist; hostapi is the same-dir
127
+ # host seam (ADR-0011), stdlib-only like _hooklib.
128
+ import hostapi # noqa: PLC0415
129
+ sys.exit(run(hostapi.load_host()) or 0)