@arbiterforge/ca-pi 0.10.13 → 0.14.7

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 (58) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -1
  3. package/plugins/ca-pi/CHANGELOG.md +167 -0
  4. package/plugins/ca-pi/SKILLS.md +1 -1
  5. package/plugins/ca-pi/SPRINT.md +40 -10
  6. package/plugins/ca-pi/agents/backend-author.md +1 -1
  7. package/plugins/ca-pi/agents/frontend-author.md +1 -1
  8. package/plugins/ca-pi/generated/command-catalog.json +1 -1
  9. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
  10. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
  11. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
  12. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
  13. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
  14. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
  15. package/plugins/ca-pi/helpers/artifacts/release.json +38 -0
  16. package/plugins/ca-pi/hooks/_approvallib.py +355 -0
  17. package/plugins/ca-pi/hooks/_artifactlib.py +855 -0
  18. package/plugins/ca-pi/hooks/_cleanuplib.py +1553 -0
  19. package/plugins/ca-pi/hooks/_hooklib.py +37 -13
  20. package/plugins/ca-pi/hooks/_host.py +1 -1
  21. package/plugins/ca-pi/hooks/_intentlib.py +31 -0
  22. package/plugins/ca-pi/hooks/_planfilelib.py +34 -1
  23. package/plugins/ca-pi/hooks/_prerequisitelib.py +971 -0
  24. package/plugins/ca-pi/hooks/_protectedstatelib.py +11 -1
  25. package/plugins/ca-pi/hooks/_readinjectlib.py +120 -0
  26. package/plugins/ca-pi/hooks/_releaselib.py +933 -85
  27. package/plugins/ca-pi/hooks/_standuplib.py +38 -10
  28. package/plugins/ca-pi/hooks/doctor.py +103 -0
  29. package/plugins/ca-pi/hooks/hostapi.py +1 -1
  30. package/plugins/ca-pi/hooks/pi-bridge.py +5 -1
  31. package/plugins/ca-pi/hooks/prompt-submit.py +47 -10
  32. package/plugins/ca-pi/hooks/releasehash.py +46 -32
  33. package/plugins/ca-pi/includes/artifacts.md +210 -0
  34. package/plugins/ca-pi/includes/author-tdd-workflow.md +2 -1
  35. package/plugins/ca-pi/includes/farm.md +27 -4
  36. package/plugins/ca-pi/includes/routing-table.md +2 -2
  37. package/plugins/ca-pi/includes/verification-boundary.md +27 -0
  38. package/plugins/ca-pi/package.json +18 -0
  39. package/plugins/ca-pi/routines/INDEX.md +4 -4
  40. package/plugins/ca-pi/routines/brainstorming/SKILL.md +23 -4
  41. package/plugins/ca-pi/routines/commit-gate/SKILL.md +30 -5
  42. package/plugins/ca-pi/routines/executing-plans/SKILL.md +52 -7
  43. package/plugins/ca-pi/routines/finishing-a-development-branch/SKILL.md +25 -3
  44. package/plugins/ca-pi/routines/refactor/SKILL.md +3 -2
  45. package/plugins/ca-pi/routines/release/SKILL.md +150 -44
  46. package/plugins/ca-pi/routines/subagent-driven-development/SKILL.md +53 -9
  47. package/plugins/ca-pi/routines/subagent-driven-development/references/farm-dispatch.md +20 -3
  48. package/plugins/ca-pi/routines/tdd/SKILL.md +27 -5
  49. package/plugins/ca-pi/routines/using-git-worktrees/SKILL.md +11 -2
  50. package/plugins/ca-pi/routines/writing-plans/SKILL.md +39 -10
  51. package/plugins/ca-pi/routines/writing-plans/references/farm-plan.md +34 -13
  52. package/plugins/ca-pi/skills/ca-chore/SKILL.md +7 -3
  53. package/plugins/ca-pi/skills/ca-commit/SKILL.md +3 -2
  54. package/plugins/ca-pi/skills/ca-doctor/SKILL.md +14 -0
  55. package/plugins/ca-pi/skills/ca-feature/SKILL.md +42 -8
  56. package/plugins/ca-pi/skills/ca-refactor/SKILL.md +4 -2
  57. package/plugins/ca-pi/skills/ca-release/SKILL.md +7 -6
  58. package/plugins/ca-pi/skills/ca-standup/SKILL.md +36 -8
@@ -381,6 +381,16 @@ def marker_name_for(rel_path):
381
381
  (non-empty) rel_path can ever produce, since a real segment always
382
382
  contributes a non-empty stem.
383
383
 
384
+ Every segment is case-folded (`.lower()`) before encoding (#624), matching
385
+ `_canon`/`lookup_policy`'s own case-insensitive registry comparison: a
386
+ marker name computed for a differently-cased Write/Edit target must equal
387
+ the one minted under the registry's canonical casing, or a legitimate
388
+ authoring write is wrongly blocked despite a fresh, correctly-minted
389
+ marker. This widens what two paths can collide into one marker (a
390
+ same-name directory differing only by case now shares a marker with its
391
+ canonical sibling) - the same accepted "widens admission, never narrows
392
+ protection" tradeoff `lookup_policy`'s own case fold already makes.
393
+
384
394
  Residual (accepted): the escaping above closes hyphen/segment-boundary
385
395
  ambiguity but is not a fully bijective encoding against adversarially
386
396
  crafted underscore runs (e.g. a directory literally named "a_" holding
@@ -388,7 +398,7 @@ def marker_name_for(rel_path):
388
398
  small, curated, human-authored set (spec B1/B2), not attacker-chosen
389
399
  directory names, so this is judged out of proportion to close fully
390
400
  here."""
391
- parts = [p for p in norm_path(rel_path).split("/") if p not in ("", ".", "..")]
401
+ parts = [p for p in norm_path(rel_path).lower().split("/") if p not in ("", ".", "..")]
392
402
  if not parts:
393
403
  return "-authoring"
394
404
  if parts[0] == ".codearbiter" and len(parts) > 1:
@@ -1084,3 +1084,123 @@ def compute_injection(root, session_id, rel, runner=None):
1084
1084
  return ""
1085
1085
  except Exception: # noqa: BLE001
1086
1086
  return ""
1087
+
1088
+ # Typed artifact pilot bridge. Keep legacy matching unchanged; never cache HTML
1089
+ # authority by mtime. Importing this module performs no subprocess or file I/O.
1090
+ _artifact_legacy_build_index = build_index
1091
+ _artifact_legacy_spec_pointers = spec_pointers
1092
+
1093
+
1094
+ def build_index(root):
1095
+ result = _artifact_legacy_build_index(root)
1096
+ if not isinstance(result, dict):
1097
+ return result
1098
+ result = dict(result)
1099
+ try:
1100
+ from _artifactlib import ArtifactClient, helper_installation, has_html
1101
+ if not has_html(root):
1102
+ return result
1103
+ # Preserve full engine identities in the advisory snapshot. Pointer
1104
+ # matching uses only spec/path/globs, while dedup below hashes the same
1105
+ # entries including revision/model/normative identity. Do not reread the
1106
+ # artifact after this validated index stream.
1107
+ extra = []
1108
+ client = ArtifactClient(root, helper_installation(__file__))
1109
+ for entry in client.index("spec"):
1110
+ authority = entry["authority"]
1111
+ if authority["authority_verified"] and entry.get("governs"):
1112
+ extra.append({
1113
+ "spec": entry["artifact_id"],
1114
+ "path": entry["path"],
1115
+ "globs": entry["governs"],
1116
+ "artifact_html": True,
1117
+ "revision": entry.get("revision"),
1118
+ "model_sha256": entry.get("model_sha256"),
1119
+ "normative_sha256": entry.get("normative_sha256"),
1120
+ })
1121
+ except Exception as error:
1122
+ # Advisory read injection remains fail-open. This warning is NOT an
1123
+ # approved-spec pointer and MUST NOT authorize a planning/execution gate.
1124
+ code = getattr(error, "code", "CAPABILITY_MISSING")
1125
+ code = str(code)[:80]
1126
+ capability_codes = {
1127
+ "CAPABILITY_MISSING", "INVALID_INSTALLATION", "PACKAGE_INTEGRITY",
1128
+ "UNSUPPORTED_PLATFORM", "UNSUPPORTED_VERSION", "UNVERIFIED_PLATFORM",
1129
+ "INVALID_RESPONSE", "RESPONSE_TOO_LARGE", "SUBPROCESS_FAILED", "TIMEOUT",
1130
+ }
1131
+ extra = [{
1132
+ "artifact_diagnostic": code,
1133
+ "artifact_diagnostic_kind": (
1134
+ "capability" if code in capability_codes else "invalid-artifact"
1135
+ ),
1136
+ "artifact_html": True,
1137
+ }]
1138
+ result["spec"] = [item for item in result.get("spec", [])
1139
+ if not isinstance(item, dict) or not item.get("artifact_html")] + extra
1140
+ return result
1141
+
1142
+
1143
+ def spec_pointers(rel, index):
1144
+ if not isinstance(index, list):
1145
+ return _artifact_legacy_spec_pointers(rel, index)
1146
+ warnings = [item for item in index if isinstance(item, dict) and item.get("artifact_diagnostic")]
1147
+ normal = [item for item in index if not isinstance(item, dict) or not item.get("artifact_diagnostic")]
1148
+ result = _artifact_legacy_spec_pointers(rel, normal)
1149
+ if warnings:
1150
+ warning = warnings[0]
1151
+ code = warning["artifact_diagnostic"]
1152
+ if warning.get("artifact_diagnostic_kind") == "capability":
1153
+ text = (
1154
+ "HTML artifact capability unavailable (" + code + "). Repair or reinstall "
1155
+ "the pinned artifact payload. Source inspection and unrelated legacy Markdown "
1156
+ "may continue; HTML planning/execution must stop until resolved."
1157
+ )
1158
+ else:
1159
+ text = (
1160
+ "HTML specification is invalid or unreadable (" + code + "). Inspect the "
1161
+ "engine diagnostic and repair-preview before any repair. Source inspection may "
1162
+ "continue; HTML planning/execution must stop until resolved."
1163
+ )
1164
+ result.append({"tier": "artifact-diagnostic", "text": text})
1165
+ return result
1166
+
1167
+ _artifact_legacy_compute_injection = compute_injection
1168
+
1169
+
1170
+ def compute_injection(root, session_id, rel, runner=None):
1171
+ # Bind the injected pointers and dedup epoch to ONE validated HTML snapshot.
1172
+ # A second artifact read here permits B(epoch) -> A(content) and records A
1173
+ # under B's marker, suppressing a later stable B read. HTML therefore builds
1174
+ # once before dedup; the same entries drive both context and epoch. This is
1175
+ # advisory identity only, never approval or execution authority.
1176
+ if not isinstance(rel, str) or rel.replace("\\", "/").split("/", 1)[0] == ".codearbiter":
1177
+ return _artifact_legacy_compute_injection(root, session_id, rel, runner)
1178
+ try:
1179
+ from _artifactlib import has_html
1180
+ if not has_html(root):
1181
+ return _artifact_legacy_compute_injection(root, session_id, rel, runner)
1182
+ index = build_index(root)
1183
+ if not isinstance(index, dict):
1184
+ return ""
1185
+ artifact_entries = [
1186
+ item for item in index.get("spec", [])
1187
+ if isinstance(item, dict) and item.get("artifact_html") is True
1188
+ ]
1189
+ epoch = hashlib.sha256(
1190
+ json.dumps(
1191
+ artifact_entries, sort_keys=True, separators=(",", ":")
1192
+ ).encode()
1193
+ ).hexdigest()
1194
+ epoch_session = str(session_id) + "|html:" + epoch
1195
+ if already_injected(root, epoch_session, rel):
1196
+ return ""
1197
+ pointers = governing_docs(rel, index, runner)
1198
+ if not pointers:
1199
+ return ""
1200
+ context = assemble_context(pointers, budget=150)
1201
+ if context:
1202
+ record_injection(root, epoch_session, rel)
1203
+ return context
1204
+ return ""
1205
+ except Exception: # noqa: BLE001 - advisory injection always fails open
1206
+ return ""