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.
- autoharness/__init__.py +7 -0
- autoharness/cli.py +544 -0
- autoharness/data/.github/agents/auto-mergeinstall.agent.md +222 -0
- autoharness/data/.github/agents/auto-tune.agent.md +164 -0
- autoharness/data/.github/agents/orchestrator.agent.md +134 -0
- autoharness/data/.github/agents/ship.agent.md +196 -0
- autoharness/data/.github/agents/stage.agent.md +175 -0
- autoharness/data/.github/copilot-instructions.md +148 -0
- autoharness/data/.github/copilot-review-instructions.md +190 -0
- autoharness/data/.github/instructions/harness-architecture.instructions.md +306 -0
- autoharness/data/.github/prompts/install-harness.prompt.md +16 -0
- autoharness/data/.github/prompts/tune-harness.prompt.md +14 -0
- autoharness/data/.github/skills/install-harness/SKILL.md +1229 -0
- autoharness/data/.github/skills/tune-harness/SKILL.md +821 -0
- autoharness/data/.github/skills/verify-harness/SKILL.md +211 -0
- autoharness/data/.github/skills/workspace-discovery/SKILL.md +632 -0
- autoharness/data/AGENTS.md +58 -0
- autoharness/data/docs/backlog-integration.md +64 -0
- autoharness/data/docs/backlogit-compatibility-matrix.md +95 -0
- autoharness/data/docs/backlogit-graduation-checklist.md +105 -0
- autoharness/data/docs/backlogit-operating-model.md +262 -0
- autoharness/data/docs/capability-packs.md +596 -0
- autoharness/data/docs/closure/.gitkeep +0 -0
- autoharness/data/docs/compound/.gitkeep +0 -0
- autoharness/data/docs/compound/010-S-session-lifecycle-gates.md +58 -0
- autoharness/data/docs/compound/012-S-dynamic-policy-acceptance-path.md +78 -0
- autoharness/data/docs/compound/012-S-portability-scan-allow-list.md +70 -0
- autoharness/data/docs/compound/015-s-graphtor-docs-pack-registration.md +67 -0
- autoharness/data/docs/compound/016-s-community-template-registry.md +52 -0
- autoharness/data/docs/compound/2026-05-05-agent-tool-list-completeness.md +35 -0
- autoharness/data/docs/compound/2026-05-05-assertion-specificity-agent-file-names.md +41 -0
- autoharness/data/docs/compound/2026-05-05-foundation-assertion-anchoring.md +46 -0
- autoharness/data/docs/compound/2026-05-05-git-reset-hard-vs-checkout-hash.md +35 -0
- autoharness/data/docs/compound/2026-05-05-harness-config-round-trip-requirement.md +39 -0
- autoharness/data/docs/compound/2026-05-05-multi-phase-skill-scope-matrix.md +45 -0
- autoharness/data/docs/compound/2026-05-05-path-traversal-validation-parts.md +41 -0
- autoharness/data/docs/compound/2026-05-05-resolution-order-config-first-not-detection-first.md +40 -0
- autoharness/data/docs/compound/2026-05-05-runtime-surfaces-browser-tooling-detection.md +40 -0
- autoharness/data/docs/compound/2026-05-05-stride-evidence-anchor-pattern.md +43 -0
- autoharness/data/docs/compound/2026-05-06-github-review-comment-id-types.md +63 -0
- autoharness/data/docs/compound/2026-05-06-harness-manifest-artifacts-vs-entries-added.md +61 -0
- autoharness/data/docs/compound/2026-05-06-p010-agent-role-boundaries.md +38 -0
- autoharness/data/docs/compound/2026-05-06-p011-branch-before-mutation-design.md +42 -0
- autoharness/data/docs/compound/2026-05-06-p012-tool-availability-gate-and-dispatch.md +55 -0
- autoharness/data/docs/compound/2026-05-06-template-variable-escaping-in-docs.md +51 -0
- autoharness/data/docs/compound/2026-05-07-backlogit-shipment-status-constraints.md +47 -0
- autoharness/data/docs/compound/2026-05-07-nested-code-fence-rendering.md +56 -0
- autoharness/data/docs/compound/p013-orchestrator-model-routing.md +116 -0
- autoharness/data/docs/credits.md +186 -0
- autoharness/data/docs/decisions/.gitkeep +0 -0
- autoharness/data/docs/design-docs/.gitkeep +0 -0
- autoharness/data/docs/design-docs/context-window-compaction-strategy.md +14 -0
- autoharness/data/docs/environment-setup.md +157 -0
- autoharness/data/docs/exec-plans/2026-04-23-shipment-integrity-gates-plan.md +270 -0
- autoharness/data/docs/exec-plans/2026-04-24-fix-ci-v2-overhaul-plan.md +136 -0
- autoharness/data/docs/exec-plans/2026-04-24-policy-standards-enforcement-plan.md +127 -0
- autoharness/data/docs/exec-plans/2026-04-24-self-install-support-plan.md +184 -0
- autoharness/data/docs/exec-plans/2026-05-05-browser-experiment-skills-plan.md +106 -0
- autoharness/data/docs/exec-plans/2026-05-05-harness-doctor-skill-plan.md +64 -0
- autoharness/data/docs/exec-plans/2026-05-05-security-harness-surface-plan.md +256 -0
- autoharness/data/docs/exec-plans/2026-05-09-context-window-compaction-spike.md +168 -0
- autoharness/data/docs/exec-plans/2026-05-09-role-enforcement-deliberation.md +222 -0
- autoharness/data/docs/getting-started.md +474 -0
- autoharness/data/docs/memory/.gitkeep +0 -0
- autoharness/data/docs/memory/2026-04-26-ship-005-s-execution.md +24 -0
- autoharness/data/docs/memory/2026-04-26-stage-autotune-follow-up-hardening.md +19 -0
- autoharness/data/docs/memory/2026-05-05-ship-006-s-security-harness-surface.md +49 -0
- autoharness/data/docs/memory/2026-05-05-ship-007-s-closure.md +65 -0
- autoharness/data/docs/memory/2026-05-05-stage-007-s-restaging.md +26 -0
- autoharness/data/docs/memory/2026-05-05-stage-atv-security-integration.md +38 -0
- autoharness/data/docs/memory/2026-05-06-ship-008-s-harness-doctor.md +76 -0
- autoharness/data/docs/memory/2026-05-06-ship-009-s-agent-session-discipline.md +54 -0
- autoharness/data/docs/memory/ship-010-S.md +44 -0
- autoharness/data/docs/memory/ship-011-S.md +75 -0
- autoharness/data/docs/memory/ship-012-S.md +59 -0
- autoharness/data/docs/plans/.gitkeep +0 -0
- autoharness/data/docs/primitives.md +375 -0
- autoharness/data/docs/product-specs/.gitkeep +0 -0
- autoharness/data/docs/product-specs/orchestrator-model-routing-spec.md +129 -0
- autoharness/data/docs/reference-library.md +219 -0
- autoharness/data/docs/research/2026-04-08-backlogit-harness-evolution-analysis.md +572 -0
- autoharness/data/docs/research/2026-04-10-atv-starterkit-integration-analysis.md +794 -0
- autoharness/data/docs/spikes/011.001-coding-discipline-instructions.md +133 -0
- autoharness/data/docs/spikes/011.002-review-persona-expansion.md +145 -0
- autoharness/data/docs/spikes/011.003-sdk-guardrail-patterns.md +132 -0
- autoharness/data/docs/spikes/011.004-portability-audit-pattern.md +174 -0
- autoharness/data/docs/tuning-guide.md +218 -0
- autoharness/data/schemas/backlog-tool-registry.schema.json +203 -0
- autoharness/data/schemas/community-template-registry.schema.json +104 -0
- autoharness/data/schemas/harness-config/0.9.0.schema.json +70 -0
- autoharness/data/schemas/harness-config/1.0.0.schema.json +214 -0
- autoharness/data/schemas/harness-config.schema.json +357 -0
- autoharness/data/schemas/harness-manifest/0.9.0.schema.json +103 -0
- autoharness/data/schemas/harness-manifest/1.0.0.schema.json +203 -0
- autoharness/data/schemas/harness-manifest.schema.json +203 -0
- autoharness/data/schemas/workspace-profile/0.9.0.schema.json +95 -0
- autoharness/data/schemas/workspace-profile/1.0.0.schema.json +436 -0
- autoharness/data/schemas/workspace-profile.schema.json +459 -0
- autoharness/data/templates/agents/adversarial-review.agent.md.tmpl +180 -0
- autoharness/data/templates/agents/language-engineer.agent.md.tmpl +67 -0
- autoharness/data/templates/agents/orchestrator.agent.md.tmpl +339 -0
- autoharness/data/templates/agents/prompt-builder.agent.md.tmpl +67 -0
- autoharness/data/templates/agents/research/learnings-researcher.agent.md.tmpl +79 -0
- autoharness/data/templates/agents/review/agent-native-parity-reviewer.agent.md.tmpl +69 -0
- autoharness/data/templates/agents/review/architecture-strategist.agent.md.tmpl +65 -0
- autoharness/data/templates/agents/review/concurrency-reviewer.agent.md.tmpl +66 -0
- autoharness/data/templates/agents/review/constitution-reviewer.agent.md.tmpl +66 -0
- autoharness/data/templates/agents/review/scope-boundary-auditor.agent.md.tmpl +66 -0
- autoharness/data/templates/agents/review/security-lens-reviewer.agent.md.tmpl +110 -0
- autoharness/data/templates/agents/review/security-reviewer.agent.md.tmpl +83 -0
- autoharness/data/templates/agents/review/technology-reviewer.agent.md.tmpl +68 -0
- autoharness/data/templates/agents/security-sentinel.agent.md.tmpl +159 -0
- autoharness/data/templates/agents/ship.agent.md.tmpl +726 -0
- autoharness/data/templates/agents/stage.agent.md.tmpl +758 -0
- autoharness/data/templates/backlog/config.yml.tmpl +58 -0
- autoharness/data/templates/backlog/registries/backlog-md.registry.yaml +119 -0
- autoharness/data/templates/backlog/registries/backlogit.registry.yaml +436 -0
- autoharness/data/templates/backlog/stash.md.tmpl +17 -0
- autoharness/data/templates/community/README.md +81 -0
- autoharness/data/templates/community/agents/adr-generator.agent.md.tmpl +202 -0
- autoharness/data/templates/community/instructions/agent-safety.instructions.md.tmpl +94 -0
- autoharness/data/templates/community/instructions/code-review-generic.instructions.md.tmpl +173 -0
- autoharness/data/templates/community/registry.yaml +245 -0
- autoharness/data/templates/community/skills/acquire-codebase-knowledge/SKILL.md.tmpl +111 -0
- autoharness/data/templates/community/skills/brainstorming/SKILL.md.tmpl +61 -0
- autoharness/data/templates/community/skills/changelog-generator/SKILL.md.tmpl +96 -0
- autoharness/data/templates/community/skills/debugging/SKILL.md.tmpl +160 -0
- autoharness/data/templates/community/skills/document-review/SKILL.md.tmpl +134 -0
- autoharness/data/templates/community/skills/mcp-builder/SKILL.md.tmpl +212 -0
- autoharness/data/templates/community/skills/meta-prompting/SKILL.md.tmpl +80 -0
- autoharness/data/templates/community/skills/receiving-code-review/SKILL.md.tmpl +145 -0
- autoharness/data/templates/community/skills/simplifying-code/SKILL.md.tmpl +91 -0
- autoharness/data/templates/community/skills/skill-creator/SKILL.md.tmpl +163 -0
- autoharness/data/templates/community/skills/verification-before-completion/SKILL.md.tmpl +169 -0
- autoharness/data/templates/community/skills/writing-plans/SKILL.md.tmpl +87 -0
- autoharness/data/templates/community/skills/writing-tests/SKILL.md.tmpl +199 -0
- autoharness/data/templates/foundation/AGENTS.md.tmpl +381 -0
- autoharness/data/templates/foundation/constitution.instructions.md.tmpl +306 -0
- autoharness/data/templates/foundation/copilot-instructions.md.tmpl +214 -0
- autoharness/data/templates/harness-config.yaml.tmpl +106 -0
- autoharness/data/templates/instructions/adversarial-review.instructions.md.tmpl +64 -0
- autoharness/data/templates/instructions/agent-engram.instructions.md.tmpl +109 -0
- autoharness/data/templates/instructions/agent-intercom.instructions.md.tmpl +84 -0
- autoharness/data/templates/instructions/architecture-doc.instructions.md.tmpl +96 -0
- autoharness/data/templates/instructions/backlog-integration.instructions.md.tmpl +114 -0
- autoharness/data/templates/instructions/backlogit-sql-schema.instructions.md.tmpl +342 -0
- autoharness/data/templates/instructions/backlogit-yaml-header-tooling.instructions.md.tmpl +131 -0
- autoharness/data/templates/instructions/backlogit.instructions.md.tmpl +139 -0
- autoharness/data/templates/instructions/browser-verification.instructions.md.tmpl +79 -0
- autoharness/data/templates/instructions/ci-security.instructions.md.tmpl +95 -0
- autoharness/data/templates/instructions/circuit-breaker.instructions.md.tmpl +148 -0
- autoharness/data/templates/instructions/commit-message.instructions.md.tmpl +63 -0
- autoharness/data/templates/instructions/concurrency.instructions.md.tmpl +79 -0
- autoharness/data/templates/instructions/context-efficiency.instructions.md.tmpl +95 -0
- autoharness/data/templates/instructions/continuous-learning.instructions.md.tmpl +60 -0
- autoharness/data/templates/instructions/git-merge.instructions.md.tmpl +74 -0
- autoharness/data/templates/instructions/github-pr-automation.instructions.md.tmpl +517 -0
- autoharness/data/templates/instructions/graphtor-docs.instructions.md.tmpl +88 -0
- autoharness/data/templates/instructions/markdown.instructions.md.tmpl +64 -0
- autoharness/data/templates/instructions/mcp-server.instructions.md.tmpl +118 -0
- autoharness/data/templates/instructions/prompt-builder.instructions.md.tmpl +57 -0
- autoharness/data/templates/instructions/pull-request.instructions.md.tmpl +42 -0
- autoharness/data/templates/instructions/release-observability.instructions.md.tmpl +72 -0
- autoharness/data/templates/instructions/role-enforcement.instructions.md.tmpl +68 -0
- autoharness/data/templates/instructions/strict-safety.instructions.md.tmpl +73 -0
- autoharness/data/templates/instructions/technology-go.instructions.md.tmpl +126 -0
- autoharness/data/templates/instructions/technology-python.instructions.md.tmpl +97 -0
- autoharness/data/templates/instructions/technology-rust.instructions.md.tmpl +98 -0
- autoharness/data/templates/instructions/technology-typescript.instructions.md.tmpl +96 -0
- autoharness/data/templates/instructions/technology.instructions.md.tmpl +52 -0
- autoharness/data/templates/instructions/workflows.instructions.md.tmpl +90 -0
- autoharness/data/templates/instructions/writing-style.instructions.md.tmpl +61 -0
- autoharness/data/templates/policies/policy-proposal.md.tmpl +49 -0
- autoharness/data/templates/policies/workflow-policies.md.tmpl +352 -0
- autoharness/data/templates/prompts/ping-loop.prompt.md.tmpl +31 -0
- autoharness/data/templates/prompts/stage-grouping-analysis.prompt.md.tmpl +30 -0
- autoharness/data/templates/scripts/.markdownlint.json.tmpl +6 -0
- autoharness/data/templates/scripts/pre-commit-markdownlint.ps1.tmpl +39 -0
- autoharness/data/templates/scripts/pre-commit-markdownlint.sh.tmpl +38 -0
- autoharness/data/templates/scripts/start.ps1.tmpl +149 -0
- autoharness/data/templates/scripts/start.sh.tmpl +57 -0
- autoharness/data/templates/skills/browser-automation/SKILL.md.tmpl +146 -0
- autoharness/data/templates/skills/build-feature/SKILL.md.tmpl +153 -0
- autoharness/data/templates/skills/compact-context/SKILL.md.tmpl +128 -0
- autoharness/data/templates/skills/compound/SKILL.md.tmpl +100 -0
- autoharness/data/templates/skills/compound-refresh/SKILL.md.tmpl +102 -0
- autoharness/data/templates/skills/deliberate/SKILL.md.tmpl +295 -0
- autoharness/data/templates/skills/evolve/SKILL.md.tmpl +54 -0
- autoharness/data/templates/skills/file-lock/SKILL.md.tmpl +79 -0
- autoharness/data/templates/skills/file-lock/scripts/acquire_lock.ps1 +72 -0
- autoharness/data/templates/skills/file-lock/scripts/acquire_lock.sh +48 -0
- autoharness/data/templates/skills/file-lock/scripts/release_lock.ps1 +48 -0
- autoharness/data/templates/skills/file-lock/scripts/release_lock.sh +41 -0
- autoharness/data/templates/skills/fix-ci/SKILL.md.tmpl +386 -0
- autoharness/data/templates/skills/harness-architect/SKILL.md.tmpl +162 -0
- autoharness/data/templates/skills/harness-doctor/SKILL.md.tmpl +233 -0
- autoharness/data/templates/skills/harvest/SKILL.md.tmpl +150 -0
- autoharness/data/templates/skills/impl-plan/SKILL.md.tmpl +135 -0
- autoharness/data/templates/skills/iterative-experiment/SKILL.md.tmpl +136 -0
- autoharness/data/templates/skills/learn/SKILL.md.tmpl +54 -0
- autoharness/data/templates/skills/observe/SKILL.md.tmpl +55 -0
- autoharness/data/templates/skills/operational-closure/SKILL.md.tmpl +113 -0
- autoharness/data/templates/skills/plan-harden/SKILL.md.tmpl +107 -0
- autoharness/data/templates/skills/plan-review/SKILL.md.tmpl +155 -0
- autoharness/data/templates/skills/pr-lifecycle/SKILL.md.tmpl +190 -0
- autoharness/data/templates/skills/review/SKILL.md.tmpl +190 -0
- autoharness/data/templates/skills/runtime-verification/SKILL.md.tmpl +161 -0
- autoharness/data/templates/skills/safety-modes/SKILL.md.tmpl +161 -0
- autoharness/data/templates/skills/security-audit/SKILL.md.tmpl +191 -0
- autoharness/data/templates/skills/shipment-reconcile/SKILL.md.tmpl +160 -0
- autoharness/data/templates/skills/skill-search/SKILL.md.tmpl +100 -0
- autoharness/data/templates/skills/skill-search/scripts/search.ps1 +85 -0
- autoharness/data/templates/skills/skill-search/scripts/search.sh +78 -0
- autoharness/data/templates/skills/spike/SKILL.md.tmpl +361 -0
- autoharness/schema_contracts.py +460 -0
- autoharness/verify_workspace.py +2137 -0
- autoharness-1.4.2.dist-info/METADATA +158 -0
- autoharness-1.4.2.dist-info/RECORD +221 -0
- autoharness-1.4.2.dist-info/WHEEL +4 -0
- autoharness-1.4.2.dist-info/entry_points.txt +2 -0
- autoharness-1.4.2.dist-info/licenses/LICENSE +21 -0
autoharness/__init__.py
ADDED
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()
|