autoharness 1.4.2__py3-none-any.whl

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 (221) hide show
  1. autoharness/__init__.py +7 -0
  2. autoharness/cli.py +544 -0
  3. autoharness/data/.github/agents/auto-mergeinstall.agent.md +222 -0
  4. autoharness/data/.github/agents/auto-tune.agent.md +164 -0
  5. autoharness/data/.github/agents/orchestrator.agent.md +134 -0
  6. autoharness/data/.github/agents/ship.agent.md +196 -0
  7. autoharness/data/.github/agents/stage.agent.md +175 -0
  8. autoharness/data/.github/copilot-instructions.md +148 -0
  9. autoharness/data/.github/copilot-review-instructions.md +190 -0
  10. autoharness/data/.github/instructions/harness-architecture.instructions.md +306 -0
  11. autoharness/data/.github/prompts/install-harness.prompt.md +16 -0
  12. autoharness/data/.github/prompts/tune-harness.prompt.md +14 -0
  13. autoharness/data/.github/skills/install-harness/SKILL.md +1229 -0
  14. autoharness/data/.github/skills/tune-harness/SKILL.md +821 -0
  15. autoharness/data/.github/skills/verify-harness/SKILL.md +211 -0
  16. autoharness/data/.github/skills/workspace-discovery/SKILL.md +632 -0
  17. autoharness/data/AGENTS.md +58 -0
  18. autoharness/data/docs/backlog-integration.md +64 -0
  19. autoharness/data/docs/backlogit-compatibility-matrix.md +95 -0
  20. autoharness/data/docs/backlogit-graduation-checklist.md +105 -0
  21. autoharness/data/docs/backlogit-operating-model.md +262 -0
  22. autoharness/data/docs/capability-packs.md +596 -0
  23. autoharness/data/docs/closure/.gitkeep +0 -0
  24. autoharness/data/docs/compound/.gitkeep +0 -0
  25. autoharness/data/docs/compound/010-S-session-lifecycle-gates.md +58 -0
  26. autoharness/data/docs/compound/012-S-dynamic-policy-acceptance-path.md +78 -0
  27. autoharness/data/docs/compound/012-S-portability-scan-allow-list.md +70 -0
  28. autoharness/data/docs/compound/015-s-graphtor-docs-pack-registration.md +67 -0
  29. autoharness/data/docs/compound/016-s-community-template-registry.md +52 -0
  30. autoharness/data/docs/compound/2026-05-05-agent-tool-list-completeness.md +35 -0
  31. autoharness/data/docs/compound/2026-05-05-assertion-specificity-agent-file-names.md +41 -0
  32. autoharness/data/docs/compound/2026-05-05-foundation-assertion-anchoring.md +46 -0
  33. autoharness/data/docs/compound/2026-05-05-git-reset-hard-vs-checkout-hash.md +35 -0
  34. autoharness/data/docs/compound/2026-05-05-harness-config-round-trip-requirement.md +39 -0
  35. autoharness/data/docs/compound/2026-05-05-multi-phase-skill-scope-matrix.md +45 -0
  36. autoharness/data/docs/compound/2026-05-05-path-traversal-validation-parts.md +41 -0
  37. autoharness/data/docs/compound/2026-05-05-resolution-order-config-first-not-detection-first.md +40 -0
  38. autoharness/data/docs/compound/2026-05-05-runtime-surfaces-browser-tooling-detection.md +40 -0
  39. autoharness/data/docs/compound/2026-05-05-stride-evidence-anchor-pattern.md +43 -0
  40. autoharness/data/docs/compound/2026-05-06-github-review-comment-id-types.md +63 -0
  41. autoharness/data/docs/compound/2026-05-06-harness-manifest-artifacts-vs-entries-added.md +61 -0
  42. autoharness/data/docs/compound/2026-05-06-p010-agent-role-boundaries.md +38 -0
  43. autoharness/data/docs/compound/2026-05-06-p011-branch-before-mutation-design.md +42 -0
  44. autoharness/data/docs/compound/2026-05-06-p012-tool-availability-gate-and-dispatch.md +55 -0
  45. autoharness/data/docs/compound/2026-05-06-template-variable-escaping-in-docs.md +51 -0
  46. autoharness/data/docs/compound/2026-05-07-backlogit-shipment-status-constraints.md +47 -0
  47. autoharness/data/docs/compound/2026-05-07-nested-code-fence-rendering.md +56 -0
  48. autoharness/data/docs/compound/p013-orchestrator-model-routing.md +116 -0
  49. autoharness/data/docs/credits.md +186 -0
  50. autoharness/data/docs/decisions/.gitkeep +0 -0
  51. autoharness/data/docs/design-docs/.gitkeep +0 -0
  52. autoharness/data/docs/design-docs/context-window-compaction-strategy.md +14 -0
  53. autoharness/data/docs/environment-setup.md +157 -0
  54. autoharness/data/docs/exec-plans/2026-04-23-shipment-integrity-gates-plan.md +270 -0
  55. autoharness/data/docs/exec-plans/2026-04-24-fix-ci-v2-overhaul-plan.md +136 -0
  56. autoharness/data/docs/exec-plans/2026-04-24-policy-standards-enforcement-plan.md +127 -0
  57. autoharness/data/docs/exec-plans/2026-04-24-self-install-support-plan.md +184 -0
  58. autoharness/data/docs/exec-plans/2026-05-05-browser-experiment-skills-plan.md +106 -0
  59. autoharness/data/docs/exec-plans/2026-05-05-harness-doctor-skill-plan.md +64 -0
  60. autoharness/data/docs/exec-plans/2026-05-05-security-harness-surface-plan.md +256 -0
  61. autoharness/data/docs/exec-plans/2026-05-09-context-window-compaction-spike.md +168 -0
  62. autoharness/data/docs/exec-plans/2026-05-09-role-enforcement-deliberation.md +222 -0
  63. autoharness/data/docs/getting-started.md +474 -0
  64. autoharness/data/docs/memory/.gitkeep +0 -0
  65. autoharness/data/docs/memory/2026-04-26-ship-005-s-execution.md +24 -0
  66. autoharness/data/docs/memory/2026-04-26-stage-autotune-follow-up-hardening.md +19 -0
  67. autoharness/data/docs/memory/2026-05-05-ship-006-s-security-harness-surface.md +49 -0
  68. autoharness/data/docs/memory/2026-05-05-ship-007-s-closure.md +65 -0
  69. autoharness/data/docs/memory/2026-05-05-stage-007-s-restaging.md +26 -0
  70. autoharness/data/docs/memory/2026-05-05-stage-atv-security-integration.md +38 -0
  71. autoharness/data/docs/memory/2026-05-06-ship-008-s-harness-doctor.md +76 -0
  72. autoharness/data/docs/memory/2026-05-06-ship-009-s-agent-session-discipline.md +54 -0
  73. autoharness/data/docs/memory/ship-010-S.md +44 -0
  74. autoharness/data/docs/memory/ship-011-S.md +75 -0
  75. autoharness/data/docs/memory/ship-012-S.md +59 -0
  76. autoharness/data/docs/plans/.gitkeep +0 -0
  77. autoharness/data/docs/primitives.md +375 -0
  78. autoharness/data/docs/product-specs/.gitkeep +0 -0
  79. autoharness/data/docs/product-specs/orchestrator-model-routing-spec.md +129 -0
  80. autoharness/data/docs/reference-library.md +219 -0
  81. autoharness/data/docs/research/2026-04-08-backlogit-harness-evolution-analysis.md +572 -0
  82. autoharness/data/docs/research/2026-04-10-atv-starterkit-integration-analysis.md +794 -0
  83. autoharness/data/docs/spikes/011.001-coding-discipline-instructions.md +133 -0
  84. autoharness/data/docs/spikes/011.002-review-persona-expansion.md +145 -0
  85. autoharness/data/docs/spikes/011.003-sdk-guardrail-patterns.md +132 -0
  86. autoharness/data/docs/spikes/011.004-portability-audit-pattern.md +174 -0
  87. autoharness/data/docs/tuning-guide.md +218 -0
  88. autoharness/data/schemas/backlog-tool-registry.schema.json +203 -0
  89. autoharness/data/schemas/community-template-registry.schema.json +104 -0
  90. autoharness/data/schemas/harness-config/0.9.0.schema.json +70 -0
  91. autoharness/data/schemas/harness-config/1.0.0.schema.json +214 -0
  92. autoharness/data/schemas/harness-config.schema.json +357 -0
  93. autoharness/data/schemas/harness-manifest/0.9.0.schema.json +103 -0
  94. autoharness/data/schemas/harness-manifest/1.0.0.schema.json +203 -0
  95. autoharness/data/schemas/harness-manifest.schema.json +203 -0
  96. autoharness/data/schemas/workspace-profile/0.9.0.schema.json +95 -0
  97. autoharness/data/schemas/workspace-profile/1.0.0.schema.json +436 -0
  98. autoharness/data/schemas/workspace-profile.schema.json +459 -0
  99. autoharness/data/templates/agents/adversarial-review.agent.md.tmpl +180 -0
  100. autoharness/data/templates/agents/language-engineer.agent.md.tmpl +67 -0
  101. autoharness/data/templates/agents/orchestrator.agent.md.tmpl +339 -0
  102. autoharness/data/templates/agents/prompt-builder.agent.md.tmpl +67 -0
  103. autoharness/data/templates/agents/research/learnings-researcher.agent.md.tmpl +79 -0
  104. autoharness/data/templates/agents/review/agent-native-parity-reviewer.agent.md.tmpl +69 -0
  105. autoharness/data/templates/agents/review/architecture-strategist.agent.md.tmpl +65 -0
  106. autoharness/data/templates/agents/review/concurrency-reviewer.agent.md.tmpl +66 -0
  107. autoharness/data/templates/agents/review/constitution-reviewer.agent.md.tmpl +66 -0
  108. autoharness/data/templates/agents/review/scope-boundary-auditor.agent.md.tmpl +66 -0
  109. autoharness/data/templates/agents/review/security-lens-reviewer.agent.md.tmpl +110 -0
  110. autoharness/data/templates/agents/review/security-reviewer.agent.md.tmpl +83 -0
  111. autoharness/data/templates/agents/review/technology-reviewer.agent.md.tmpl +68 -0
  112. autoharness/data/templates/agents/security-sentinel.agent.md.tmpl +159 -0
  113. autoharness/data/templates/agents/ship.agent.md.tmpl +726 -0
  114. autoharness/data/templates/agents/stage.agent.md.tmpl +758 -0
  115. autoharness/data/templates/backlog/config.yml.tmpl +58 -0
  116. autoharness/data/templates/backlog/registries/backlog-md.registry.yaml +119 -0
  117. autoharness/data/templates/backlog/registries/backlogit.registry.yaml +436 -0
  118. autoharness/data/templates/backlog/stash.md.tmpl +17 -0
  119. autoharness/data/templates/community/README.md +81 -0
  120. autoharness/data/templates/community/agents/adr-generator.agent.md.tmpl +202 -0
  121. autoharness/data/templates/community/instructions/agent-safety.instructions.md.tmpl +94 -0
  122. autoharness/data/templates/community/instructions/code-review-generic.instructions.md.tmpl +173 -0
  123. autoharness/data/templates/community/registry.yaml +245 -0
  124. autoharness/data/templates/community/skills/acquire-codebase-knowledge/SKILL.md.tmpl +111 -0
  125. autoharness/data/templates/community/skills/brainstorming/SKILL.md.tmpl +61 -0
  126. autoharness/data/templates/community/skills/changelog-generator/SKILL.md.tmpl +96 -0
  127. autoharness/data/templates/community/skills/debugging/SKILL.md.tmpl +160 -0
  128. autoharness/data/templates/community/skills/document-review/SKILL.md.tmpl +134 -0
  129. autoharness/data/templates/community/skills/mcp-builder/SKILL.md.tmpl +212 -0
  130. autoharness/data/templates/community/skills/meta-prompting/SKILL.md.tmpl +80 -0
  131. autoharness/data/templates/community/skills/receiving-code-review/SKILL.md.tmpl +145 -0
  132. autoharness/data/templates/community/skills/simplifying-code/SKILL.md.tmpl +91 -0
  133. autoharness/data/templates/community/skills/skill-creator/SKILL.md.tmpl +163 -0
  134. autoharness/data/templates/community/skills/verification-before-completion/SKILL.md.tmpl +169 -0
  135. autoharness/data/templates/community/skills/writing-plans/SKILL.md.tmpl +87 -0
  136. autoharness/data/templates/community/skills/writing-tests/SKILL.md.tmpl +199 -0
  137. autoharness/data/templates/foundation/AGENTS.md.tmpl +381 -0
  138. autoharness/data/templates/foundation/constitution.instructions.md.tmpl +306 -0
  139. autoharness/data/templates/foundation/copilot-instructions.md.tmpl +214 -0
  140. autoharness/data/templates/harness-config.yaml.tmpl +106 -0
  141. autoharness/data/templates/instructions/adversarial-review.instructions.md.tmpl +64 -0
  142. autoharness/data/templates/instructions/agent-engram.instructions.md.tmpl +109 -0
  143. autoharness/data/templates/instructions/agent-intercom.instructions.md.tmpl +84 -0
  144. autoharness/data/templates/instructions/architecture-doc.instructions.md.tmpl +96 -0
  145. autoharness/data/templates/instructions/backlog-integration.instructions.md.tmpl +114 -0
  146. autoharness/data/templates/instructions/backlogit-sql-schema.instructions.md.tmpl +342 -0
  147. autoharness/data/templates/instructions/backlogit-yaml-header-tooling.instructions.md.tmpl +131 -0
  148. autoharness/data/templates/instructions/backlogit.instructions.md.tmpl +139 -0
  149. autoharness/data/templates/instructions/browser-verification.instructions.md.tmpl +79 -0
  150. autoharness/data/templates/instructions/ci-security.instructions.md.tmpl +95 -0
  151. autoharness/data/templates/instructions/circuit-breaker.instructions.md.tmpl +148 -0
  152. autoharness/data/templates/instructions/commit-message.instructions.md.tmpl +63 -0
  153. autoharness/data/templates/instructions/concurrency.instructions.md.tmpl +79 -0
  154. autoharness/data/templates/instructions/context-efficiency.instructions.md.tmpl +95 -0
  155. autoharness/data/templates/instructions/continuous-learning.instructions.md.tmpl +60 -0
  156. autoharness/data/templates/instructions/git-merge.instructions.md.tmpl +74 -0
  157. autoharness/data/templates/instructions/github-pr-automation.instructions.md.tmpl +517 -0
  158. autoharness/data/templates/instructions/graphtor-docs.instructions.md.tmpl +88 -0
  159. autoharness/data/templates/instructions/markdown.instructions.md.tmpl +64 -0
  160. autoharness/data/templates/instructions/mcp-server.instructions.md.tmpl +118 -0
  161. autoharness/data/templates/instructions/prompt-builder.instructions.md.tmpl +57 -0
  162. autoharness/data/templates/instructions/pull-request.instructions.md.tmpl +42 -0
  163. autoharness/data/templates/instructions/release-observability.instructions.md.tmpl +72 -0
  164. autoharness/data/templates/instructions/role-enforcement.instructions.md.tmpl +68 -0
  165. autoharness/data/templates/instructions/strict-safety.instructions.md.tmpl +73 -0
  166. autoharness/data/templates/instructions/technology-go.instructions.md.tmpl +126 -0
  167. autoharness/data/templates/instructions/technology-python.instructions.md.tmpl +97 -0
  168. autoharness/data/templates/instructions/technology-rust.instructions.md.tmpl +98 -0
  169. autoharness/data/templates/instructions/technology-typescript.instructions.md.tmpl +96 -0
  170. autoharness/data/templates/instructions/technology.instructions.md.tmpl +52 -0
  171. autoharness/data/templates/instructions/workflows.instructions.md.tmpl +90 -0
  172. autoharness/data/templates/instructions/writing-style.instructions.md.tmpl +61 -0
  173. autoharness/data/templates/policies/policy-proposal.md.tmpl +49 -0
  174. autoharness/data/templates/policies/workflow-policies.md.tmpl +352 -0
  175. autoharness/data/templates/prompts/ping-loop.prompt.md.tmpl +31 -0
  176. autoharness/data/templates/prompts/stage-grouping-analysis.prompt.md.tmpl +30 -0
  177. autoharness/data/templates/scripts/.markdownlint.json.tmpl +6 -0
  178. autoharness/data/templates/scripts/pre-commit-markdownlint.ps1.tmpl +39 -0
  179. autoharness/data/templates/scripts/pre-commit-markdownlint.sh.tmpl +38 -0
  180. autoharness/data/templates/scripts/start.ps1.tmpl +149 -0
  181. autoharness/data/templates/scripts/start.sh.tmpl +57 -0
  182. autoharness/data/templates/skills/browser-automation/SKILL.md.tmpl +146 -0
  183. autoharness/data/templates/skills/build-feature/SKILL.md.tmpl +153 -0
  184. autoharness/data/templates/skills/compact-context/SKILL.md.tmpl +128 -0
  185. autoharness/data/templates/skills/compound/SKILL.md.tmpl +100 -0
  186. autoharness/data/templates/skills/compound-refresh/SKILL.md.tmpl +102 -0
  187. autoharness/data/templates/skills/deliberate/SKILL.md.tmpl +295 -0
  188. autoharness/data/templates/skills/evolve/SKILL.md.tmpl +54 -0
  189. autoharness/data/templates/skills/file-lock/SKILL.md.tmpl +79 -0
  190. autoharness/data/templates/skills/file-lock/scripts/acquire_lock.ps1 +72 -0
  191. autoharness/data/templates/skills/file-lock/scripts/acquire_lock.sh +48 -0
  192. autoharness/data/templates/skills/file-lock/scripts/release_lock.ps1 +48 -0
  193. autoharness/data/templates/skills/file-lock/scripts/release_lock.sh +41 -0
  194. autoharness/data/templates/skills/fix-ci/SKILL.md.tmpl +386 -0
  195. autoharness/data/templates/skills/harness-architect/SKILL.md.tmpl +162 -0
  196. autoharness/data/templates/skills/harness-doctor/SKILL.md.tmpl +233 -0
  197. autoharness/data/templates/skills/harvest/SKILL.md.tmpl +150 -0
  198. autoharness/data/templates/skills/impl-plan/SKILL.md.tmpl +135 -0
  199. autoharness/data/templates/skills/iterative-experiment/SKILL.md.tmpl +136 -0
  200. autoharness/data/templates/skills/learn/SKILL.md.tmpl +54 -0
  201. autoharness/data/templates/skills/observe/SKILL.md.tmpl +55 -0
  202. autoharness/data/templates/skills/operational-closure/SKILL.md.tmpl +113 -0
  203. autoharness/data/templates/skills/plan-harden/SKILL.md.tmpl +107 -0
  204. autoharness/data/templates/skills/plan-review/SKILL.md.tmpl +155 -0
  205. autoharness/data/templates/skills/pr-lifecycle/SKILL.md.tmpl +190 -0
  206. autoharness/data/templates/skills/review/SKILL.md.tmpl +190 -0
  207. autoharness/data/templates/skills/runtime-verification/SKILL.md.tmpl +161 -0
  208. autoharness/data/templates/skills/safety-modes/SKILL.md.tmpl +161 -0
  209. autoharness/data/templates/skills/security-audit/SKILL.md.tmpl +191 -0
  210. autoharness/data/templates/skills/shipment-reconcile/SKILL.md.tmpl +160 -0
  211. autoharness/data/templates/skills/skill-search/SKILL.md.tmpl +100 -0
  212. autoharness/data/templates/skills/skill-search/scripts/search.ps1 +85 -0
  213. autoharness/data/templates/skills/skill-search/scripts/search.sh +78 -0
  214. autoharness/data/templates/skills/spike/SKILL.md.tmpl +361 -0
  215. autoharness/schema_contracts.py +460 -0
  216. autoharness/verify_workspace.py +2137 -0
  217. autoharness-1.4.2.dist-info/METADATA +158 -0
  218. autoharness-1.4.2.dist-info/RECORD +221 -0
  219. autoharness-1.4.2.dist-info/WHEEL +4 -0
  220. autoharness-1.4.2.dist-info/entry_points.txt +2 -0
  221. autoharness-1.4.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,7 @@
1
+ """autoharness — globally-installed agent harness framework."""
2
+
3
+ try:
4
+ from importlib.metadata import version
5
+ __version__ = version("autoharness")
6
+ except Exception:
7
+ __version__ = "1.4.2" # fallback for editable / pre-install contexts
autoharness/cli.py ADDED
@@ -0,0 +1,544 @@
1
+ """Thin CLI for autoharness — resolves installation paths for AI coding agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import platform
8
+ import shutil
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from autoharness.verify_workspace import verify_workspace
13
+
14
+ # The data directory is bundled inside the package at build time.
15
+ # In a dev/editable install, fall back to the repo root.
16
+ _PACKAGE_DIR = Path(__file__).resolve().parent
17
+ _DATA_DIR = _PACKAGE_DIR / "data"
18
+
19
+ if not _DATA_DIR.exists():
20
+ # Editable / dev install — repo root is two levels up from src/autoharness/
21
+ _DATA_DIR = _PACKAGE_DIR.parent.parent
22
+
23
+
24
+ def _home() -> Path:
25
+ """Return the autoharness home directory containing templates, schemas, etc."""
26
+ return _DATA_DIR
27
+
28
+
29
+ def _version() -> str:
30
+ from autoharness import __version__
31
+ return __version__
32
+
33
+
34
+ USAGE = """\
35
+ autoharness — agent harness framework
36
+
37
+ Usage:
38
+ autoharness home Print the autoharness installation path
39
+ autoharness version Print the installed version
40
+ autoharness verify-workspace Deterministically verify an installed workspace harness
41
+ autoharness setup-vscode Write agent discovery entries to VS Code user settings
42
+ autoharness setup-copilot-cli Copy agents/skills into Copilot CLI (deprecated — use plugin)
43
+ autoharness setup-claude Copy agents and skills into the Claude Code global config dir
44
+ autoharness setup-codex Copy skills into the Codex global config dir
45
+ autoharness help Show this message
46
+
47
+ Install (Copilot CLI plugin — recommended, no Python needed):
48
+ copilot plugin marketplace add softwaresalt/autoharness
49
+ copilot plugin install autoharness@autoharness
50
+
51
+ Install (Python CLI — stable releases on PyPI):
52
+ uv tool install autoharness
53
+
54
+ Install (Python CLI — unreleased snapshots from GitHub):
55
+ uv tool install git+https://github.com/softwaresalt/autoharness.git
56
+
57
+ Update:
58
+ copilot plugin update autoharness # plugin
59
+ uv tool upgrade autoharness # Python CLI from PyPI
60
+
61
+ The AI coding assistant is the runtime. This CLI primarily helps agents
62
+ resolve the autoharness home path via `autoharness home`, and also
63
+ provides user-facing setup and verification commands.
64
+ """
65
+
66
+
67
+ def _parse_verify_workspace_args(args: list[str]) -> tuple[Path, Path, Path | None, bool]:
68
+ """Parse arguments for the verify-workspace command."""
69
+ workspace_path: Path | None = None
70
+ autoharness_home: Path = _home()
71
+ staging_dir: Path | None = None
72
+ emit_json = False
73
+
74
+ index = 0
75
+ while index < len(args):
76
+ arg = args[index]
77
+ if arg in ("--workspace", "-w"):
78
+ index += 1
79
+ if index >= len(args):
80
+ raise ValueError("Missing value for --workspace")
81
+ workspace_path = Path(args[index])
82
+ elif arg == "--autoharness-home":
83
+ index += 1
84
+ if index >= len(args):
85
+ raise ValueError("Missing value for --autoharness-home")
86
+ autoharness_home = Path(args[index])
87
+ elif arg == "--staging-dir":
88
+ index += 1
89
+ if index >= len(args):
90
+ raise ValueError("Missing value for --staging-dir")
91
+ staging_dir = Path(args[index])
92
+ elif arg == "--json":
93
+ emit_json = True
94
+ else:
95
+ raise ValueError(f"Unknown verify-workspace argument: {arg}")
96
+ index += 1
97
+
98
+ if workspace_path is None:
99
+ raise ValueError("verify-workspace requires --workspace <path>")
100
+
101
+ return workspace_path, autoharness_home, staging_dir, emit_json
102
+
103
+
104
+ def _report_has_failures(report: dict) -> bool:
105
+ """Return True when the verification report contains failing conditions."""
106
+ if report.get("strict_schema_blockers"):
107
+ return True
108
+ if report.get("blockers"):
109
+ return True
110
+ if report.get("unresolved"):
111
+ return True
112
+ targeted_checks = report.get("targeted_checks", {})
113
+ return any(not check.get("ok", False) for check in targeted_checks.values())
114
+
115
+
116
+ def _verify_workspace_command(args: list[str]) -> None:
117
+ """Run deterministic workspace verification and emit a report."""
118
+ try:
119
+ workspace_path, autoharness_home, staging_dir, emit_json = _parse_verify_workspace_args(args)
120
+ except ValueError as exc:
121
+ print(str(exc), file=sys.stderr)
122
+ print(USAGE, file=sys.stderr)
123
+ sys.exit(2)
124
+
125
+ report = verify_workspace(
126
+ workspace_path=workspace_path,
127
+ autoharness_home=autoharness_home,
128
+ staging_dir=staging_dir,
129
+ )
130
+
131
+ if emit_json:
132
+ print(json.dumps(report, indent=2, ensure_ascii=False))
133
+ else:
134
+ print(f"Workspace: {report['workspace_path']}")
135
+ print(f"Staging dir: {report['staging_dir']}")
136
+ print(f"Markdown report: {report['report_paths']['markdown']}")
137
+ print(f"JSON report: {report['report_paths']['json']}")
138
+ print()
139
+ print(f"Strict schema blockers: {len(report['strict_schema_blockers'])}")
140
+ print(f"Blockers: {len(report['blockers'])}")
141
+ warning_count = len(report["warnings"])
142
+ warning_instances = int(report.get("warning_instances", warning_count))
143
+ if warning_instances > warning_count:
144
+ print(f"Warnings: {warning_count} grouped summaries ({warning_instances} findings)")
145
+ else:
146
+ print(f"Warnings: {warning_count}")
147
+ print(f"Migration proposals: {len(report['migration_proposals'])}")
148
+ print(f"Unresolved placeholders: {len(report['unresolved'])}")
149
+ print(f"Rendered artifacts: {len(report['rendered'])}")
150
+ print(f"Skipped artifacts: {len(report['skipped'])}")
151
+
152
+ if _report_has_failures(report):
153
+ sys.exit(1)
154
+
155
+
156
+ def _vscode_user_settings_path() -> Path | None:
157
+ """Return the platform-appropriate VS Code user settings path (no tilde)."""
158
+ system = platform.system()
159
+ if system == "Windows":
160
+ appdata = os.environ.get("APPDATA")
161
+ if not appdata:
162
+ return None
163
+ return Path(appdata) / "Code" / "User" / "settings.json"
164
+ elif system == "Darwin":
165
+ return Path.home() / "Library" / "Application Support" / "Code" / "User" / "settings.json"
166
+ else:
167
+ xdg = os.environ.get("XDG_CONFIG_HOME")
168
+ base = Path(xdg) if xdg else Path.home() / ".config"
169
+ return base / "Code" / "User" / "settings.json"
170
+
171
+
172
+ def _strip_jsonc(text: str) -> str:
173
+ """Strip JSONC comments and trailing commas without touching quoted strings.
174
+
175
+ Uses a character-level state machine that tracks string boundaries so that
176
+ comment-like sequences inside string values are never removed.
177
+ """
178
+ # Phase 1: strip comments
179
+ result: list[str] = []
180
+ i = 0
181
+ in_string = False
182
+ escape = False
183
+ in_line_comment = False
184
+ in_block_comment = False
185
+
186
+ while i < len(text):
187
+ ch = text[i]
188
+ nxt = text[i + 1] if i + 1 < len(text) else ""
189
+
190
+ if in_line_comment:
191
+ if ch == "\n":
192
+ in_line_comment = False
193
+ result.append(ch)
194
+ i += 1
195
+ continue
196
+
197
+ if in_block_comment:
198
+ if ch == "*" and nxt == "/":
199
+ in_block_comment = False
200
+ i += 2
201
+ else:
202
+ i += 1
203
+ continue
204
+
205
+ if in_string:
206
+ result.append(ch)
207
+ if escape:
208
+ escape = False
209
+ elif ch == "\\":
210
+ escape = True
211
+ elif ch == '"':
212
+ in_string = False
213
+ i += 1
214
+ continue
215
+
216
+ if ch == '"':
217
+ in_string = True
218
+ result.append(ch)
219
+ i += 1
220
+ continue
221
+
222
+ if ch == "/" and nxt == "/":
223
+ in_line_comment = True
224
+ i += 2
225
+ continue
226
+
227
+ if ch == "/" and nxt == "*":
228
+ in_block_comment = True
229
+ i += 2
230
+ continue
231
+
232
+ result.append(ch)
233
+ i += 1
234
+
235
+ if in_string:
236
+ raise ValueError("Invalid JSONC: unterminated string literal in VS Code settings.")
237
+ if in_block_comment:
238
+ raise ValueError("Invalid JSONC: unterminated block comment in VS Code settings.")
239
+
240
+ # Phase 2: strip trailing commas before } or ]
241
+ stripped = "".join(result)
242
+ cleaned: list[str] = []
243
+ in_string = False
244
+ escape = False
245
+ i = 0
246
+
247
+ while i < len(stripped):
248
+ ch = stripped[i]
249
+
250
+ if in_string:
251
+ cleaned.append(ch)
252
+ if escape:
253
+ escape = False
254
+ elif ch == "\\":
255
+ escape = True
256
+ elif ch == '"':
257
+ in_string = False
258
+ i += 1
259
+ continue
260
+
261
+ if ch == '"':
262
+ in_string = True
263
+ cleaned.append(ch)
264
+ i += 1
265
+ continue
266
+
267
+ if ch == ",":
268
+ j = i + 1
269
+ while j < len(stripped) and stripped[j] in " \t\r\n":
270
+ j += 1
271
+ if j < len(stripped) and stripped[j] in "}]":
272
+ i += 1
273
+ continue
274
+
275
+ cleaned.append(ch)
276
+ i += 1
277
+
278
+ if in_string:
279
+ raise ValueError("Invalid JSONC: unterminated string literal in VS Code settings.")
280
+
281
+ return "".join(cleaned)
282
+
283
+
284
+ def _setup_vscode() -> None:
285
+ """Write autoharness agent discovery entries into VS Code user settings."""
286
+ home = _home()
287
+ settings_path = _vscode_user_settings_path()
288
+
289
+ if settings_path is None:
290
+ print("Error: could not determine VS Code user settings path for this OS.", file=sys.stderr)
291
+ sys.exit(1)
292
+
293
+ # Build path keys using fully-resolved absolute paths — no tilde, no variables.
294
+ # Use POSIX (forward-slash) paths so the same key works on all platforms and
295
+ # avoids duplicate entries if the user already has forward-slash keys.
296
+ agents_path = home / ".github" / "agents"
297
+ skills_path = home / ".github" / "skills"
298
+ prompts_path = home / ".github" / "prompts"
299
+
300
+ entries = [
301
+ ("chat.agentFilesLocations", agents_path.as_posix()),
302
+ ("chat.agentSkillsLocations", skills_path.as_posix()),
303
+ ("chat.promptFilesLocations", prompts_path.as_posix()),
304
+ ]
305
+
306
+ # Read existing settings, tolerating JSONC comments.
307
+ if settings_path.exists():
308
+ raw = settings_path.read_text(encoding="utf-8")
309
+ try:
310
+ settings: dict = json.loads(raw)
311
+ except json.JSONDecodeError:
312
+ settings = json.loads(_strip_jsonc(raw))
313
+ else:
314
+ settings = {}
315
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
316
+
317
+ added: list[str] = []
318
+ skipped: list[str] = []
319
+
320
+ for setting_key, entry_key in entries:
321
+ existing = settings.get(setting_key)
322
+ if existing is None:
323
+ settings[setting_key] = {}
324
+ elif not isinstance(existing, dict):
325
+ print(
326
+ f"Error: '{setting_key}' in {settings_path} is not an object "
327
+ f"(found {type(existing).__name__}). "
328
+ f"Fix or remove that key manually, then re-run this command.",
329
+ file=sys.stderr,
330
+ )
331
+ sys.exit(1)
332
+ bucket: dict = settings[setting_key]
333
+ if entry_key in bucket:
334
+ skipped.append(f" {setting_key} (already present)")
335
+ else:
336
+ bucket[entry_key] = True
337
+ added.append(f" {setting_key}")
338
+
339
+ settings_path.write_text(
340
+ json.dumps(settings, indent=2, ensure_ascii=False) + "\n",
341
+ encoding="utf-8",
342
+ )
343
+
344
+ print(f"VS Code user settings: {settings_path}")
345
+ if added:
346
+ print("Added:")
347
+ for line in added:
348
+ print(line)
349
+ if skipped:
350
+ print("Already present (skipped):")
351
+ for line in skipped:
352
+ print(line)
353
+ if not added:
354
+ print("No changes needed — all entries were already present.")
355
+ else:
356
+ print("\nReload your VS Code window (Ctrl+Shift+P → 'Reload Window') for the")
357
+ print("Auto-MergeInstall agent to appear in the agents dropdown.")
358
+
359
+
360
+ def _copilot_cli_config_dir() -> Path:
361
+ """Return the Copilot CLI global config directory.
362
+
363
+ Mirrors the resolution order in copilot.exe:
364
+ 1. COPILOT_HOME environment variable
365
+ 2. ~/.copilot/ (default)
366
+ """
367
+ env = os.environ.get("COPILOT_HOME")
368
+ if env:
369
+ return Path(env)
370
+ return Path.home() / ".copilot"
371
+
372
+
373
+ def _setup_copilot_cli() -> None:
374
+ """Copy autoharness agents and skills into the Copilot CLI global config dir.
375
+
376
+ Copilot CLI discovers agents from {config_dir}/agents/ and skills from
377
+ {config_dir}/skills/ at session start. This command copies the autoharness
378
+ .github/agents/ and .github/skills/ trees into those directories so the
379
+ Auto-MergeInstall and Auto-Tune agents are available in every session.
380
+
381
+ Re-run this command after upgrading autoharness to pick up new agents or
382
+ updated skill files.
383
+
384
+ DEPRECATED: Use the marketplace install flow instead.
385
+ """
386
+ print("NOTE: setup-copilot-cli is deprecated.")
387
+ print(" Prefer: copilot plugin marketplace add softwaresalt/autoharness")
388
+ print(" copilot plugin install autoharness@autoharness")
389
+ print(" The plugin provides the same agents and skills with built-in")
390
+ print(" versioning and no Python dependency.")
391
+ print()
392
+
393
+ home = _home()
394
+ config_dir = _copilot_cli_config_dir()
395
+ src_agents = home / ".github" / "agents"
396
+ src_skills = home / ".github" / "skills"
397
+ dst_agents = config_dir / "agents"
398
+ dst_skills = config_dir / "skills"
399
+
400
+ print(f"Copilot CLI config dir: {config_dir}")
401
+ print()
402
+
403
+ a, u = _copy_tree(src_agents, dst_agents, "*.md")
404
+ _report_copy("Agents", a, u)
405
+
406
+ a, u = _copy_tree(src_skills, dst_skills, "SKILL.md")
407
+ _report_copy("Skills", a, u)
408
+
409
+ print("Done. Start a new Copilot CLI session to pick up the changes.")
410
+ print("Run this command again after upgrading autoharness.")
411
+
412
+
413
+
414
+ def _copy_tree(src_dir: Path, dst_dir: Path, glob: str) -> tuple[list[str], list[str]]:
415
+ """Copy files matching glob from src_dir into dst_dir, preserving structure.
416
+
417
+ Returns (added, updated) lists of relative paths.
418
+ """
419
+ added: list[str] = []
420
+ updated: list[str] = []
421
+ for src_file in sorted(src_dir.rglob(glob)):
422
+ rel = src_file.relative_to(src_dir)
423
+ dst_file = dst_dir / rel
424
+ dst_file.parent.mkdir(parents=True, exist_ok=True)
425
+ existed = dst_file.exists()
426
+ shutil.copy2(src_file, dst_file)
427
+ (updated if existed else added).append(f" {rel}")
428
+ return added, updated
429
+
430
+
431
+ def _report_copy(label: str, added: list[str], updated: list[str]) -> None:
432
+ if added:
433
+ print(f"{label} added:")
434
+ print("\n".join(added))
435
+ if updated:
436
+ print(f"{label} updated:")
437
+ print("\n".join(updated))
438
+ if not added and not updated:
439
+ print(f"{label}: nothing to copy (source directory empty or missing)")
440
+ print()
441
+
442
+
443
+ def _claude_config_dir() -> Path:
444
+ """Return the Claude Code global config directory.
445
+
446
+ Resolution order (mirrors Claude Code):
447
+ 1. CLAUDE_CONFIG_DIR environment variable
448
+ 2. ~/.claude/ (default)
449
+ """
450
+ env = os.environ.get("CLAUDE_CONFIG_DIR")
451
+ if env:
452
+ return Path(env)
453
+ return Path.home() / ".claude"
454
+
455
+
456
+ def _setup_claude() -> None:
457
+ """Copy autoharness agents and skills into the Claude Code global config dir.
458
+
459
+ Claude Code discovers agents from {config_dir}/agents/ and skills from
460
+ {config_dir}/skills/ at session start.
461
+ """
462
+ home = _home()
463
+ config_dir = _claude_config_dir()
464
+ print(f"Claude Code config dir: {config_dir}")
465
+ print()
466
+
467
+ a, u = _copy_tree(home / ".github" / "agents", config_dir / "agents", "*.md")
468
+ _report_copy("Agents", a, u)
469
+
470
+ a, u = _copy_tree(home / ".github" / "skills", config_dir / "skills", "SKILL.md")
471
+ _report_copy("Skills", a, u)
472
+
473
+ print("Done. Restart Claude Code to pick up the changes.")
474
+ print("Run this command again after upgrading autoharness.")
475
+
476
+
477
+ def _codex_config_dir() -> Path:
478
+ """Return the Codex global config directory.
479
+
480
+ Resolution order (mirrors Codex):
481
+ 1. CODEX_HOME environment variable
482
+ 2. ~/.codex/ (default)
483
+ """
484
+ env = os.environ.get("CODEX_HOME")
485
+ if env:
486
+ return Path(env)
487
+ return Path.home() / ".codex"
488
+
489
+
490
+ def _setup_codex() -> None:
491
+ """Copy autoharness skills into the Codex global skills directory.
492
+
493
+ Codex discovers skills from {config_dir}/skills/<skill-name>/SKILL.md.
494
+ Codex does not have a separate agents directory — skills serve as the
495
+ agent entry points. The skill directory name becomes the skill name
496
+ (e.g. install-harness, tune-harness).
497
+
498
+ Note: Codex SKILL.md files use the same frontmatter format as autoharness.
499
+ If the frontmatter lacks a top-level 'name:' field, Codex infers the name
500
+ from the directory.
501
+ """
502
+ home = _home()
503
+ config_dir = _codex_config_dir()
504
+ print(f"Codex config dir: {config_dir}")
505
+ print()
506
+
507
+ a, u = _copy_tree(home / ".github" / "skills", config_dir / "skills", "SKILL.md")
508
+ _report_copy("Skills", a, u)
509
+
510
+ print("Done. Restart Codex to pick up the changes.")
511
+ print("Run this command again after upgrading autoharness.")
512
+
513
+
514
+ def main(argv: list[str] | None = None) -> None:
515
+ args = argv if argv is not None else sys.argv[1:]
516
+
517
+ if not args or args[0] in ("help", "--help", "-h"):
518
+ print(USAGE)
519
+ return
520
+
521
+ command = args[0]
522
+
523
+ if command == "home":
524
+ print(_home())
525
+ elif command == "version":
526
+ print(_version())
527
+ elif command == "verify-workspace":
528
+ _verify_workspace_command(args[1:])
529
+ elif command == "setup-vscode":
530
+ _setup_vscode()
531
+ elif command == "setup-copilot-cli":
532
+ _setup_copilot_cli()
533
+ elif command == "setup-claude":
534
+ _setup_claude()
535
+ elif command == "setup-codex":
536
+ _setup_codex()
537
+ else:
538
+ print(f"Unknown command: {command}", file=sys.stderr)
539
+ print(USAGE, file=sys.stderr)
540
+ sys.exit(1)
541
+
542
+
543
+ if __name__ == "__main__":
544
+ main()