@arbiterforge/ca-pi 0.14.12 → 0.14.16
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/README.md +31 -12
- package/package.json +2 -1
- package/plugins/ca-pi/CHANGELOG.md +39 -0
- package/plugins/ca-pi/LICENSE +661 -0
- package/plugins/ca-pi/THIRD_PARTY_NOTICES.md +40 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/release.json +6 -6
- package/plugins/ca-pi/hooks/_artifactauthoritylib.py +486 -37
- package/plugins/ca-pi/hooks/_artifactlib.py +150 -3
- package/plugins/ca-pi/hooks/_host.py +1 -1
- package/plugins/ca-pi/hooks/_releaselib.py +345 -111
- package/plugins/ca-pi/hooks/artifact-authority-hook.py +57 -2
- package/plugins/ca-pi/hooks/artifact-authority.py +23 -1
- package/plugins/ca-pi/hooks/hostapi.py +1 -1
- package/plugins/ca-pi/hooks/releasehash.py +36 -23
- package/plugins/ca-pi/includes/artifacts.md +26 -6
- package/plugins/ca-pi/package.json +1 -1
- package/plugins/ca-pi/routines/release/SKILL.md +220 -70
- package/plugins/ca-pi/skills/ca-release/SKILL.md +6 -4
|
@@ -244,6 +244,145 @@ def _resolve_workflow_pair(root: str | Path, slug: str) -> dict[str, object]:
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
|
|
247
|
+
def _installed_workflow_preflight(installation: Path) -> dict[str, object]:
|
|
248
|
+
"""Inspect installed prerequisites, not host trust or live-event evidence.
|
|
249
|
+
|
|
250
|
+
This never imports an adapter, executes a hook, or writes consumer state.
|
|
251
|
+
Package integrity and the actual authority boundaries remain separate checks.
|
|
252
|
+
A successful engine capabilities call alone cannot admit a host workflow.
|
|
253
|
+
"""
|
|
254
|
+
result: dict[str, object] = {
|
|
255
|
+
"host": None, "adapter_version": None, "resources_available": False,
|
|
256
|
+
"evidence_kind": "installed-resource-preflight", "live_host_verified": False,
|
|
257
|
+
"missing": [],
|
|
258
|
+
}
|
|
259
|
+
missing: list[str] = []
|
|
260
|
+
if installation.name != "artifacts" or installation.parent.name != "helpers":
|
|
261
|
+
result["missing"] = ["installed host package layout"]
|
|
262
|
+
return result
|
|
263
|
+
plugin = installation.parent.parent
|
|
264
|
+
|
|
265
|
+
def resource(relative: str, *, document: bool = False):
|
|
266
|
+
path = plugin / relative
|
|
267
|
+
try:
|
|
268
|
+
_trusted_directory(installation, "HOST_WORKFLOW_UNAVAILABLE")
|
|
269
|
+
_trusted_directory(path.parent, "HOST_WORKFLOW_UNAVAILABLE")
|
|
270
|
+
descriptor = _open_pinned_regular(path)
|
|
271
|
+
try:
|
|
272
|
+
info = os.fstat(descriptor)
|
|
273
|
+
reparse = getattr(info, "st_file_attributes", 0) & getattr(
|
|
274
|
+
stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0)
|
|
275
|
+
if reparse or not stat.S_ISREG(info.st_mode) or info.st_size <= 0:
|
|
276
|
+
raise OSError("required installed resource is not a nonempty regular file")
|
|
277
|
+
return _decode(os.read(descriptor, MAX_RESPONSE + 1)) if document else True
|
|
278
|
+
finally:
|
|
279
|
+
os.close(descriptor)
|
|
280
|
+
except (OSError, ArtifactError):
|
|
281
|
+
missing.append(relative)
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
manifests = [("claude", "ca", ".claude-plugin/plugin.json"),
|
|
285
|
+
("codex", "ca-codex", ".codex-plugin/plugin.json")]
|
|
286
|
+
present = [row for row in manifests if (plugin / row[2]).exists()
|
|
287
|
+
or (plugin / row[2]).is_symlink()]
|
|
288
|
+
if not present:
|
|
289
|
+
present = [("pi", "ca-pi", "package.json")]
|
|
290
|
+
if len(present) != 1:
|
|
291
|
+
result["missing"] = ["one unambiguous installed host manifest"]
|
|
292
|
+
return result
|
|
293
|
+
host, name, manifest_path = present[0]
|
|
294
|
+
manifest = resource(manifest_path, document=True)
|
|
295
|
+
if (not isinstance(manifest, dict) or manifest.get("name") != name
|
|
296
|
+
or not isinstance(manifest.get("version"), str)
|
|
297
|
+
or re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?",
|
|
298
|
+
manifest["version"]) is None):
|
|
299
|
+
result["missing"] = ["matching installed host identity"]
|
|
300
|
+
return result
|
|
301
|
+
result.update(host=host, adapter_version=manifest["version"])
|
|
302
|
+
if host == "pi":
|
|
303
|
+
result["missing"] = ["Pi production prompt, verification and review authority"]
|
|
304
|
+
return result
|
|
305
|
+
|
|
306
|
+
for filename in ("_approvallib.py", "_prerequisitelib.py", "_sprintapprovallib.py",
|
|
307
|
+
"_reconciliationlib.py", "_artifactauthoritylib.py", "_gitexec.py",
|
|
308
|
+
"artifact-authority.py", "artifact-authority-hook.py", "prompt-submit.py"):
|
|
309
|
+
resource("hooks/" + filename)
|
|
310
|
+
if host == "claude":
|
|
311
|
+
resource("agents/authority-reviewer.md")
|
|
312
|
+
registry = resource("hooks/hooks.json", document=True)
|
|
313
|
+
hooks = registry.get("hooks") if isinstance(registry, dict) else None
|
|
314
|
+
if not isinstance(hooks, dict):
|
|
315
|
+
result["missing"] = sorted(set(missing + ["valid installed hook registry"]))
|
|
316
|
+
return result
|
|
317
|
+
|
|
318
|
+
token = "${CLAUDE_PLUGIN_ROOT}" if host == "claude" else "${PLUGIN_ROOT}"
|
|
319
|
+
command_field = "commandWindows" if host == "codex" and platform.system() == "Windows" else "command"
|
|
320
|
+
|
|
321
|
+
def registered(event: str, tool: str | None, script: str) -> bool:
|
|
322
|
+
groups = hooks.get(event)
|
|
323
|
+
if not isinstance(groups, list):
|
|
324
|
+
return False
|
|
325
|
+
commands = {f'{python} "{token}/hooks/{script}"' for python in ("python", "python3")}
|
|
326
|
+
for group in groups:
|
|
327
|
+
if not isinstance(group, dict):
|
|
328
|
+
continue
|
|
329
|
+
matcher = group.get("matcher", "")
|
|
330
|
+
if not isinstance(matcher, str):
|
|
331
|
+
continue
|
|
332
|
+
# Check the shipped literal alternatives, not a second regex DSL.
|
|
333
|
+
if (tool is None and matcher not in {"", "*"}) or (
|
|
334
|
+
tool is not None and tool not in matcher.split("|")):
|
|
335
|
+
continue
|
|
336
|
+
entries = group.get("hooks")
|
|
337
|
+
if not isinstance(entries, list):
|
|
338
|
+
continue
|
|
339
|
+
if any(isinstance(entry, dict) and entry.get("type") == "command"
|
|
340
|
+
and entry.get("async", False) is False
|
|
341
|
+
and isinstance(entry.get(command_field), str)
|
|
342
|
+
and entry[command_field] in commands for entry in entries):
|
|
343
|
+
return True
|
|
344
|
+
return False
|
|
345
|
+
|
|
346
|
+
required = [("UserPromptSubmit", None, "prompt-submit.py")]
|
|
347
|
+
tools = ("Bash", "Agent") if host == "claude" else (
|
|
348
|
+
"Bash", "shell_command", "exec_command", "unified_exec", "spawn_agent")
|
|
349
|
+
required += [(event, tool, "artifact-authority-hook.py")
|
|
350
|
+
for event in ("PreToolUse", "PostToolUse") for tool in tools]
|
|
351
|
+
required += [(event, None, "artifact-authority-hook.py")
|
|
352
|
+
for event in ("SubagentStart", "SubagentStop")]
|
|
353
|
+
if host == "claude":
|
|
354
|
+
required += [("PostToolUseFailure", "Bash", "artifact-authority-hook.py"),
|
|
355
|
+
("PreToolUse", "SendMessage", "artifact-authority-hook.py")]
|
|
356
|
+
missing.extend(f"{event}:{tool or '*'}:{script}"
|
|
357
|
+
for event, tool, script in required if not registered(event, tool, script))
|
|
358
|
+
result["missing"] = sorted(set(missing))
|
|
359
|
+
result["resources_available"] = not missing
|
|
360
|
+
return result
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _require_host_workflow(client: "ArtifactClient") -> None:
|
|
364
|
+
readiness = client.workflow_preflight()
|
|
365
|
+
if readiness["resources_available"] is not True:
|
|
366
|
+
raise ArtifactError(
|
|
367
|
+
"HOST_WORKFLOW_UNAVAILABLE",
|
|
368
|
+
("installed HTML workflow prerequisites are unavailable: "
|
|
369
|
+
+ ", ".join(readiness["missing"]))[:400]
|
|
370
|
+
+ "; repair this package or use a supported host; no fallback artifacts",
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# The native engine ships only through the release distribution channels, never
|
|
375
|
+
# through a main-branch checkout, so the repair is always "install from the
|
|
376
|
+
# channel". Named concretely so the user is not left to guess (ADR-0040).
|
|
377
|
+
PAYLOAD_REPAIR_HINT = (
|
|
378
|
+
"Claude Code: /plugin marketplace update codearbiter, then /plugin update ca@codearbiter; "
|
|
379
|
+
"Codex (registered without --ref): codex plugin remove ca-codex@codearbiter, "
|
|
380
|
+
"codex plugin marketplace remove codearbiter, "
|
|
381
|
+
"codex plugin marketplace add arbiterForge/codeArbiter --ref ca-codex-marketplace, "
|
|
382
|
+
"then codex plugin add ca-codex@codearbiter; Pi: pi install npm:@arbiterforge/ca-pi"
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
247
386
|
def _require_authoring_capability(client: "ArtifactClient") -> None:
|
|
248
387
|
"""Fail one default-HTML route with a bounded repair diagnostic."""
|
|
249
388
|
try:
|
|
@@ -252,7 +391,7 @@ def _require_authoring_capability(client: "ArtifactClient") -> None:
|
|
|
252
391
|
raise ArtifactError(
|
|
253
392
|
"CAPABILITY_MISSING",
|
|
254
393
|
f"repair or reinstall the pinned artifact payload ({exc.code}); "
|
|
255
|
-
"new HTML work cannot fall back to Markdown",
|
|
394
|
+
f"new HTML work cannot fall back to Markdown. Repair: {PAYLOAD_REPAIR_HINT}",
|
|
256
395
|
) from exc
|
|
257
396
|
if (
|
|
258
397
|
not isinstance(capabilities, dict)
|
|
@@ -262,7 +401,7 @@ def _require_authoring_capability(client: "ArtifactClient") -> None:
|
|
|
262
401
|
raise ArtifactError(
|
|
263
402
|
"CAPABILITY_MISSING",
|
|
264
403
|
"repair or reinstall the qualified default-on artifact payload; "
|
|
265
|
-
"new HTML work cannot fall back to Markdown",
|
|
404
|
+
f"new HTML work cannot fall back to Markdown. Repair: {PAYLOAD_REPAIR_HINT}",
|
|
266
405
|
)
|
|
267
406
|
|
|
268
407
|
|
|
@@ -305,10 +444,13 @@ def _select_authoring_route(
|
|
|
305
444
|
"an installation-pinned artifact client bound to this repository is required",
|
|
306
445
|
)
|
|
307
446
|
_require_authoring_capability(client)
|
|
447
|
+
if _resolve_workflow_pair(root, slug) != selected:
|
|
448
|
+
raise ArtifactError("STALE_ROUTE", "artifact namespace changed during the capability probe")
|
|
449
|
+
_require_host_workflow(client)
|
|
308
450
|
if _resolve_workflow_pair(root, slug) != selected:
|
|
309
451
|
raise ArtifactError(
|
|
310
452
|
"STALE_ROUTE",
|
|
311
|
-
"artifact namespace changed during the
|
|
453
|
+
"artifact namespace changed during the installed host preflight",
|
|
312
454
|
)
|
|
313
455
|
return {"workflow": workflow, "lane": lane, "mode": "artifact", **selected}
|
|
314
456
|
|
|
@@ -371,6 +513,7 @@ def _preflight_plan_authoring(
|
|
|
371
513
|
):
|
|
372
514
|
raise ArtifactError("AUTHORITY_MISMATCH", "spec normative digest is invalid")
|
|
373
515
|
|
|
516
|
+
_require_host_workflow(client)
|
|
374
517
|
identity = client.call("identity", {"artifact_id": spec_artifact_id})
|
|
375
518
|
expected_path = f".codearbiter/specs/{slug}.html"
|
|
376
519
|
if (
|
|
@@ -654,6 +797,10 @@ class ArtifactClient:
|
|
|
654
797
|
self.installation = _trusted_directory(installation, "CAPABILITY_MISSING")
|
|
655
798
|
self.timeout = timeout
|
|
656
799
|
|
|
800
|
+
def workflow_preflight(self) -> dict[str, object]:
|
|
801
|
+
"""Read fresh installed prerequisites; never attest live host authority."""
|
|
802
|
+
return _installed_workflow_preflight(self.installation)
|
|
803
|
+
|
|
657
804
|
def _open_binary(self) -> int:
|
|
658
805
|
system = {"Linux":"linux", "Darwin":"darwin", "Windows":"windows"}.get(platform.system())
|
|
659
806
|
arch = {"x86_64":"amd64", "amd64":"amd64", "aarch64":"arm64", "arm64":"arm64"}.get(platform.machine().lower())
|
|
@@ -11,7 +11,7 @@ import hostapi # noqa: E402
|
|
|
11
11
|
class PiHost(hostapi.Host):
|
|
12
12
|
name = "pi"
|
|
13
13
|
adapter_name = "@arbiterforge/ca-pi"
|
|
14
|
-
adapter_version = "0.14.
|
|
14
|
+
adapter_version = "0.14.16"
|
|
15
15
|
update_target = "ca-pi"
|
|
16
16
|
update_tag_prefix = "ca-pi-v"
|
|
17
17
|
update_command = "pi update npm:@arbiterforge/ca-pi"
|