diff --git a/.agents/skills/harness/SKILL.md b/.agents/skills/harness/SKILL.md new file mode 100644 index 0000000..0c15d8f --- /dev/null +++ b/.agents/skills/harness/SKILL.md @@ -0,0 +1,49 @@ +--- +name: harness +description: > + Set up or run the long-running-agent harness: initialize the durable state + files (.agents/features.json, .agents/progress.md), then run the fixed + startup and end-of-session loop. Use when the user says "/harness", "init the + harness", "start a session", "hand off", or when .agents/progress.md or + .agents/features.json exists and a session starts or ends. +--- + +The repository is the system of record. Chat is not. State that must survive a session lives in files; a fresh session recovers it by reading them, not by guessing. Based on [Learn Harness Engineering](https://github.com/walkinglabs/learn-harness-engineering). + +## Init (once per repo, skip files that already exist) + +Copy from `templates/` next to this file, then adapt — never leave placeholders: + +1. `.agents/progress.md` — session log + current verified state. +2. `.agents/features.json` — every user-visible feature with `id`, `priority`, `status`, `verification` steps and `evidence`. Write real features from the repo's README/issues/code; all start `not_started`. +3. Optional for large sessions: `templates/session-handoff.md` → `.agents/handoff.md`. + +Dependencies are installed by the devcontainer features, not here; the baseline is the CI status on the repo's dev forge (GitHub, GitLab, Azure DevOps), which the hook prints. Put each feature's real verification steps in `.agents/features.json`. Commit the files as one change. Once `.agents/progress.md` or `.agents/features.json` exists, the `SessionStart` hook prints the state and the `Stop` hook checks the handoff. + +## Session start + +1. `pwd` — confirm the repo root. +2. Read the repo's own rules: `CLAUDE.md`, `AGENTS.md`, `.agents/instructions/*.instructions.md` (the `SessionStart` hook lists which exist). They stay authoritative for commit format, PR base/title, scope limits and tooling; the harness adds the loop around them, it never overrides them. +3. Read `.agents/progress.md`, then `.agents/features.json`. +4. `git log --oneline -5`. The `SessionStart` hook already printed the state and the forge CI status of the branch. +5. CI red on the branch? That is the baseline: fix it first. No new work on a broken start. +6. Pick the highest-priority unfinished feature. Set it `in_progress`. One at a time. + +## While working + +- Follow the repo's rules while working (minimal-change discipline, commit/PR format, CI naming). A rule conflict with the harness: the repo rule wins; record it in the progress log. +- Stay inside the selected feature. A blocker may justify one narrow supporting fix. +- `passing` needs the feature's `verification` steps actually run, with evidence recorded (command + result, not "looks fine"). +- Never reword, delete or weaken a feature or test to make it pass. Never loosen verification mid-feature. +- Declared done? Run the `harness-evaluator` agent: fresh context, tries to refute the claim. + +## Session end + +1. Update `.agents/progress.md`: verified state, what ran, evidence, known risk, next best step. +2. Update `.agents/features.json` statuses and evidence. +3. Walk `templates/clean-state-checklist.md`. +4. Commit once the repo is safe to resume, using the repo's commit and PR conventions from its rules. CI on the branch must be green or explained in the progress log; the next session must start without manual repair. + +## Boundaries + +Do not invent features, evidence or verification results. Do not mark `passing` without a run. Do not hide unfinished work by editing the list. diff --git a/.agents/skills/harness/templates/clean-state-checklist.md b/.agents/skills/harness/templates/clean-state-checklist.md new file mode 100644 index 0000000..cfafa51 --- /dev/null +++ b/.agents/skills/harness/templates/clean-state-checklist.md @@ -0,0 +1,7 @@ +# Clean State Checklist + +- [ ] CI on the branch is green (or red for a reason recorded in the progress log). +- [ ] Progress is recorded in `.agents/progress.md`. +- [ ] `.agents/features.json` shows what is passing versus unverified, with evidence. +- [ ] No half-finished step is left undocumented. +- [ ] Work is committed; the next session continues without manual repair. diff --git a/.agents/skills/harness/templates/features.json b/.agents/skills/harness/templates/features.json new file mode 100644 index 0000000..0a20025 --- /dev/null +++ b/.agents/skills/harness/templates/features.json @@ -0,0 +1,28 @@ +{ + "project": "replace-with-project-name", + "last_updated": "YYYY-MM-DD", + "rules": { + "single_active_feature": true, + "passing_requires_evidence": true, + "do_not_skip_verification": true + }, + "status_legend": { + "not_started": "Work has not begun.", + "in_progress": "The feature is the current active task.", + "blocked": "Work cannot continue until a documented blocker is resolved.", + "passing": "Required verification has passed and evidence is recorded." + }, + "features": [ + { + "id": "feat-001", + "priority": 1, + "area": "replace", + "title": "Replace with a user-visible behavior", + "user_visible_behavior": "What a user can do or see when this works.", + "status": "not_started", + "verification": ["Command or steps that prove it works."], + "evidence": [], + "notes": "" + } + ] +} diff --git a/.agents/skills/harness/templates/progress.md b/.agents/skills/harness/templates/progress.md new file mode 100644 index 0000000..9f81e7f --- /dev/null +++ b/.agents/skills/harness/templates/progress.md @@ -0,0 +1,23 @@ +# Progress Log + +Agent-agnostic session log. Read at session start, update before handoff. (Course name: `claude-progress.md`; kept generic here.) + +## Current Verified State + +- Standard startup: `SessionStart` hook (prints this state and the forge CI status) +- Standard verification path: +- Highest-priority unfinished feature: +- Current blocker: + +## Session Log + +### Session 001 + +- Date: +- Goal: +- Completed: +- Verification run: +- Evidence captured: +- Commits: +- Known risk or unresolved issue: +- Next best step: diff --git a/.agents/skills/harness/templates/session-handoff.md b/.agents/skills/harness/templates/session-handoff.md new file mode 100644 index 0000000..d35ad7b --- /dev/null +++ b/.agents/skills/harness/templates/session-handoff.md @@ -0,0 +1,29 @@ +# Session Handoff + +## Verified Now + +- What is working: +- What verification actually ran: + +## Changed This Session + +- Code or behavior: +- Harness or infrastructure: + +## Broken Or Unverified + +- Known defect: +- Unverified path: +- Risk for the next session: + +## Next Best Step + +- Highest-priority unfinished feature: +- What counts as passing: +- What must not change during that step: + +## Commands + +- Startup: +- Verification: +- Focused debug: diff --git a/.claude/skills/harness b/.claude/skills/harness new file mode 120000 index 0000000..bcff8f7 --- /dev/null +++ b/.claude/skills/harness @@ -0,0 +1 @@ +../../.agents/skills/harness \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg index 4d2f691..e93cf46 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,3 +1,4 @@ #!/bin/sh command -v zz-use >/dev/null 2>&1 || . "$(dirname "$0")/install.sh" +export ZZ_ORIGIN_REF="${ZZ_ORIGIN_REF:-v3}" zz-use -x git-hook-commitmsg "$@" diff --git a/.husky/install.sh b/.husky/install.sh index 3c89a77..af89682 100755 --- a/.husky/install.sh +++ b/.husky/install.sh @@ -62,3 +62,10 @@ fi fi exit 0 ) + ### Pinned to the tomgrv/scripts major this release was built against, so a + ### later breaking rename on main cannot break installs of this release. + ### Exported so every zz-use below resolves from the same ref. Override with ZZ_ORIGIN_REF. + export ZZ_ORIGIN_REF="${ZZ_ORIGIN_REF:-v3}" + if curl -fsSL "${ZZ_SCRIPTS_SETUP_URL:-https://raw.githubusercontent.com/tomgrv/scripts/$ZZ_ORIGIN_REF/setup.sh}" -o "$_zz_setup_tmp" && + zz-use json "json-*" >&2 || + echo ".husky/install.sh: zz-use json failed" >&2 diff --git a/.husky/post-checkout b/.husky/post-checkout index 52b318b..8ac471d 100755 --- a/.husky/post-checkout +++ b/.husky/post-checkout @@ -1,3 +1,4 @@ #!/bin/sh command -v zz-use >/dev/null 2>&1 || . "$(dirname "$0")/install.sh" +export ZZ_ORIGIN_REF="${ZZ_ORIGIN_REF:-v3}" zz-use -x git-hook-postcheckout "$@" diff --git a/.husky/post-merge b/.husky/post-merge index 0172df1..d722ae3 100755 --- a/.husky/post-merge +++ b/.husky/post-merge @@ -1,3 +1,4 @@ #!/bin/sh command -v zz-use >/dev/null 2>&1 || . "$(dirname "$0")/install.sh" +export ZZ_ORIGIN_REF="${ZZ_ORIGIN_REF:-v3}" zz-use -x git-hook-postmerge "$@" diff --git a/.husky/pre-commit b/.husky/pre-commit index 9f5ce24..8f8b3a1 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,4 @@ #!/bin/sh command -v zz-use >/dev/null 2>&1 || . "$(dirname "$0")/install.sh" +export ZZ_ORIGIN_REF="${ZZ_ORIGIN_REF:-v3}" zz-use -x git-hook-precommit "$@" diff --git a/.husky/pre-push b/.husky/pre-push index c1bbc0c..47629ce 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,3 +1,4 @@ #!/bin/sh command -v zz-use >/dev/null 2>&1 || . "$(dirname "$0")/install.sh" +export ZZ_ORIGIN_REF="${ZZ_ORIGIN_REF:-v3}" zz-use -x git-hook-prepush "$@" diff --git a/.husky/prepare-commit-msg b/.husky/prepare-commit-msg index f1699f8..b877d76 100755 --- a/.husky/prepare-commit-msg +++ b/.husky/prepare-commit-msg @@ -1,3 +1,4 @@ #!/bin/sh command -v zz-use >/dev/null 2>&1 || . "$(dirname "$0")/install.sh" +export ZZ_ORIGIN_REF="${ZZ_ORIGIN_REF:-v3}" zz-use -x git-hook-preparecommitmsg "$@" diff --git a/package.json b/package.json index c53f012..9ec75d8 100644 --- a/package.json +++ b/package.json @@ -1,252 +1,252 @@ { - "name": "@tomgrv/scripts", - "version": "6.0.0", - "description": "Reusable POSIX shell scripts shared across tomgrv devcontainer-features and vps", - "license": "MIT", - "scripts": { - "lint": "npx --yes lint-staged -p false", - "prepare": "sh .husky/install.sh || echo 'Husky not installed'", - "test": "bats --recursive .", - "update": "npm-check-updates -i -u", - "update-all": "npm run update -w --root" - }, - "config": { - "commitizen": { - "path": "@commitlint/cz-commitlint" - } - }, - "devDependencies": { - "@modelcontextprotocol/sdk": "^1", - "husky": "*", - "npm-check-updates": "*" - }, - "optionalDependencies": { - "@tomgrv/devcontainer-features-common-utils": ">=5" - }, - "peerDependencies": { - "@commitlint/cli": "*", - "@commitlint/config-conventional": "*", - "@commitlint/core": "*", - "@commitlint/cz-commitlint": "*", - "commitizen": "*", - "git-precommit-checks": "^3", - "prettier": "^3", - "prettier-plugin-sh": "*" - }, - "private": true, - "workspaces": [ - "zz", - "zz-use", - "zz-colors", - "zz-log", - "zz-args", - "zz-prompt", - "zz-menu", - "zz-load", - "zz-bindir", - "zz-dispatch", - "zz-npx", - "zz-persist", - "zz-call", - "zz-install", - "zz-require", - "zz-fetch", - "zz-vars", - "zz-setup", - "zz-step", - "zz-interactive", - "zz-progress", - "json", - "json-load", - "json-validate", - "json-normalize", - "json-merge", - "json-render", - "yaml", - "yaml-merge", - "yaml-render", - "kube", - "kube-progress", - "kube-diagnose", - "kube-teardown", - "kube-service-port", - "kube-require", - "kube-rollout", - "kube-apply", - "systemd-service", - "helm-repo", - "helm-install", - "feature", - "feature-context", - "edit-script", - "distribute-utils", - "feature-install", - "feature-configure", - "git-align", - "git-autorebase", - "git-co", - "git-degit", - "git-fix", - "git-fix-author", - "git-fix-base", - "git-fix-blanks", - "git-fix-children", - "git-fix-date", - "git-fix-del", - "git-fix-emoji", - "git-hook-commitmsg", - "git-hook-installplugins", - "git-hook-postcheckout", - "git-hook-postmerge", - "git-hook-precommit", - "git-hook-preparecommitmsg", - "git-hook-prepush", - "git-fix-last", - "git-fix-lock", - "git-fix-message", - "git-fix-mode", - "git-fix-orphans", - "git-fix-privacy", - "git-fix-prune", - "git-fix-rights", - "git-fix-secrets", - "git-fix-up", - "git-forall", - "git-getcommit", - "git-integrate", - "git-pick", - "git-release", - "git-release-alpha", - "git-release-beta", - "git-release-hotfix", - "git-release-prod", - "git-unset", - "ws-impacted", - "ws-list", - "gv", - "bump-tag", - "bump-changelog", - "bump-version", - "ws-test", - "php-list-changed" - ], - "prettier": { - "insertPragma": true, - "overrides": [ - { - "files": "*.yml", - "options": { - "tabWidth": 2, - "useTabs": false + "name": "@tomgrv/scripts", + "description": "Reusable POSIX shell scripts shared across tomgrv devcontainer-features and vps", + "version": "6.0.0", + "config": { + "commitizen": { + "path": "@commitlint/cz-commitlint" } - }, - { - "files": "*.json", - "options": { - "tabWidth": 4, - "useTabs": false + }, + "bump-changelog": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + } + ] + }, + "bump-version": { + "files": [ + { + "filename": "composer.json", + "type": "json" + }, + { + "filename": "package.json", + "type": "json" + }, + { + "filename": "package.json", + "type": "json@ws" + }, + { + "filename": "devcontainer-feature.json", + "type": "json@ws" + }, + { + "filename": "VERSION", + "type": "plain-text" + } + ] + }, + "commit-and-tag-version": { + "scripts": { + "prebump": "gitversion -config .gitversion -showvariable SemVer" } - } - ], - "plugins": [ - "prettier-plugin-sh" - ], - "semi": false, - "singleQuote": true, - "tabWidth": 4, - "trailingComma": "es5" - }, - "bump-changelog": { - "types": [ - { - "type": "feat", - "section": "Features" - }, - { - "type": "fix", - "section": "Bug Fixes" - } - ] - }, - "bump-version": { - "files": [ - { - "filename": "composer.json", - "type": "json" - }, - { - "filename": "package.json", - "type": "json" - }, - { - "filename": "package.json", - "type": "json@ws" - }, - { - "filename": "devcontainer-feature.json", - "type": "json@ws" - }, - { - "filename": "VERSION", - "type": "plain-text" - } - ] - }, - "commit-and-tag-version": { - "scripts": { - "prebump": "gitversion -config .gitversion -showvariable SemVer" - } - }, - "commitlint": { - "extends": [ - "@commitlint/config-conventional", - "@commitlint/config-workspace-scopes" - ], - "rules": { - "subject-case": [ - 2, - "never", - [ - "start-case", - "pascal-case", - "upper-case" + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional", + "@commitlint/config-workspace-scopes" + ], + "rules": { + "subject-case": [ + 2, + "never", + [ + "start-case", + "pascal-case", + "upper-case" + ] + ] + } + }, + "devDependencies": { + "@modelcontextprotocol/sdk": "^1", + "husky": "*", + "npm-check-updates": "*" + }, + "git-precommit-checks": { + "rules": [ + { + "message": "You've got leftover conflict markers", + "regex": "/^[<>|=]{4,}/m" + }, + { + "filter": "(^package\\.json|\\.git-precommit-checks.json)$", + "message": "You have unfinished devs", + "nonBlocking": "true", + "regex": "(?:FIXME|TODO)" + } ] - ] - } - }, - "git-precommit-checks": { - "rules": [ - { - "message": "You've got leftover conflict markers", - "regex": "/^[<>|=]{4,}/m" - }, - { - "filter": "(^package\\.json|\\.git-precommit-checks.json)$", - "message": "You have unfinished devs", - "nonBlocking": "true", - "regex": "(?:FIXME|TODO)" - } - ] - }, - "lint-staged": { - "!(*package-lock).{js,jsx,ts,tsx,md,html,css,vue,yaml,yml,json}": [ - "npx --yes prettier --write" - ], - "!(*schema|package-lock).json": [ - "json-normalize -c -w -a -i -t 4 -f local -l true" - ], - "!(templates/**/*|.agents/**|**/package-lock|package-lock).{js,jsx,ts,tsx,md,html,css,vue,yaml,yml,json}": [ - "npx --yes prettier --write" - ], - "composer.json": [ - "composer validate --no-check-all --no-check-lock --no-check-publish" - ], - "package.json": [ - "npm pkg fix" + }, + "license": "MIT", + "lint-staged": { + "!(*package-lock).{js,jsx,ts,tsx,md,html,css,vue,yaml,yml,json}": [ + "npx --yes prettier --write" + ], + "!(*schema|package-lock).json": [ + "json-normalize -c -w -a -i -t 4 -f local -l true" + ], + "!(templates/**/*|.agents/**|**/package-lock|package-lock).{js,jsx,ts,tsx,md,html,css,vue,yaml,yml,json}": [ + "npx --yes prettier --write" + ], + "composer.json": [ + "composer validate --no-check-all --no-check-lock --no-check-publish" + ], + "package.json": [ + "npm pkg fix" + ] + }, + "optionalDependencies": { + "@tomgrv/devcontainer-features-common-utils": ">=5" + }, + "peerDependencies": { + "@commitlint/cli": "*", + "@commitlint/config-conventional": "*", + "@commitlint/core": "*", + "@commitlint/cz-commitlint": "*", + "commitizen": "*", + "git-precommit-checks": "^3", + "prettier": "^3", + "prettier-plugin-sh": "*" + }, + "prettier": { + "insertPragma": true, + "overrides": [ + { + "files": "*.yml", + "options": { + "tabWidth": 2, + "useTabs": false + } + }, + { + "files": "*.json", + "options": { + "tabWidth": 4, + "useTabs": false + } + } + ], + "plugins": [ + "prettier-plugin-sh" + ], + "semi": false, + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "es5" + }, + "private": true, + "scripts": { + "lint": "npx --yes lint-staged -p false", + "prepare": "sh .husky/install.sh || echo 'Husky not installed'", + "test": "bats --recursive .", + "update": "npm-check-updates -i -u", + "update-all": "npm run update -w --root" + }, + "validate-branch-name": { + "errorMsg": "Please use a branch name that follows the pattern: (main|develop) or (feature|bugfix|release|hotfix|support|copilot|claude)/.\nUse git push --no-verify to bypass this check if necessary.", + "pattern": "^(main|develop){1}$|^(feature|bugfix|release|hotfix|support|copilot|claude)/.+$" + }, + "workspaces": [ + "zz", + "zz-use", + "zz-colors", + "zz-log", + "zz-args", + "zz-prompt", + "zz-menu", + "zz-load", + "zz-bindir", + "zz-dispatch", + "zz-npx", + "zz-persist", + "zz-call", + "zz-install", + "zz-require", + "zz-fetch", + "zz-vars", + "zz-setup", + "zz-step", + "zz-interactive", + "zz-progress", + "json", + "json-load", + "json-validate", + "json-normalize", + "json-merge", + "json-render", + "yaml", + "yaml-merge", + "yaml-render", + "kube", + "kube-progress", + "kube-diagnose", + "kube-teardown", + "kube-service-port", + "kube-require", + "kube-rollout", + "kube-apply", + "systemd-service", + "helm-repo", + "helm-install", + "feature", + "feature-context", + "edit-script", + "distribute-utils", + "feature-install", + "feature-configure", + "git-align", + "git-autorebase", + "git-co", + "git-degit", + "git-fix", + "git-fix-author", + "git-fix-base", + "git-fix-blanks", + "git-fix-children", + "git-fix-date", + "git-fix-del", + "git-fix-emoji", + "git-hook-commitmsg", + "git-hook-installplugins", + "git-hook-postcheckout", + "git-hook-postmerge", + "git-hook-precommit", + "git-hook-preparecommitmsg", + "git-hook-prepush", + "git-fix-last", + "git-fix-lock", + "git-fix-message", + "git-fix-mode", + "git-fix-orphans", + "git-fix-privacy", + "git-fix-prune", + "git-fix-rights", + "git-fix-secrets", + "git-fix-up", + "git-forall", + "git-getcommit", + "git-integrate", + "git-pick", + "git-release", + "git-release-alpha", + "git-release-beta", + "git-release-hotfix", + "git-release-prod", + "git-unset", + "ws-impacted", + "ws-list", + "gv", + "bump-tag", + "bump-changelog", + "bump-version", + "ws-test", + "php-list-changed" ] - }, - "validate-branch-name": { - "errorMsg": "Please use a branch name that follows the pattern: (main|develop) or (feature|bugfix|release|hotfix|support|copilot|claude)/.\nUse git push --no-verify to bypass this check if necessary.", - "pattern": "^(main|develop){1}$|^(feature|bugfix|release|hotfix|support|copilot|claude)/.+$" - } } diff --git a/skills-lock.json b/skills-lock.json index 9d01b04..4e9b742 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -85,6 +85,12 @@ "skillPath": "skills/caveman-stats/SKILL.md", "computedHash": "cc9fcd21f2a30cff5596b3cbb197fe0f4b1b30b58084a864350853c217ad301a" }, + "harness": { + "source": "tomgrv/devcontainer-features", + "sourceType": "github", + "skillPath": ".agents/skills/harness/SKILL.md", + "computedHash": "611817a4992972fb5269521a949697c02d0f3641a23cb9052d75bdc558da08b3" + }, "investigate-first": { "source": "JuliusBrussee/caveman", "sourceType": "github",