@arbiterforge/ca-pi 0.15.12 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/plugins/ca-pi/CHANGELOG.md +33 -0
  4. package/plugins/ca-pi/SKILLS.md +1 -1
  5. package/plugins/ca-pi/agents/infra-author.md +7 -1
  6. package/plugins/ca-pi/agents/scout.md +40 -8
  7. package/plugins/ca-pi/generated/command-catalog.json +1 -1
  8. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
  9. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
  10. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
  11. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
  12. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
  13. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
  14. package/plugins/ca-pi/helpers/artifacts/release.json +104 -6
  15. package/plugins/ca-pi/hooks/_activationlib.py +97 -13
  16. package/plugins/ca-pi/hooks/_arbiterstatelib.py +7 -2
  17. package/plugins/ca-pi/hooks/_artifactauthoritylib.py +313 -76
  18. package/plugins/ca-pi/hooks/_artifactlib.py +166 -2
  19. package/plugins/ca-pi/hooks/_artifactpromptlib.py +106 -2
  20. package/plugins/ca-pi/hooks/_bashguardlib.py +14 -0
  21. package/plugins/ca-pi/hooks/_contextreportlib.py +564 -6
  22. package/plugins/ca-pi/hooks/_contextselectlib.py +464 -0
  23. package/plugins/ca-pi/hooks/_contextsnapshotlib.py +999 -0
  24. package/plugins/ca-pi/hooks/_debughandofflib.py +767 -0
  25. package/plugins/ca-pi/hooks/_hooklib.py +1 -0
  26. package/plugins/ca-pi/hooks/_host.py +1 -1
  27. package/plugins/ca-pi/hooks/_intentlib.py +5 -5
  28. package/plugins/ca-pi/hooks/_protectedlib.py +3 -3
  29. package/plugins/ca-pi/hooks/_protectedstatelib.py +152 -2
  30. package/plugins/ca-pi/hooks/_provenancelib.py +604 -34
  31. package/plugins/ca-pi/hooks/_readinjectlib.py +131 -1
  32. package/plugins/ca-pi/hooks/artifact-authority.py +20 -1
  33. package/plugins/ca-pi/hooks/debug-handoff.py +85 -0
  34. package/plugins/ca-pi/hooks/doctor.py +4 -2
  35. package/plugins/ca-pi/hooks/hostapi.py +1 -1
  36. package/plugins/ca-pi/hooks/init-codearbiter.py +143 -15
  37. package/plugins/ca-pi/hooks/prompt-submit.py +14 -0
  38. package/plugins/ca-pi/hooks/session-start.py +26 -7
  39. package/plugins/ca-pi/includes/author-tdd-workflow.md +16 -4
  40. package/plugins/ca-pi/includes/helper-invocation.md +128 -0
  41. package/plugins/ca-pi/includes/pi-host-notes.md +13 -0
  42. package/plugins/ca-pi/includes/routing-table.md +1 -1
  43. package/plugins/ca-pi/package.json +1 -1
  44. package/plugins/ca-pi/routines/INDEX.md +1 -1
  45. package/plugins/ca-pi/routines/commit-gate/SKILL.md +5 -3
  46. package/plugins/ca-pi/routines/context-check/SKILL.md +103 -17
  47. package/plugins/ca-pi/routines/context-creation/SKILL.md +290 -54
  48. package/plugins/ca-pi/routines/debug/SKILL.md +303 -55
  49. package/plugins/ca-pi/routines/debug/references/debug-handoff.schema.json +879 -0
  50. package/plugins/ca-pi/routines/debug/references/handoff.md +155 -0
  51. package/plugins/ca-pi/routines/decompose/SKILL.md +13 -0
  52. package/plugins/ca-pi/routines/subagent-driven-development/SKILL.md +34 -0
  53. package/plugins/ca-pi/routines/tdd/SKILL.md +91 -17
  54. package/plugins/ca-pi/skills/ca-commit/SKILL.md +5 -3
  55. package/plugins/ca-pi/skills/ca-context-check/SKILL.md +103 -17
  56. package/plugins/ca-pi/skills/ca-create-context/SKILL.md +290 -54
  57. package/plugins/ca-pi/skills/ca-debug/SKILL.md +303 -55
  58. package/plugins/ca-pi/skills/ca-decompose/SKILL.md +13 -0
  59. package/plugins/ca-pi/skills/ca-feature/SKILL.md +1 -1
  60. package/plugins/ca-pi/skills/ca-fix/SKILL.md +36 -3
  61. package/plugins/ca-pi/skills/ca-init/SKILL.md +19 -0
  62. package/plugins/ca-pi/skills/ca-review/SKILL.md +33 -4
  63. package/plugins/ca-pi/skills/ca-status/SKILL.md +5 -0
  64. package/plugins/ca-pi/skills/ca-task/SKILL.md +55 -8
package/README.md CHANGED
@@ -10,7 +10,7 @@ project context. You decide. codeArbiter enforces.
10
10
  <img alt="Claude Code plugin" src="https://img.shields.io/badge/Claude_Code-plugin-d97757">
11
11
  <img alt="Codex plugin" src="https://img.shields.io/badge/OpenAI_Codex-plugin-10a37f">
12
12
  <img alt="Pi Feature Forge preview" src="https://img.shields.io/badge/ca--pi-Feature_Forge_preview-d97757">
13
- <img alt="version 2.23.4" src="https://img.shields.io/badge/version-2.23.4-2b7489">
13
+ <img alt="version 2.24.1" src="https://img.shields.io/badge/version-2.24.1-2b7489">
14
14
  <img alt="core lanes" src="https://img.shields.io/badge/core_lanes-18-555">
15
15
  <img alt="skills" src="https://img.shields.io/badge/skills-22-555">
16
16
  <img alt="agents" src="https://img.shields.io/badge/agents-20-555">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arbiterforge/ca-pi",
3
- "version": "0.15.12",
3
+ "version": "0.16.1",
4
4
  "license": "AGPL-3.0-only",
5
5
  "repository": {
6
6
  "type": "git",
@@ -4,6 +4,39 @@ All notable changes to `ca-pi` are documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.16.1] - 2026-10-04
8
+
9
+ ### Fixed
10
+
11
+ - Carry the shared Claude review-join fix in the vendored authority library. Pi
12
+ behavior is unchanged.
13
+
14
+ ## [0.16.0] - 2026-10-03
15
+
16
+ ### Added
17
+
18
+ - Add bounded task-context selection, per-document freshness and attempt accounting
19
+ to the shared governance payload, with explicit actor-delivery gaps.
20
+ - Carry shared context resources while preserving Pi's refusal of the unqualified
21
+ repository-context write workflow. No live Pi qualification is claimed.
22
+
23
+ ### Fixed
24
+
25
+ - Preserve Unicode whitespace in activation frontmatter while requiring a real
26
+ initialized marker outside fenced code and comments.
27
+ - Synchronize the packaged adapter version with its release manifest.
28
+ - Validate diagnostic handoffs before repair or task follow-ups, preserving
29
+ the caller's authority and checking that the evidence is current.
30
+ - Keep diagnostic outcomes, regression reuse and helper invocation consistent
31
+ across the generated host packages.
32
+
33
+ ## [0.15.13] - 2026-10-04
34
+
35
+ ### Changed
36
+
37
+ - Synchronize the shared evidence-context reader with native UTF-8 canonical
38
+ bytes while preserving digest and frozen-context checks.
39
+
7
40
  ## [0.15.12] - 2026-10-03
8
41
 
9
42
  ### Fixed
@@ -77,7 +77,7 @@ skill is invoked — never bulk-read this directory.
77
77
 
78
78
  | Skill | Purpose |
79
79
  |---|---|
80
- | `/ca-debug` | Investigate an unexplained defect or unexpected behavior without changing application code. Use for root-cause diagnosis and an evidence-backed handoff. A no-action close records a board note. Not for implementing a known fix, new features, or explanation-only questions. |
80
+ | `/ca-debug` | Investigate an unexplained defect or unexpected behavior without changing application code. Use for root-cause diagnosis and an evidence-backed handoff, including a bounded unresolved result. Not for implementing a known fix, new features, or explanation-only questions. |
81
81
 
82
82
  ### Review
83
83
 
@@ -9,7 +9,12 @@ model: sonnet
9
9
 
10
10
  # Infrastructure Author Agent
11
11
 
12
- Infrastructure implementation executor. Write IaC, container, CI/CD, and deployment configuration only after the relevant planning phase has completed.
12
+ Infrastructure implementation executor. Write IaC, container, CI/CD, and deployment
13
+ configuration only after the owning route clears its prerequisite: approved
14
+ feature criteria (and a plan where applicable) for `/feature`, or the original
15
+ caller's repair authority and a bug-origin regression obligation for `/fix`.
16
+ The regression must be red for the
17
+ right reason before implementation. Do not fabricate a feature artifact for a fix.
13
18
 
14
19
  ## Required Reading at the Start of Every Task
15
20
 
@@ -17,6 +22,7 @@ Read in full before writing any infrastructure code:
17
22
 
18
23
  1. `<project-root>/.codearbiter/tech-stack.md` — IaC tool (OpenTofu, Ansible, Helm, etc.), container runtime, CI/CD system, approved registries
19
24
  2. `<project-root>/.codearbiter/security-controls.md` — security boundaries, approved secrets store, compliance requirements affecting infrastructure
25
+ 3. `<plugin-root>/includes/author-tdd-workflow.md` — the shared six-step TDD execution order. Read it; do not carry a remembered copy.
20
26
 
21
27
  ## Security Rules
22
28
 
@@ -18,16 +18,48 @@ Dispatched by `decision-variance` and `context-creation` to scan an assigned cod
18
18
  - Codebase sections have natural ownership boundaries that map to scout assignments.
19
19
  - Parallel evidence gathering across multiple areas is needed.
20
20
 
21
- Under ~50 files, `decision-variance` scans inline. `context-creation` always
22
- dispatches its six fixed, isolated scout assignments because its report-only
23
- synthesis boundary depends on the orchestrator not reading raw source.
24
-
25
- ## Scout Assignment Format
21
+ Under ~50 files, `decision-variance` scans inline. When a qualified context
22
+ profile becomes available, `context-creation` dispatches its six fixed,
23
+ isolated scout assignments because its report-only synthesis boundary depends
24
+ on the orchestrator not reading raw source.
25
+
26
+ ## Context-creation assignment
27
+
28
+ The shared `scout` frontmatter includes Bash for existing `decision-variance`
29
+ assignments. It is therefore not a context-creation containment profile.
30
+ `context-creation` must BLOCK before dispatch until the active host supplies a
31
+ separate, fresh child identity with host-enforced read-only restriction and an
32
+ observed denied benign write, successful source read, and bounded report
33
+ delivery. A prose instruction to avoid writes is not enforcement. An explicit
34
+ session-only `context-scout` may retain Bash solely for exact, frozen
35
+ `git hash-object -- '<source path>'` reads when a caller-owned command guard and
36
+ read-only OS phase are independently qualified. No other Bash command is part
37
+ of this assignment, and the inherited shared `scout` role remains ineligible. This
38
+ assignment preserves report-only synthesis; the orchestrator uses reports and
39
+ does not load raw source after pre-flight. A qualified context scout must emit
40
+ the versioned closed report envelope accepted by `_contextreportlib`, bound to
41
+ its assignment, attempt, snapshot and scope. A trusted read-only hash collector
42
+ must supply each cited digest when the restricted child has no Bash.
43
+
44
+ For context-creation, report each assigned category even when the search finds
45
+ no match. Use the closed outcome vocabulary: `observed`, `not_applicable`,
46
+ `not_found_in_scope`, `not_inspected`, `ambiguous`, `failed`, or `oversize`.
47
+ Negative results name the searched scope and predicates; `not_applicable`
48
+ also needs a reason why the domain does not apply. Do not turn a missing report
49
+ or an incomplete search into a negative finding. Keep contradictory evidence
50
+ with both citations and describe the dispute without choosing a policy. A
51
+ reported convention, instruction, package script or ADR remains evidence:
52
+ never assign approval or policy authority from confidence, a source label or
53
+ the scout's own judgment. Do not propose a task or local ADR from an observed
54
+ pattern. This context-creation report rule does not change the separate
55
+ decision-variance Markdown assignment below.
56
+
57
+ ## Decision-variance assignment format
26
58
 
27
59
  The dispatching skill provides:
28
60
 
29
61
  ```
30
- You are a Scout for the decision-variance or context-creation skill.
62
+ You are a Scout for the decision-variance skill.
31
63
 
32
64
  Your scope: <specific paths or areas, e.g., "backend/src/auth/", "helm/", ".codearbiter/decisions/">
33
65
 
@@ -49,9 +81,9 @@ Constraints:
49
81
 
50
82
  ## Output Template
51
83
 
52
- Every scout returns this exact format.
84
+ Decision-variance scouts return this exact format.
53
85
 
54
- **Hash field:** For each cited file, emit its `git hash-object <path>` oid in the `hash` field. The scout already Read those files and has Bash — no additional pass is needed. A git oid is not a code excerpt; the "no excerpts" rule (file path and line numbers only) is not violated by including an oid.
86
+ **Hash field:** For decision-variance assignments, emit each cited file's `git hash-object <path>` oid in the `hash` field. That legacy role has Bash. A context-creation assignment needs a digest in the method and shape required by the closed report decoder. A qualified context-specific Bash route may use only its frozen exact `git hash-object -- '<source path>'` commands and must label the result `git-blob-normalized-sha1`. A different qualified read-only collector may provide `sha256-raw`, retaining that distinct method identity. A missing actual digest blocks the citation; neither the caller's snapshot checksum nor an injected answer substitutes for the child's or trusted collector's observed result. A git oid is not a code excerpt; the "no excerpts" rule (file path and line numbers only) is not violated by including an oid.
55
87
 
56
88
  ```markdown
57
89
  # Scout Report — <scope>
@@ -161,7 +161,7 @@
161
161
  },
162
162
  "debug": {
163
163
  "name": "debug",
164
- "description": "Investigate an unexplained defect or unexpected behavior without changing application code. Use for root-cause diagnosis and an evidence-backed handoff. A no-action close records a board note. Not for implementing a known fix, new features, or explanation-only questions.",
164
+ "description": "Investigate an unexplained defect or unexpected behavior without changing application code. Use for root-cause diagnosis and an evidence-backed handoff, including a bounded unresolved result. Not for implementing a known fix, new features, or explanation-only questions.",
165
165
  "skillPath": "skills/ca-debug/SKILL.md",
166
166
  "visibility": "advanced",
167
167
  "workflow": "change",
@@ -6,33 +6,131 @@
6
6
  "binaries": {
7
7
  "darwin/amd64": {
8
8
  "file": "ca-artifact-darwin-amd64",
9
- "sha256": "2b2f735f123364bdc23adf937ba3088fe996ece9b36a20dce14491f12c0a02da",
9
+ "sha256": "27fc51465a79c1525b1300b347cee4d09afde3c7b0d845414659302f5bbca2f7",
10
10
  "native_tested": true
11
11
  },
12
12
  "darwin/arm64": {
13
13
  "file": "ca-artifact-darwin-arm64",
14
- "sha256": "cfd38fc1d52a41659d8c01e4668e595fac806bdc36162e87f0118605e17e666c",
14
+ "sha256": "3a3d61be18eba426db4f260b9cbae0d3d3c89f47efe184395d7f011cc3b4f59a",
15
15
  "native_tested": true
16
16
  },
17
17
  "linux/amd64": {
18
18
  "file": "ca-artifact-linux-amd64",
19
- "sha256": "f73fb0e676f8bfd1222fdcaec7eeced54fe106f17dbf53aae50e86ac553bf1ce",
19
+ "sha256": "3b6178bb54d6a19fc56d99f8fced862ae151605a0373297a69e130d3857a8406",
20
20
  "native_tested": true
21
21
  },
22
22
  "linux/arm64": {
23
23
  "file": "ca-artifact-linux-arm64",
24
- "sha256": "70852f1dbebc00a730a6cffc3835f17f45c3c7c8e0d554b4b9f0a248aecb6ecb",
24
+ "sha256": "a5259074098932f815f33d3e1dea98e6d9a04277d1ada958469905da67d29437",
25
25
  "native_tested": true
26
26
  },
27
27
  "windows/amd64": {
28
28
  "file": "ca-artifact-windows-amd64.exe",
29
- "sha256": "7048725daa508e7aeccaa0b531fe49b816bdd9e28f56127afe46b17eb1b4f582",
29
+ "sha256": "8339c077262f861631631de49bca7cdafec5e0730b9e3676fd2db36f3c119036",
30
30
  "native_tested": true
31
31
  },
32
32
  "windows/arm64": {
33
33
  "file": "ca-artifact-windows-arm64.exe",
34
- "sha256": "294b5bd3418979fb094bcc4f480d7284fdcdcd4e5e89f0459073d5315e243e66",
34
+ "sha256": "f7b193cf0d1eb9ecd233bf1588f6290c2fb8bc41ad327918a740beb2099f719c",
35
35
  "native_tested": true
36
36
  }
37
+ },
38
+ "context_qualification": {
39
+ "darwin/amd64": {
40
+ "source_commit": "fa94c56afa59aaa3e429bf15613989bd444090f6",
41
+ "workflow": ".github/workflows/ci.yml",
42
+ "run_id": "37253197914",
43
+ "binary_sha256": "27fc51465a79c1525b1300b347cee4d09afde3c7b0d845414659302f5bbca2f7",
44
+ "observation_sha256": "f6a8c71afa0e06521de263ae49934ce33f4272ed40d0fec6d9a17f7a68357c87",
45
+ "module_sha256": "d5cccd13647e2c3a2a752b94b34dec4d4fc6aec5ced8fb0abce6ec9346ba1a7e",
46
+ "native_cases": [
47
+ "TestContextBoundedRender",
48
+ "TestContextDocumentContract",
49
+ "TestContextMarkdownPreservation",
50
+ "TestContextMutationAdmission",
51
+ "TestContextRecoveryAndPaths",
52
+ "TestContextRepresentationBoundary"
53
+ ]
54
+ },
55
+ "darwin/arm64": {
56
+ "source_commit": "fa94c56afa59aaa3e429bf15613989bd444090f6",
57
+ "workflow": ".github/workflows/ci.yml",
58
+ "run_id": "37253197914",
59
+ "binary_sha256": "3a3d61be18eba426db4f260b9cbae0d3d3c89f47efe184395d7f011cc3b4f59a",
60
+ "observation_sha256": "2ca37cd149e0efdf73929f0398334089cd3bbb1bba03c61407a485eb8edaecad",
61
+ "module_sha256": "d5cccd13647e2c3a2a752b94b34dec4d4fc6aec5ced8fb0abce6ec9346ba1a7e",
62
+ "native_cases": [
63
+ "TestContextBoundedRender",
64
+ "TestContextDocumentContract",
65
+ "TestContextMarkdownPreservation",
66
+ "TestContextMutationAdmission",
67
+ "TestContextRecoveryAndPaths",
68
+ "TestContextRepresentationBoundary"
69
+ ]
70
+ },
71
+ "linux/amd64": {
72
+ "source_commit": "fa94c56afa59aaa3e429bf15613989bd444090f6",
73
+ "workflow": ".github/workflows/ci.yml",
74
+ "run_id": "37253197914",
75
+ "binary_sha256": "3b6178bb54d6a19fc56d99f8fced862ae151605a0373297a69e130d3857a8406",
76
+ "observation_sha256": "85f789dc87517f96042a8e88da689100bcd561ddda863e0986ace221f18dbbab",
77
+ "module_sha256": "d5cccd13647e2c3a2a752b94b34dec4d4fc6aec5ced8fb0abce6ec9346ba1a7e",
78
+ "native_cases": [
79
+ "TestContextBoundedRender",
80
+ "TestContextDocumentContract",
81
+ "TestContextMarkdownPreservation",
82
+ "TestContextMutationAdmission",
83
+ "TestContextRecoveryAndPaths",
84
+ "TestContextRepresentationBoundary"
85
+ ]
86
+ },
87
+ "linux/arm64": {
88
+ "source_commit": "fa94c56afa59aaa3e429bf15613989bd444090f6",
89
+ "workflow": ".github/workflows/ci.yml",
90
+ "run_id": "37253197914",
91
+ "binary_sha256": "a5259074098932f815f33d3e1dea98e6d9a04277d1ada958469905da67d29437",
92
+ "observation_sha256": "50afdbe9ce2e9893cc3557fc230010baf5c093da6f0839f17ed15a3c45c69905",
93
+ "module_sha256": "d5cccd13647e2c3a2a752b94b34dec4d4fc6aec5ced8fb0abce6ec9346ba1a7e",
94
+ "native_cases": [
95
+ "TestContextBoundedRender",
96
+ "TestContextDocumentContract",
97
+ "TestContextMarkdownPreservation",
98
+ "TestContextMutationAdmission",
99
+ "TestContextRecoveryAndPaths",
100
+ "TestContextRepresentationBoundary"
101
+ ]
102
+ },
103
+ "windows/amd64": {
104
+ "source_commit": "fa94c56afa59aaa3e429bf15613989bd444090f6",
105
+ "workflow": ".github/workflows/ci.yml",
106
+ "run_id": "37253197914",
107
+ "binary_sha256": "8339c077262f861631631de49bca7cdafec5e0730b9e3676fd2db36f3c119036",
108
+ "observation_sha256": "56e2f848ba306bbf4acbb2febdfd298e08988761a53b87d9dded7bbf23c9a0ea",
109
+ "module_sha256": "d5cccd13647e2c3a2a752b94b34dec4d4fc6aec5ced8fb0abce6ec9346ba1a7e",
110
+ "native_cases": [
111
+ "TestContextBoundedRender",
112
+ "TestContextDocumentContract",
113
+ "TestContextMarkdownPreservation",
114
+ "TestContextMutationAdmission",
115
+ "TestContextRecoveryAndPaths",
116
+ "TestContextRepresentationBoundary"
117
+ ]
118
+ },
119
+ "windows/arm64": {
120
+ "source_commit": "fa94c56afa59aaa3e429bf15613989bd444090f6",
121
+ "workflow": ".github/workflows/ci.yml",
122
+ "run_id": "37253197914",
123
+ "binary_sha256": "f7b193cf0d1eb9ecd233bf1588f6290c2fb8bc41ad327918a740beb2099f719c",
124
+ "observation_sha256": "5db05023f4fe8441c0e2f664fc0704f42967285a54566f801f9e37f9ad4819a5",
125
+ "module_sha256": "d5cccd13647e2c3a2a752b94b34dec4d4fc6aec5ced8fb0abce6ec9346ba1a7e",
126
+ "native_cases": [
127
+ "TestContextBoundedRender",
128
+ "TestContextDocumentContract",
129
+ "TestContextMarkdownPreservation",
130
+ "TestContextMutationAdmission",
131
+ "TestContextRecoveryAndPaths",
132
+ "TestContextRepresentationBoundary"
133
+ ]
134
+ }
37
135
  }
38
136
  }
@@ -127,26 +127,110 @@ def marker_root(payload=None):
127
127
  return get_host().marker_root(payload)
128
128
 
129
129
  ARBITER_RE = re.compile(r"^\s*arbiter:\s*enabled\s*$", re.I)
130
+ _BODY_MARKER_RE = re.compile(r"^\s*<!--\s*INITIALIZED\s*-->\s*$")
131
+ _FENCE_RE = re.compile(r"^ {0,3}(`{3,}|~{3,})(?:[^`~].*)?$")
130
132
 
131
- def frontmatter_enabled_text(text):
132
- """(enabled, malformed) for CONTEXT.md *content* (see frontmatter_enabled).
133
- Split out so the #159 Write/Edit guard can vet the RESULTING content of an
134
- edit — 'does this edit keep the repo arbiter-enabled?' — without going to
135
- disk, sharing one parser with the on-disk activation check so the two never
136
- disagree on what 'enabled' means."""
133
+ def context_state_text(text):
134
+ """Return (enabled, malformed, initialized) from one CONTEXT.md parse."""
137
135
  lines = (text or "").split("\n")
138
136
  if not lines:
139
- return (False, False)
137
+ return (False, False, False)
140
138
  first = lines[0].lstrip("") # tolerate a leading UTF-8 BOM
141
139
  if first.strip() != "---":
142
- return (False, False) # no opening delimiter — dormant, not malformed
143
- found = False
144
- for ln in lines[1:]:
140
+ return (False, False, False)
141
+ enabled = False
142
+ body_start = None
143
+ for index, ln in enumerate(lines[1:], 1):
145
144
  if ln.strip() == "---":
146
- return (found, False) # closing delimiter — decision is final
145
+ body_start = index + 1
146
+ break
147
147
  if ARBITER_RE.match(ln):
148
- found = True
149
- return (False, True) # opened but never closed — malformed
148
+ enabled = True
149
+ if body_start is None:
150
+ return (False, True, False)
151
+ if not enabled:
152
+ return (False, False, False)
153
+
154
+ fence_char = ""
155
+ fence_length = 0
156
+ in_comment = False
157
+ for ln in lines[body_start:]:
158
+ stripped = ln.strip()
159
+ if fence_char:
160
+ if re.fullmatch(r" {0,3}" + re.escape(fence_char) +
161
+ "{" + str(fence_length) + r",}\s*", ln):
162
+ fence_char = ""
163
+ continue
164
+ if in_comment:
165
+ if "-->" in ln:
166
+ in_comment = False
167
+ continue
168
+ fence = _FENCE_RE.match(ln)
169
+ if fence:
170
+ fence_char, fence_length = fence.group(1)[0], len(fence.group(1))
171
+ continue
172
+ if _BODY_MARKER_RE.fullmatch(ln):
173
+ return (True, False, True)
174
+ if "<!--" in stripped and "-->" not in stripped:
175
+ in_comment = True
176
+ return (True, False, False)
177
+
178
+
179
+ def frontmatter_enabled_text(text):
180
+ """(enabled, malformed) for CONTEXT.md content; shared with body parsing."""
181
+ enabled, malformed, _initialized = context_state_text(text)
182
+ return (enabled, malformed)
183
+
184
+
185
+ def initialized_body_text(text):
186
+ """True only for a standalone marker in an enabled CONTEXT.md body."""
187
+ return context_state_text(text)[2]
188
+
189
+
190
+ def passive_activation_inventory(root):
191
+ """Read only the explicit root's activation marker; execute no project tools.
192
+
193
+ This is a pre-session inspection result, not proof of repository identity,
194
+ host capability, network policy, or permission to activate. The caller must
195
+ inventory sensitive paths before sending any source to a model.
196
+ """
197
+ root = os.path.abspath(root)
198
+ if not os.path.isdir(root) or os.path.islink(root):
199
+ raise ValueError("passive inspection requires an existing, non-link directory")
200
+ state_dir = os.path.join(root, ".codearbiter")
201
+ ctx = os.path.join(state_dir, "CONTEXT.md")
202
+ if os.path.islink(state_dir) or os.path.islink(ctx):
203
+ raise ValueError("passive inspection refuses linked context paths")
204
+ if os.path.lexists(state_dir) and not os.path.isdir(state_dir):
205
+ raise ValueError(".codearbiter is not a directory")
206
+ if os.path.lexists(ctx) and not os.path.isfile(ctx):
207
+ raise ValueError("CONTEXT.md is not a regular file")
208
+ exists = os.path.isfile(ctx)
209
+ enabled = malformed = initialized = False
210
+ if exists:
211
+ if os.path.getsize(ctx) > 1024 * 1024:
212
+ raise ValueError("CONTEXT.md exceeds passive inspection read limit")
213
+ with open(ctx, encoding="utf-8") as stream:
214
+ enabled, malformed, initialized = context_state_text(stream.read(1024 * 1024 + 1))
215
+ effect = {"capability": "unverified", "authorization": "not_established",
216
+ "performed": False}
217
+ activation_behaviors = {
218
+ "git_hooks_exclusions": "scaffold may write Git-local exclusions and hooks; active startup may refresh hooks",
219
+ "git_fetch": "active startup may spawn detached Git fetch",
220
+ "update_refresh": "active startup may spawn detached update refresh",
221
+ "inference_transport": "host-selected model may receive approved source after authorization",
222
+ }
223
+ return {
224
+ "profile": "passive-before-activation",
225
+ "root": root,
226
+ "root_identity": "caller_supplied_unverified",
227
+ "context": {"exists": exists, "enabled": enabled,
228
+ "initialized": initialized, "malformed": malformed},
229
+ "effects": {name: {**effect, "activation_behavior": behavior}
230
+ for name, behavior in activation_behaviors.items()},
231
+ "source_reads": "none",
232
+ "sensitive_path_inventory": "pending",
233
+ }
150
234
 
151
235
 
152
236
  def frontmatter_enabled(ctx_path):
@@ -52,6 +52,7 @@ import re
52
52
  # exactly like a failure to import `os` would. No circularity risk: _modelib
53
53
  # imports _activationlib and _hooklib, neither of which imports this module.
54
54
  from _modelib import current_mode as _modelib_current_mode
55
+ from _activationlib import frontmatter_enabled_text as _activation_enabled_text
55
56
 
56
57
  # mtime-keyed memo: statusline.py is a short-lived subprocess, but a single render
57
58
  # can resolve arbiter_state more than once (safe() probes), and the StopHook fires
@@ -143,14 +144,18 @@ def _arbiter_enabled(ctx_path, frontmatter_enabled=None, ctx_text=None):
143
144
  When `ctx_text` is supplied (performance-003: the caller already read
144
145
  CONTEXT.md), the decision is made from that text — no re-read of ctx_path."""
145
146
  if ctx_text is not None:
146
- return frontmatter_text(ctx_text).get("arbiter", "").lower() == "enabled"
147
+ return _activation_enabled_text(ctx_text)[0]
147
148
  if frontmatter_enabled is not None:
148
149
  try:
149
150
  enabled, _malformed = frontmatter_enabled(ctx_path)
150
151
  return enabled
151
152
  except Exception: # noqa: BLE001 — degrade to the local parser, never crash
152
153
  pass
153
- return frontmatter(ctx_path).get("arbiter", "").lower() == "enabled"
154
+ try:
155
+ with open(ctx_path, encoding="utf-8-sig", errors="replace") as handle:
156
+ return _activation_enabled_text(handle.read())[0]
157
+ except OSError:
158
+ return False
154
159
 
155
160
 
156
161
  def arbiter_state(root, count_in_flight=None, read_board=None, frontmatter_enabled=None,