@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.
- package/LICENSE +661 -0
- package/README.md +558 -0
- package/package.json +35 -0
- package/plugins/ca-pi/CHANGELOG.md +1030 -0
- package/plugins/ca-pi/COMMANDS.md +90 -0
- package/plugins/ca-pi/ORCHESTRATOR.md +159 -0
- package/plugins/ca-pi/SKILLS.md +47 -0
- package/plugins/ca-pi/SPRINT.md +142 -0
- package/plugins/ca-pi/agents/INDEX.md +31 -0
- package/plugins/ca-pi/agents/architecture-drift-reviewer.md +86 -0
- package/plugins/ca-pi/agents/auth-crypto-reviewer.md +60 -0
- package/plugins/ca-pi/agents/backend-author.md +60 -0
- package/plugins/ca-pi/agents/checkpoint-aggregator.md +111 -0
- package/plugins/ca-pi/agents/coverage-auditor.md +71 -0
- package/plugins/ca-pi/agents/decision-challenger.md +116 -0
- package/plugins/ca-pi/agents/dependency-reviewer.md +79 -0
- package/plugins/ca-pi/agents/design-quality-reviewer.md +80 -0
- package/plugins/ca-pi/agents/finding-triage.md +86 -0
- package/plugins/ca-pi/agents/frontend-author.md +64 -0
- package/plugins/ca-pi/agents/grader.md +173 -0
- package/plugins/ca-pi/agents/infra-author.md +64 -0
- package/plugins/ca-pi/agents/map-deps.md +35 -0
- package/plugins/ca-pi/agents/map-structure.md +37 -0
- package/plugins/ca-pi/agents/migration-reviewer.md +65 -0
- package/plugins/ca-pi/agents/scout.md +127 -0
- package/plugins/ca-pi/agents/security-reviewer.md +72 -0
- package/plugins/ca-pi/agents/tribunal-lens-reviewer.md +65 -0
- package/plugins/ca-pi/extensions/codearbiter-child.js +1885 -0
- package/plugins/ca-pi/extensions/codearbiter.js +9802 -0
- package/plugins/ca-pi/generated/command-catalog.json +197 -0
- package/plugins/ca-pi/generated/roles.json +213 -0
- package/plugins/ca-pi/helpers/windows-supervisor.js +205 -0
- package/plugins/ca-pi/hooks/_activationlib.py +196 -0
- package/plugins/ca-pi/hooks/_arbiterstatelib.py +208 -0
- package/plugins/ca-pi/hooks/_babysitlib.py +76 -0
- package/plugins/ca-pi/hooks/_bashguardlib.py +1667 -0
- package/plugins/ca-pi/hooks/_boxlib.py +131 -0
- package/plugins/ca-pi/hooks/_colorlib.py +304 -0
- package/plugins/ca-pi/hooks/_durabilitylib.py +186 -0
- package/plugins/ca-pi/hooks/_entrylib.py +41 -0
- package/plugins/ca-pi/hooks/_fmtlib.py +161 -0
- package/plugins/ca-pi/hooks/_gitexec.py +45 -0
- package/plugins/ca-pi/hooks/_githooks.py +920 -0
- package/plugins/ca-pi/hooks/_gitlib.py +110 -0
- package/plugins/ca-pi/hooks/_hooklib.py +595 -0
- package/plugins/ca-pi/hooks/_host.py +115 -0
- package/plugins/ca-pi/hooks/_intentlib.py +242 -0
- package/plugins/ca-pi/hooks/_ledgerlib.py +1035 -0
- package/plugins/ca-pi/hooks/_metricslib.py +709 -0
- package/plugins/ca-pi/hooks/_pathnorm.py +74 -0
- package/plugins/ca-pi/hooks/_planfilelib.py +664 -0
- package/plugins/ca-pi/hooks/_previewlib.py +193 -0
- package/plugins/ca-pi/hooks/_protectedlib.py +312 -0
- package/plugins/ca-pi/hooks/_protectedstatelib.py +411 -0
- package/plugins/ca-pi/hooks/_provenancelib.py +971 -0
- package/plugins/ca-pi/hooks/_prunelib.py +1398 -0
- package/plugins/ca-pi/hooks/_prunepolicy.py +235 -0
- package/plugins/ca-pi/hooks/_readinjectlib.py +1080 -0
- package/plugins/ca-pi/hooks/_releaselib.py +2657 -0
- package/plugins/ca-pi/hooks/_scopelib.py +262 -0
- package/plugins/ca-pi/hooks/_segmentslib.py +278 -0
- package/plugins/ca-pi/hooks/_sensitivelib.py +270 -0
- package/plugins/ca-pi/hooks/_sessionlib.py +78 -0
- package/plugins/ca-pi/hooks/_sloplib.py +244 -0
- package/plugins/ca-pi/hooks/_standuplib.py +214 -0
- package/plugins/ca-pi/hooks/_subagentslib.py +219 -0
- package/plugins/ca-pi/hooks/_taskboardlib.py +1088 -0
- package/plugins/ca-pi/hooks/_updatelib.py +278 -0
- package/plugins/ca-pi/hooks/babysit.py +47 -0
- package/plugins/ca-pi/hooks/boardsync.py +129 -0
- package/plugins/ca-pi/hooks/doctor.py +420 -0
- package/plugins/ca-pi/hooks/git-enforce.py +325 -0
- package/plugins/ca-pi/hooks/hostapi.py +460 -0
- package/plugins/ca-pi/hooks/init-codearbiter.py +225 -0
- package/plugins/ca-pi/hooks/metrics.py +62 -0
- package/plugins/ca-pi/hooks/migration-pass.py +129 -0
- package/plugins/ca-pi/hooks/pi-bridge.py +543 -0
- package/plugins/ca-pi/hooks/post-write-edit.py +231 -0
- package/plugins/ca-pi/hooks/pre-bash.py +90 -0
- package/plugins/ca-pi/hooks/pre-edit.py +284 -0
- package/plugins/ca-pi/hooks/pre-read.py +81 -0
- package/plugins/ca-pi/hooks/pre-write.py +217 -0
- package/plugins/ca-pi/hooks/preview.py +69 -0
- package/plugins/ca-pi/hooks/prune-transcript.py +232 -0
- package/plugins/ca-pi/hooks/releasehash.py +216 -0
- package/plugins/ca-pi/hooks/security-pass.py +139 -0
- package/plugins/ca-pi/hooks/session-start.py +1218 -0
- package/plugins/ca-pi/hooks/statusline.py +736 -0
- package/plugins/ca-pi/hooks/taskwrite.py +351 -0
- package/plugins/ca-pi/hooks/update-refresh.py +51 -0
- package/plugins/ca-pi/hooks/wire-statusline.py +435 -0
- package/plugins/ca-pi/includes/anti-slop-design/INDEX.md +55 -0
- package/plugins/ca-pi/includes/anti-slop-design/color.md +43 -0
- package/plugins/ca-pi/includes/anti-slop-design/core.md +244 -0
- package/plugins/ca-pi/includes/anti-slop-design/images.md +32 -0
- package/plugins/ca-pi/includes/anti-slop-design/layout.md +45 -0
- package/plugins/ca-pi/includes/anti-slop-design/medium-cli.md +39 -0
- package/plugins/ca-pi/includes/anti-slop-design/medium-dataviz.md +43 -0
- package/plugins/ca-pi/includes/anti-slop-design/medium-diagram.md +35 -0
- package/plugins/ca-pi/includes/anti-slop-design/medium-documents.md +70 -0
- package/plugins/ca-pi/includes/anti-slop-design/medium-slides.md +30 -0
- package/plugins/ca-pi/includes/anti-slop-design/medium-web.md +39 -0
- package/plugins/ca-pi/includes/anti-slop-design/typography.md +51 -0
- package/plugins/ca-pi/includes/author-tdd-workflow.md +14 -0
- package/plugins/ca-pi/includes/compaction-charter.md +16 -0
- package/plugins/ca-pi/includes/cut-docs.md +16 -0
- package/plugins/ca-pi/includes/dev-mode.md +30 -0
- package/plugins/ca-pi/includes/farm.md +237 -0
- package/plugins/ca-pi/includes/fresh-verification.md +14 -0
- package/plugins/ca-pi/includes/harvest.md +69 -0
- package/plugins/ca-pi/includes/maturity-coverage.md +102 -0
- package/plugins/ca-pi/includes/pi-host-notes.md +69 -0
- package/plugins/ca-pi/includes/redirect.md +69 -0
- package/plugins/ca-pi/includes/reference-map.md +22 -0
- package/plugins/ca-pi/includes/review-matrix.md +14 -0
- package/plugins/ca-pi/includes/reviewer-contract.md +53 -0
- package/plugins/ca-pi/includes/routing-table.md +47 -0
- package/plugins/ca-pi/includes/security-gate-record.md +22 -0
- package/plugins/ca-pi/includes/smarts/core.md +90 -0
- package/plugins/ca-pi/includes/smarts/decision-log-format.md +56 -0
- package/plugins/ca-pi/routines/INDEX.md +32 -0
- package/plugins/ca-pi/routines/brainstorming/SKILL.md +122 -0
- package/plugins/ca-pi/routines/commit-gate/SKILL.md +151 -0
- package/plugins/ca-pi/routines/context-check/SKILL.md +85 -0
- package/plugins/ca-pi/routines/context-creation/SKILL.md +171 -0
- package/plugins/ca-pi/routines/crypto-compliance/SKILL.md +41 -0
- package/plugins/ca-pi/routines/debug/SKILL.md +99 -0
- package/plugins/ca-pi/routines/decision-lifecycle/SKILL.md +104 -0
- package/plugins/ca-pi/routines/decision-lifecycle/references/adr-template.md +74 -0
- package/plugins/ca-pi/routines/decision-variance/SKILL.md +147 -0
- package/plugins/ca-pi/routines/decompose/SKILL.md +168 -0
- package/plugins/ca-pi/routines/dispatching-parallel-agents/SKILL.md +76 -0
- package/plugins/ca-pi/routines/executing-plans/SKILL.md +83 -0
- package/plugins/ca-pi/routines/finishing-a-development-branch/SKILL.md +91 -0
- package/plugins/ca-pi/routines/post-merge-cleanup/SKILL.md +233 -0
- package/plugins/ca-pi/routines/refactor/SKILL.md +91 -0
- package/plugins/ca-pi/routines/release/SKILL.md +315 -0
- package/plugins/ca-pi/routines/secret-handling/SKILL.md +67 -0
- package/plugins/ca-pi/routines/security-architecture/SKILL.md +63 -0
- package/plugins/ca-pi/routines/skill-author/SKILL.md +108 -0
- package/plugins/ca-pi/routines/skill-author/references/skill-template.md +58 -0
- package/plugins/ca-pi/routines/subagent-driven-development/SKILL.md +149 -0
- package/plugins/ca-pi/routines/subagent-driven-development/references/farm-dispatch.md +145 -0
- package/plugins/ca-pi/routines/tdd/SKILL.md +139 -0
- package/plugins/ca-pi/routines/tribunal/SKILL.md +109 -0
- package/plugins/ca-pi/routines/tribunal/references/ai-markers.md +29 -0
- package/plugins/ca-pi/routines/tribunal/references/cost-and-models.md +64 -0
- package/plugins/ca-pi/routines/tribunal/references/finding-record.md +27 -0
- package/plugins/ca-pi/routines/tribunal/references/issue-filing.md +47 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/appsec.md +22 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/architecture.md +23 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/coverage.md +20 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/infra.md +24 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/migration.md +22 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/observability.md +21 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/performance.md +22 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/reliability.md +23 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/secrets-supply.md +22 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/test-fidelity.md +24 -0
- package/plugins/ca-pi/routines/tribunal/references/lenses/typesafety.md +21 -0
- package/plugins/ca-pi/routines/tribunal/references/report.md +19 -0
- package/plugins/ca-pi/routines/tribunal/references/schemas.md +58 -0
- package/plugins/ca-pi/routines/tribunal/references/telemetry.md +28 -0
- package/plugins/ca-pi/routines/tribunal/references/triage.md +53 -0
- package/plugins/ca-pi/routines/using-git-worktrees/SKILL.md +85 -0
- package/plugins/ca-pi/routines/writing-plans/SKILL.md +129 -0
- package/plugins/ca-pi/routines/writing-plans/references/farm-plan.md +50 -0
- package/plugins/ca-pi/skills/ca-add-dep/SKILL.md +88 -0
- package/plugins/ca-pi/skills/ca-adr/SKILL.md +30 -0
- package/plugins/ca-pi/skills/ca-adr-status/SKILL.md +30 -0
- package/plugins/ca-pi/skills/ca-arbiter/SKILL.md +36 -0
- package/plugins/ca-pi/skills/ca-audit/SKILL.md +51 -0
- package/plugins/ca-pi/skills/ca-btw/SKILL.md +23 -0
- package/plugins/ca-pi/skills/ca-checkpoint/SKILL.md +50 -0
- package/plugins/ca-pi/skills/ca-chore/SKILL.md +58 -0
- package/plugins/ca-pi/skills/ca-cleanup/SKILL.md +55 -0
- package/plugins/ca-pi/skills/ca-commands/SKILL.md +21 -0
- package/plugins/ca-pi/skills/ca-commit/SKILL.md +27 -0
- package/plugins/ca-pi/skills/ca-conflict/SKILL.md +61 -0
- package/plugins/ca-pi/skills/ca-context-check/SKILL.md +32 -0
- package/plugins/ca-pi/skills/ca-create-context/SKILL.md +32 -0
- package/plugins/ca-pi/skills/ca-debug/SKILL.md +42 -0
- package/plugins/ca-pi/skills/ca-decompose/SKILL.md +30 -0
- package/plugins/ca-pi/skills/ca-dev/SKILL.md +42 -0
- package/plugins/ca-pi/skills/ca-doctor/SKILL.md +44 -0
- package/plugins/ca-pi/skills/ca-feature/SKILL.md +105 -0
- package/plugins/ca-pi/skills/ca-fix/SKILL.md +42 -0
- package/plugins/ca-pi/skills/ca-init/SKILL.md +56 -0
- package/plugins/ca-pi/skills/ca-metrics/SKILL.md +80 -0
- package/plugins/ca-pi/skills/ca-new-skill/SKILL.md +34 -0
- package/plugins/ca-pi/skills/ca-override/SKILL.md +72 -0
- package/plugins/ca-pi/skills/ca-pr/SKILL.md +61 -0
- package/plugins/ca-pi/skills/ca-preview/SKILL.md +86 -0
- package/plugins/ca-pi/skills/ca-prune/SKILL.md +100 -0
- package/plugins/ca-pi/skills/ca-reconcile/SKILL.md +43 -0
- package/plugins/ca-pi/skills/ca-refactor/SKILL.md +43 -0
- package/plugins/ca-pi/skills/ca-release/SKILL.md +57 -0
- package/plugins/ca-pi/skills/ca-review/SKILL.md +63 -0
- package/plugins/ca-pi/skills/ca-spike/SKILL.md +41 -0
- package/plugins/ca-pi/skills/ca-sprint/SKILL.md +44 -0
- package/plugins/ca-pi/skills/ca-standup/SKILL.md +112 -0
- package/plugins/ca-pi/skills/ca-status/SKILL.md +56 -0
- package/plugins/ca-pi/skills/ca-task/SKILL.md +61 -0
- package/plugins/ca-pi/skills/ca-threat-model/SKILL.md +45 -0
- package/plugins/ca-pi/skills/ca-tribunal/SKILL.md +44 -0
- 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)
|