diff --git a/.agents/skills/bold-build-status/SKILL.md b/.agents/skills/bold-build-status/SKILL.md new file mode 100644 index 0000000..338cfde --- /dev/null +++ b/.agents/skills/bold-build-status/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-build-status +description: "Bold: Report task/gate/artifact status for the active feature." +--- + +# bold-build-status + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/build/status.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/build/status.md` +3. `.bold/commands/build/status.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-gate-status.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-build/SKILL.md b/.agents/skills/bold-build/SKILL.md new file mode 100644 index 0000000..fb5cfae --- /dev/null +++ b/.agents/skills/bold-build/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-build +description: "Bold: Apply the ratified tier's gate set, execute, then keep artifacts continuously in sync." +--- + +# bold-build + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/build/default.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/build/default.md` +3. `.bold/commands/build/default.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-gate-status.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-install/SKILL.md b/.agents/skills/bold-install/SKILL.md new file mode 100644 index 0000000..e99eb50 --- /dev/null +++ b/.agents/skills/bold-install/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-install +description: "Bold: Fetch, preview, and sync Bold's payload into this repo (fresh install, upgrade, or already-current), then hand off to bold.plan init." +--- + +# bold-install + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/install/default.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/install/default.md` +3. `.bold/commands/install/default.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector (no collector for this subcommand — it reads only what's already on disk) before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-plan-analyze/SKILL.md b/.agents/skills/bold-plan-analyze/SKILL.md new file mode 100644 index 0000000..b4d1d38 --- /dev/null +++ b/.agents/skills/bold-plan-analyze/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-plan-analyze +description: "Bold: Check the active feature's spec for internal consistency, duplication, ambiguity, and requirement/task coverage." +--- + +# bold-plan-analyze + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/plan/analyze.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/plan/analyze.md` +3. `.bold/commands/plan/analyze.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-triage-context.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-plan-checklist/SKILL.md b/.agents/skills/bold-plan-checklist/SKILL.md new file mode 100644 index 0000000..95ab85a --- /dev/null +++ b/.agents/skills/bold-plan-checklist/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-plan-checklist +description: "Bold: Generate or verify a checklist that tests the quality of the spec's requirements, not the implementation." +--- + +# bold-plan-checklist + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/plan/checklist.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/plan/checklist.md` +3. `.bold/commands/plan/checklist.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector (no collector for this subcommand — it reads only what's already on disk) before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-plan-clarify/SKILL.md b/.agents/skills/bold-plan-clarify/SKILL.md new file mode 100644 index 0000000..930afd6 --- /dev/null +++ b/.agents/skills/bold-plan-clarify/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-plan-clarify +description: "Bold: Re-run the clarification pass on the active feature's spec." +--- + +# bold-plan-clarify + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/plan/clarify.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/plan/clarify.md` +3. `.bold/commands/plan/clarify.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector (no collector for this subcommand — it reads only what's already on disk) before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-plan-critic/SKILL.md b/.agents/skills/bold-plan-critic/SKILL.md new file mode 100644 index 0000000..417009d --- /dev/null +++ b/.agents/skills/bold-plan-critic/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-plan-critic +description: "Bold: Adversarial risk critique of the active feature's spec — what will fail in production, gated at Feature tier." +--- + +# bold-plan-critic + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/plan/critic.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/plan/critic.md` +3. `.bold/commands/plan/critic.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-triage-context.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-plan-discover/SKILL.md b/.agents/skills/bold-plan-discover/SKILL.md new file mode 100644 index 0000000..08f559d --- /dev/null +++ b/.agents/skills/bold-plan-discover/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-plan-discover +description: "Bold: Read-only brownfield archaeology — mine configs and history, infer conventions, play back findings for ratification." +--- + +# bold-plan-discover + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/plan/discover.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/plan/discover.md` +3. `.bold/commands/plan/discover.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-brownfield-signals.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-plan-init/SKILL.md b/.agents/skills/bold-plan-init/SKILL.md new file mode 100644 index 0000000..4839b95 --- /dev/null +++ b/.agents/skills/bold-plan-init/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-plan-init +description: "Bold: Detect repo entry path (migrate / discover / greenfield) and route accordingly." +--- + +# bold-plan-init + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/plan/init.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/plan/init.md` +3. `.bold/commands/plan/init.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-entry-path-context.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-plan-tasks/SKILL.md b/.agents/skills/bold-plan-tasks/SKILL.md new file mode 100644 index 0000000..b90342a --- /dev/null +++ b/.agents/skills/bold-plan-tasks/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-plan-tasks +description: "Bold: Regenerate the task breakdown for the active feature's ratified spec, or merge in gate remediation on a re-run." +--- + +# bold-plan-tasks + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/plan/tasks.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/plan/tasks.md` +3. `.bold/commands/plan/tasks.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector (no collector for this subcommand — it reads only what's already on disk) before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-plan/SKILL.md b/.agents/skills/bold-plan/SKILL.md new file mode 100644 index 0000000..35d0195 --- /dev/null +++ b/.agents/skills/bold-plan/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-plan +description: "Bold: Triage the request, get the tier ratified, and produce the tier-appropriate planning artifact." +--- + +# bold-plan + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/plan/default.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/plan/default.md` +3. `.bold/commands/plan/default.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-triage-context.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-ship-address/SKILL.md b/.agents/skills/bold-ship-address/SKILL.md new file mode 100644 index 0000000..91e87f9 --- /dev/null +++ b/.agents/skills/bold-ship-address/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-ship-address +description: "Bold: Respond to review findings on the active feature's open PR." +--- + +# bold-ship-address + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/ship/address.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/ship/address.md` +3. `.bold/commands/ship/address.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-pr-review-context.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-ship-harvest/SKILL.md b/.agents/skills/bold-ship-harvest/SKILL.md new file mode 100644 index 0000000..f9f7292 --- /dev/null +++ b/.agents/skills/bold-ship-harvest/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-ship-harvest +description: "Bold: Classify feature artifacts — promote durable knowledge, archive work products, reconcile stale docs." +--- + +# bold-ship-harvest + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/ship/harvest.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/ship/harvest.md` +3. `.bold/commands/ship/harvest.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-harvest-inventory.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-ship-review/SKILL.md b/.agents/skills/bold-ship-review/SKILL.md new file mode 100644 index 0000000..dc04c52 --- /dev/null +++ b/.agents/skills/bold-ship-review/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-ship-review +description: "Bold: Review pass over the active feature's changes before or after a PR is opened." +--- + +# bold-ship-review + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/ship/review.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/ship/review.md` +3. `.bold/commands/ship/review.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-ship-context.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.agents/skills/bold-ship/SKILL.md b/.agents/skills/bold-ship/SKILL.md new file mode 100644 index 0000000..2c2d2a0 --- /dev/null +++ b/.agents/skills/bold-ship/SKILL.md @@ -0,0 +1,16 @@ +--- +name: bold-ship +description: "Bold: Draft a PR (or finalize the deliverable, for non-code domains) for the active feature." +--- + +# bold-ship + +Resolve the effective prompt via Bold's three-tier resolution (user > team > source — +see `.bold/scripts/*/bold-which.*`): + +1. `.bold-user/{your-git-user-name}/commands/ship/default.md` (run `bold-which` to resolve the exact slug and path) +2. `bold-docs/overrides/commands/ship/default.md` +3. `.bold/commands/ship/default.md` + +Read whichever resolves first and follow it as your instructions for this invocation, +including running its declared collector `.bold/scripts/{bash,powershell}/collect-ship-context.{sh,ps1}` before anything else. \ No newline at end of file diff --git a/.documentation/copilot/harvest-2026-04-19.md b/.archive/copilot/harvest-2026-04-19.md similarity index 100% rename from .documentation/copilot/harvest-2026-04-19.md rename to .archive/copilot/harvest-2026-04-19.md diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/context.json b/.archive/devspark-runs/run_20260419T133325Z_44d25f/context.json similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/context.json rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/context.json diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/events.jsonl b/.archive/devspark-runs/run_20260419T133325Z_44d25f/events.jsonl similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/events.jsonl rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/events.jsonl diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/result.json b/.archive/devspark-runs/run_20260419T133325Z_44d25f/result.json similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/result.json rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/result.json diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/spec.resolved.yaml b/.archive/devspark-runs/run_20260419T133325Z_44d25f/spec.resolved.yaml similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/spec.resolved.yaml rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/spec.resolved.yaml diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/analyze/output.txt b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/analyze/output.txt similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/analyze/output.txt rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/analyze/output.txt diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/analyze/prompt.md b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/analyze/prompt.md similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/analyze/prompt.md rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/analyze/prompt.md diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/clarify/output.txt b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/clarify/output.txt similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/clarify/output.txt rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/clarify/output.txt diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/clarify/prompt.md b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/clarify/prompt.md similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/clarify/prompt.md rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/clarify/prompt.md diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/critic/output.txt b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/critic/output.txt similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/critic/output.txt rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/critic/output.txt diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/critic/prompt.md b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/critic/prompt.md similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/critic/prompt.md rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/critic/prompt.md diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/critic/stdout.txt b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/critic/stdout.txt similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/critic/stdout.txt rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/critic/stdout.txt diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/implement/output.txt b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/implement/output.txt similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/implement/output.txt rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/implement/output.txt diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/implement/prompt.md b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/implement/prompt.md similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/implement/prompt.md rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/implement/prompt.md diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/implement/stdout.txt b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/implement/stdout.txt similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/implement/stdout.txt rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/implement/stdout.txt diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/plan/output.txt b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/plan/output.txt similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/plan/output.txt rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/plan/output.txt diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/plan/prompt.md b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/plan/prompt.md similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/plan/prompt.md rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/plan/prompt.md diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/specify/output.txt b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/specify/output.txt similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/specify/output.txt rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/specify/output.txt diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/specify/prompt.md b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/specify/prompt.md similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/specify/prompt.md rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/specify/prompt.md diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/tasks/output.txt b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/tasks/output.txt similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/tasks/output.txt rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/tasks/output.txt diff --git a/.documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/tasks/prompt.md b/.archive/devspark-runs/run_20260419T133325Z_44d25f/steps/tasks/prompt.md similarity index 100% rename from .documentation/devspark/runs/run_20260419T133325Z_44d25f/steps/tasks/prompt.md rename to .archive/devspark-runs/run_20260419T133325Z_44d25f/steps/tasks/prompt.md diff --git a/git-spark-lifecycle.harness.yaml b/.archive/git-spark-lifecycle.harness.yaml similarity index 100% rename from git-spark-lifecycle.harness.yaml rename to .archive/git-spark-lifecycle.harness.yaml diff --git a/.archive/index.md b/.archive/index.md new file mode 100644 index 0000000..723bd36 --- /dev/null +++ b/.archive/index.md @@ -0,0 +1,5 @@ +# Archive Index + +| Date | Feature | Archived | Promoted to system/ | +|---|---|---|---| +| 2026-07-09 | devspark-to-bold-migration | `copilot/harvest-2026-04-19.md` (DevSpark harvest report for `001-dependency-hygiene-hardening`), `devspark-runs/run_20260419T133325Z_44d25f/` (DevSpark harness execution trace for the same feature), `repo-story/` (3 generated repo-narrative reports), `git-spark-lifecycle.harness.yaml` (DevSpark harness spec, single-feature-scoped, feature already shipped and harvested) | `bold-docs/backbone.md`, `bold-docs/project.json`, `bold-docs/system/tech-stack.md`, `bold-docs/system/adr.md`, `bold-docs/system/constitution-history.md` | diff --git a/.documentation/repo-story/history.json b/.archive/repo-story/history.json similarity index 100% rename from .documentation/repo-story/history.json rename to .archive/repo-story/history.json diff --git a/.documentation/repo-story/repo-story-2026-04-03.md b/.archive/repo-story/repo-story-2026-04-03.md similarity index 100% rename from .documentation/repo-story/repo-story-2026-04-03.md rename to .archive/repo-story/repo-story-2026-04-03.md diff --git a/.documentation/repo-story/repo-story-2026-04-06.md b/.archive/repo-story/repo-story-2026-04-06.md similarity index 100% rename from .documentation/repo-story/repo-story-2026-04-06.md rename to .archive/repo-story/repo-story-2026-04-06.md diff --git a/.bold-user/mark-hazleton/README.md b/.bold-user/mark-hazleton/README.md new file mode 100644 index 0000000..c23875b --- /dev/null +++ b/.bold-user/mark-hazleton/README.md @@ -0,0 +1,3 @@ +# mark-hazleton + +Personal, committed overrides for this contributor — three-tier resolution checks this directory first (user > team > source). Whole-file replacement only; see bold-tool-plan.md §5.3. diff --git a/.bold/commands/FORMAT.md b/.bold/commands/FORMAT.md new file mode 100644 index 0000000..b9d46fc --- /dev/null +++ b/.bold/commands/FORMAT.md @@ -0,0 +1,54 @@ +# Neutral Prompt Format + +The schema every file under `source/commands/` follows. Read this before writing a new command or subcommand. + +## Layout + +One file per subcommand: `source/commands/{plan,build,ship}/{subcommand}.md`. The bare command (`bold.plan` with no subcommand) lives at `{command}/default.md`. See `bold-docs/features/0001-neutral-prompt-format/spec.md` for why (whole-file override replacement per plan §5.3 means subcommand-level granularity, not one file per top-level command). + +`source/commands/install/default.md` (`bold.install`) is a deliberate fourth entry, not a widening of the three frozen methodology verbs (§18 risk table) — it's pre-methodology bootstrap: the payload-sync step that has to exist before `plan`/`build`/`ship` are on disk to run at all. It reuses the same `bold.` frontmatter shape purely so the existing slug/adapter-generation machinery (`generate-adapters.{sh,ps1}`, `generate-site.{sh,ps1}`) needs no bespoke handling — treat the `command: bold.install` value as tooling convenience, not a claim that installation is a fourth thing Bold's methodology does. + +## File shape + +```markdown +--- +command: bold.plan +subcommand: critic +description: One line — what this subcommand does. +collector: collect-triage-context # or: none, with a comment explaining why +args: + - name: --strict + description: What the flag changes. + values: [optional, enum, list] # omit if free-form +--- + +# bold.plan critic + +Agent-neutral markdown body. +``` + +### Frontmatter fields + +| Field | Required | Notes | +|---|---|---| +| `command` | yes | `bold.plan` / `bold.build` / `bold.ship` | +| `subcommand` | yes | `default` for the bare command | +| `description` | yes | One line; this is what an adapter generator turns into the host's command description | +| `collector` | yes | The collector script's base name (no extension — both `source/scripts/bash/{name}.sh` and `source/scripts/powershell/{name}.ps1` must exist), or the literal `none` if the subcommand's only input is a file already on disk. Never omit the field — an absent collector should be a stated decision, not a gap | +| `args` | yes (may be empty `[]`) | Flags the subcommand accepts, each with a one-line `description` | + +### Body + +- Markdown, no host-specific syntax — no Claude Code frontmatter keys, no Copilot directives, no Cursor rule syntax. Anything host-specific belongs in `adapters/`, not here. +- Open with a short statement of purpose, then boundaries (success criteria, halt conditions) — not a scripted step-by-step procedure the model would handle sensibly on its own (backbone principle: guardrails bound the space, never script the path). +- **A `## Definition of Done` section is required.** Plan §1 is explicit: "no prompt without success criteria either... Prompts are components with contracts, not text that gets eyeballed and shipped." One to three sentences stating what "done" concretely means for this subcommand — specific enough that a different agent run against the same input would recognize the same stopping point. Not a restatement of the body's steps; a test you could check the output against afterward. +- The Product Owner TL;DR requirement (plan §8) lives in the body wherever this subcommand instructs the agent to produce a durable artifact — it's part of what the methodology produces, not adapter-injected boilerplate. +- Reference collector output fields by name (e.g. `active_features`, `backbone_principles`) rather than re-describing how to gather them — the collector already did that. + +## Collector convention + +Collector scripts emit single-line JSON to stdout, one pair per platform: `source/scripts/bash/{name}.sh` and `source/scripts/powershell/{name}.ps1`, verified to produce equivalent output. Shared parsing logic (feature inventory, backbone principle status, system-doc inventory) lives in `source/scripts/bash/lib/common.sh` and `source/scripts/powershell/lib/Common.ps1` — reach for those before duplicating a scan across a third script. + +## Worked examples + +All 14 files under `source/commands/{plan,build,ship}/` follow this schema and are the reference set — `plan/default.md` (the triage flow) is the most heavily annotated. `install/default.md` also follows it but is the one legitimate exception to the collector rule above — see its own file for why. diff --git a/.bold/commands/WAIVERS.md b/.bold/commands/WAIVERS.md new file mode 100644 index 0000000..37c4a57 --- /dev/null +++ b/.bold/commands/WAIVERS.md @@ -0,0 +1,26 @@ +# Waiver Format + +How a deliberate, ratified exception to an `enforced` backbone principle gets recorded. Read this before writing or parsing a waiver. + +## Where it lives + +Waivers are recorded in the work item's own artifact — the feature's `spec.md` (or the mini-spec / patch log entry for lower tiers) — under a `## Waivers` heading. They travel with the work, not in a separate ledger, so they're visible wherever the feature is visible. + +## Format + +One line per waiver, under `## Waivers`: + +``` +- Waiver: principle= reason="" ratified_by="" date= +``` + +- `principle` — the backbone principle's number (matches its position in `backbone.md`'s `## Principles` list) +- `reason` — why the violation is accepted, not what was violated (the principle number already says that) +- `ratified_by` — a human accepted this; the tool never waives on its own (backbone principle: the tool proposes, the human ratifies) +- `date` — when it was ratified + +## Who writes and reads it + +- `bold.plan critic` and `bold.build` (default) write a waiver when a human accepts a flagged risk instead of fixing it. +- `bold.ship` (default) surfaces every open waiver in the PR body — a waiver is a visible tradeoff, not a way to make a gate quietly disappear. +- Collectors parse `- Waiver: ...` lines from each feature's `spec.md` into `active_features[].waivers` (see `source/scripts/{bash,powershell}/lib/`). diff --git a/.bold/commands/build/default.md b/.bold/commands/build/default.md new file mode 100644 index 0000000..a09b1ed --- /dev/null +++ b/.bold/commands/build/default.md @@ -0,0 +1,54 @@ +--- +command: bold.build +subcommand: default +description: Apply the ratified tier's gate set, execute, then keep artifacts continuously in sync. +collector: collect-gate-status +args: + - name: --strict + description: Apply the full Feature-tier gate set even at Patch or Quick. +--- + +# bold.build + +## Definition of Done + +Done when every task is `[X]`, the spec's `**Status**` reflects that (`In Progress` or `Complete`), and the ratified tier's gate set has passed — or every gap it didn't clear has a recorded waiver. A build that stops with tasks still `[ ]` and no halt/escalation reported isn't done, it's abandoned. + +## Before you begin + +Read the collector output. Find the active feature's ratified tier in `active_features`. If none is ratified, halt and route to `bold.plan` — build never guesses a tier. + +## Apply the tier's gate set + +| Tier | Gate | +|---|---| +| Patch | Tests must pass | +| Quick | Backbone check only | +| Feature | Full pre-flight: `bold.plan analyze`, `bold.plan critic`, `bold.plan checklist`, and coverage of every `enforced` backbone principle | + +`--strict` applies the Feature gate set regardless of ratified tier. + +Check `backbone_principles` from the collector: an `enforced` principle blocks the build on violation; an `adopting` principle flags but doesn't halt — new code must comply, legacy is grandfathered. + +## Execute + +Implement the ratified spec, mini-spec, or patch entry — the task list's IDs and `[P]` markers already carry the ordering and parallelism, so follow those rather than re-deriving an order. Keep `bold-docs/system/` in sync as you go — sync is continuous, not a step tacked on at the end: + +If the collector's `stale_references` is non-empty, note it once at a checkpoint: which `system/` docs reference paths that no longer exist. Surface it, don't fix it here — that's `bold.ship harvest`'s job (§13), and this build may be the very change that made the reference stale. + +- Mark each task `[X]` the moment it's done — never batch checkbox updates for later. A partially-done task stays `[ ]` with a short inline note, not half-checked. +- Flip the spec's `**Status**` the first time a task completes: `Draft` → `In Progress`. Flip it to `Complete` only once every task is `[X]`. +- If a task's completion resolves an open item in `gates/critic.md` or `gates/analyze.md`, mark that finding `resolved` in the gate file with a one-line outcome — this is what lets a re-run of `critic`/`analyze` converge instead of re-reporting the same thing. +- If implementation diverges from the spec (a different approach than what was written), update the spec inline rather than silently drifting from it. + +## Report progress at checkpoints, not per task + +In chat, report one line per phase/checkpoint, not one line per task — the task file itself carries full detail. Halt and report immediately if a non-parallel task fails; don't wait for a checkpoint to surface a failure. + +## Escalate if the work outgrows its tier + +If mid-build the change turns out to need more than its ratified tier allows — the "patch" is actually a data-model change — halt immediately. Report what you found and route back to `bold.plan` at the higher tier. Carry forward whatever work already exists; nothing already produced is discarded. Never silently continue under-scoped. + +## Waivers + +If an `enforced` principle is violated deliberately, add a `- Waiver: ...` line under the spec's `## Waivers` heading (format: `source/commands/WAIVERS.md`). Don't route around the gate silently. diff --git a/.bold/commands/build/status.md b/.bold/commands/build/status.md new file mode 100644 index 0000000..e47d984 --- /dev/null +++ b/.bold/commands/build/status.md @@ -0,0 +1,27 @@ +--- +command: bold.build +subcommand: status +description: Report task/gate/artifact status for the active feature. +collector: collect-gate-status +args: [] +--- + +# bold.build status + +Reuses `collect-gate-status` — this subcommand reports the same facts `bold.build` (default) uses to decide gates, formatted as a status report instead of acted on. + +## Definition of Done + +Done when all four report fields (tier, gates, backbone, repo state) are stated from the collector's current output — not when something changed, since nothing here should change anything. + +## Report + +For the active feature, state: +- **Tier** — the ratified tier from `active_features`, and whether it still looks right given what's been built so far +- **Gates** — which apply at this tier, and which have and haven't been satisfied yet +- **Backbone** — any `enforced` principle violations (blocking) or `adopting` flags (non-blocking) found in `backbone_principles` +- **Repo state** — `has_uncommitted_changes`, and whether test config was detected in `test_config_present`. A build claiming "tests pass" with no detected test config is itself worth flagging + +## Boundary + +This is a read-only report. Never modify the build, gates, or spec from `status` — that's `bold.build` (default)'s job. diff --git a/.bold/commands/install/default.md b/.bold/commands/install/default.md new file mode 100644 index 0000000..73a15d5 --- /dev/null +++ b/.bold/commands/install/default.md @@ -0,0 +1,51 @@ +--- +command: bold.install +subcommand: default +description: Fetch, preview, and sync Bold's payload into this repo (fresh install, upgrade, or already-current), then hand off to bold.plan init. +collector: none +args: + - name: --source + description: Manifest source — a published URL, or a local directory (for testing against a Bold checkout). + - name: --target + description: Directory the source-tier payload installs into. Defaults to .bold. +--- + +# bold install + +The one Bold prompt meant to be pasted straight into an agent, not invoked as an already-installed command — every other Bold command resolves itself through `.bold/commands/` via three-tier resolution, but on a first run nothing has synced that yet. Read this file in full before acting on it; it fetches data and writes files, and at no point pipes a remote script straight into execution. + +No collector: every other command's first step is running a deterministic collector script under `.bold/scripts/`, but that script doesn't exist until this command has already run once. The state-detection steps below are written out explicitly instead, for the same reason collectors exist elsewhere — so state is measured, not judged — just inlined here because there's nowhere else for it to live yet. + +## Host notes + +This file stays host-neutral on purpose (per `FORMAT.md`: host-specific syntax belongs in adapters, not here) — the same line gets pasted into any agent. Before continuing, fetch `{source}/commands/install/notes/{host}.md` for whichever of `claude-code`, `codex`, or `copilot` you are (you already know which; no detection needed) — if it exists, follow its adjustments; if it 404s or none applies, proceed with the steps below exactly as written. The `Why this shape` section right below is itself host-neutral and always applies regardless. + +## Why this shape + +A single `curl | bash` / `irm | iex` line executes a remote script sight-unseen, with the user's full privileges, from a domain most people have no way to vouch for. That's indistinguishable from a malware-delivery pattern regardless of what the script actually contains. This command replaces that with three properties: nothing executes before a human has seen it; the actual file-sync logic stays in one deterministic, already-tested script rather than being re-derived as prose; and every write happens through the agent's own tool calls, which a human can watch or interrupt one at a time. + +## Detect state + +1. Check whether `.bold/version.json` exists. + - Missing → **Install** (this repo has never synced Bold). +2. Fetch `{source}/latest.json` — a manifest listing file paths, SHA-256 hashes, and sizes; it is data, not code. + - Compare its `version` field against `.bold/version.json`'s `installed_version`, if step 1 found one. + - Equal → **Already current.** Report the installed version and stop; don't re-sync without being asked to. + - Different, or step 1 found nothing → **Install / Upgrade** (same procedure either way — the sync script is idempotent by design: unchanged files are skipped by hash). +3. Note, but do not act on, whether `.devspark/` or `.documentation/` exist at the repo root. That distinction belongs to `bold.plan init`'s Migrate detection (see Hand off below) — installing the Bold payload is identical either way. + +## Install / Upgrade + +1. Fetch `{source}/install.ps1` (Windows) or `{source}/install.sh` (macOS/Linux) as plain text — a request for a file's contents, nothing executes yet. +2. Summarize what it does in a few sentences (sync loop: skip files whose hash already matches, back up a locally-modified file before overwriting it, delete orphaned files scoped to what Bold owns, scaffold `.bold-user/{git-user-name}/`) — or show the full text if asked. **Wait for explicit go-ahead before continuing.** This is the one ratification point in this command; a script that looks the same as last time still gets shown, not assumed. +3. Write the fetched script, unmodified, to a local temp file. +4. Run it locally: `install.ps1 -Source {source} -Target {target}` (or the bash equivalent). This is the only step that executes anything, and only after step 2's approval. +5. Delete the temp file. + +## Hand off + +Once `.bold/version.json` reflects the current manifest version, `.bold/commands/plan/init.md` is resolvable through the normal three-tier chain like any other Bold command. Read it and continue directly into it *yourself, in this same conversation, right now* — greenfield/discover/migrate routing already lives there (including the DevSpark detection noted in step 3 above); this command's job ends at "the payload is on disk and current." If the human asked only to install or upgrade, not to proceed further, stop here and report the installed version and paths instead — but say so as plain next-step language ("tell me to continue and I'll pick up `bold.plan init`"), never as a slash command. The `.claude/skills/`, `.agents/skills/`, and `.github/prompts/` shims this run just wrote are not available as invocable commands in *this* session — most hosts index skills at session start, before this install ever ran — so telling a human to type `/bold-plan-init` (or any `/bold-*` command) right now will fail with an unknown-command error even though the file is sitting right there on disk. Reading the next command file directly, the same way this file itself was read, needs no slash command and works immediately; the generated shims only become real slash commands after the human starts a fresh session. + +## Definition of Done + +Done when `.bold/version.json`'s version matches the fetched manifest, every file the manifest lists is present with a matching hash, and either the human explicitly deferred further setup (reported and stopped) or `bold.plan init` has taken over. Not done if any file was written before the human approved the install/upgrade script in step 2. diff --git a/.bold/commands/install/notes/claude-code.md b/.bold/commands/install/notes/claude-code.md new file mode 100644 index 0000000..5a9acf3 --- /dev/null +++ b/.bold/commands/install/notes/claude-code.md @@ -0,0 +1,5 @@ +# Claude Code — install notes + +No adjustment needed to the base steps in `install/default.md`. Claude Code's own `Read`/`WebFetch` tools and its per-call permission system already give the fetch-then-preview-then-write flow those steps describe; there's nothing host-specific to substitute. + +One path detail: skill files this manifest installs after sync live under `.claude/skills/{slug}/SKILL.md`, one directory per command. diff --git a/.bold/commands/install/notes/codex.md b/.bold/commands/install/notes/codex.md new file mode 100644 index 0000000..d2b56d0 --- /dev/null +++ b/.bold/commands/install/notes/codex.md @@ -0,0 +1,5 @@ +# Codex — install notes + +No adjustment needed to the base steps in `install/default.md`. Use whichever of your own tools reads a URL as plain text and writes a local file — no Codex-specific fetch directive is assumed here, because none is confirmed to exist across Codex clients; plain natural-language "fetch and read" instructions work. + +One path detail: skill files this manifest installs after sync live under `.agents/skills/{slug}/SKILL.md`, mirroring Claude Code's `SKILL.md` format. diff --git a/.bold/commands/install/notes/copilot.md b/.bold/commands/install/notes/copilot.md new file mode 100644 index 0000000..0cdb2ea --- /dev/null +++ b/.bold/commands/install/notes/copilot.md @@ -0,0 +1,5 @@ +# GitHub Copilot — install notes + +If your Copilot surface supports the `#fetch` chat variable, prefer it over a generic "read this URL" instruction for every fetch step in `install/default.md` (`#fetch {url}`) — it's the more reliable way to pull raw URL content into context on Copilot specifically. If `#fetch` isn't available on your surface, the base steps work unchanged; nothing else differs. + +One path detail: prompt files this manifest installs after sync live under `.github/prompts/{slug}.prompt.md`. diff --git a/.bold/commands/plan/analyze.md b/.bold/commands/plan/analyze.md new file mode 100644 index 0000000..8d3d326 --- /dev/null +++ b/.bold/commands/plan/analyze.md @@ -0,0 +1,34 @@ +--- +command: bold.plan +subcommand: analyze +description: Check the active feature's spec for internal consistency, duplication, ambiguity, and requirement/task coverage. +collector: collect-triage-context +args: [] +--- + +# bold.plan analyze + +Reuses the triage collector — the same system-doc inventory, backbone principle list, and genome that triage used to decide the tier are the ground truth to check the spec against. + +## Definition of Done + +Done when the report is saved to `gates/analyze.md` and every check below has actually been run against the current spec — not when a plausible-sounding set of findings has been produced. A report that says "no findings" after genuinely checking all six categories is done; one that skips a category isn't. + +## Not `bold.plan critic` + +Analyze and critic are deliberately non-overlapping gates: analyze is a **neutral validator** asking "are the artifacts aligned?"; critic is an **adversarial skeptic** asking "what will fail in production?" When a finding could land in either, prefer the one that owns it below and cross-reference rather than duplicate. + +## Check + +- **Duplication** — near-duplicate Acceptance Criteria; mark the lower-quality phrasing for consolidation, don't just flag both. +- **Ambiguity (wording only)** — vague adjectives ("fast," "secure," "robust") with no measurable criteria, unresolved placeholders (TODO, TBD, ``). Whether a stated target is *achievable* is critic's job, not this one's — this pass only checks whether it's worded precisely enough to act on. +- **Underspecification** — an Acceptance Criterion with a verb but no measurable outcome; a task (once `bold.plan tasks` has run) referencing a file or component the spec never defined. +- **Coverage gaps** — every Acceptance Criterion should trace to something in Intent, and (once tasks exist) to at least one task. Zero-coverage in either direction is a finding. Whether the spec is *missing* an entire category of task (observability, rollback) that nothing called for is critic's job, not this one's. +- **Backbone consistency** — does the spec implicate a principle (per the collector's `backbone_principles`) that it doesn't acknowledge, or conflict with one that's `enforced`? +- **System consistency** — does the spec assume behavior that a `system/` doc (per the collector's `system_docs`) already documents differently? + +## Report + +Open with the Product Owner TL;DR (per §8 — analysis output is a generated artifact like any other). Then list findings as a flat set: duplication, ambiguity, underspecification, coverage gap, or backbone/system conflict — each with the spec line and the conflicting source. No finding is auto-fixed; `analyze` reports, it doesn't edit. The human decides whether to fix the spec, update `system/`, or waive. + +Save the report to the feature's own `gates/analyze.md`, replacing the prior run rather than appending — `bold.plan tasks` reads it to generate remediation tasks, and a stale duplicate would confuse that merge. diff --git a/.bold/commands/plan/checklist.md b/.bold/commands/plan/checklist.md new file mode 100644 index 0000000..c56494f --- /dev/null +++ b/.bold/commands/plan/checklist.md @@ -0,0 +1,54 @@ +--- +command: bold.plan +subcommand: checklist +description: Generate or verify a checklist that tests the quality of the spec's requirements, not the implementation. +collector: none +args: + - name: --verify + description: Check off items against the current spec/build state instead of generating a fresh checklist. +--- + +# bold.plan checklist + +No collector: the checklist's source of truth is the active feature's own spec (and its starter, if one applies) — not repo-wide facts. + +## Definition of Done + +Generate: done when every item is numbered, categorized, and traces to a spec section or a `[Gap]`, and none of them test implementation behavior (see below). Verify: done when every existing item has been marked done, not done, or not applicable against current state — none left unjudged. + +## Unit tests for English + +A checklist item tests whether a **requirement is well-written** — complete, unambiguous, consistent, measurable — not whether the implementation works. If the spec is code written in English, this checklist is its test suite. + +**Wrong** (tests the implementation): +- "Verify the button navigates to the home page" +- "Test that the API returns 200" +- "Confirm hover states work on desktop" + +**Right** (tests the requirement): +- "Is the button's destination explicitly specified? [Completeness]" +- "Are error-response formats specified for every failure mode? [Completeness]" +- "Is 'fast' quantified with a specific latency target? [Clarity]" +- "Are hover-state requirements defined consistently across every interactive element? [Consistency]" + +If an item would start with "Verify," "Test," "Confirm," or "Check" followed by a behavior — or names a click, render, load, or execute — it's testing the wrong thing. Rewrite it as a question about whether the requirement exists and is precise enough to act on. + +## Generate (default) + +If the feature has a starter (`project.json`'s `composition.starter`), pull its checklist template for domain-relevant categories; otherwise derive categories directly from the spec. Either way, every item falls under one of: + +- **Completeness** — is a necessary requirement documented at all? +- **Clarity** — is a vague term ("robust," "prominent," "fast") quantified? +- **Consistency** — do related requirements agree with each other? +- **Measurability** — can this requirement be objectively verified once built? +- **Coverage** — are edge cases and non-functional attributes (performance, security, accessibility) addressed, where relevant to this deliverable's domain? + +Number items sequentially (`CHK001`, `CHK002`, ...) and cite the spec section each checks, or mark `[Gap]` when checking for something the spec doesn't have at all. Open the checklist file with a one-line Product Owner TL;DR (per §8) — what it's checking and why — the same as any other generated artifact, just shorter. + +## Verify (`--verify`) + +Check each item against the spec: done, not done, or not applicable. Don't mark an item done on inference — only when the spec text demonstrably satisfies it. + +## Output + +The checklist lives alongside the feature's spec. Unchecked items are visible in the `bold.ship` PR body, not hidden. diff --git a/.bold/commands/plan/clarify.md b/.bold/commands/plan/clarify.md new file mode 100644 index 0000000..a687e3f --- /dev/null +++ b/.bold/commands/plan/clarify.md @@ -0,0 +1,41 @@ +--- +command: bold.plan +subcommand: clarify +description: Re-run the clarification pass on the active feature's spec. +collector: none +args: [] +--- + +# bold.plan clarify + +No collector: the only input this needs is the feature's own spec, already on disk — reaching for repo-wide facts here would be scope creep. + +## Definition of Done + +Done when the question queue is exhausted — five asked, full coverage reached, or the human signals they're done — and every accepted answer is already folded into the spec, not just recorded in chat. A session that asks zero questions because coverage was already Clear is done just as validly as one that asks five. + +## Before you begin + +Load the active feature's `spec.md` and `bold-docs/backbone.md`. + +## Scan for ambiguity + +Ambiguity is exactly what forced Feature tier in the first place (triage signal: "contains ambiguity that clarification would need to resolve"). Scan the spec against these categories — for each, judge Clear / Partial / Missing, and only Partial/Missing categories become candidate questions: + +- **Scope & behavior** — core goals, explicit out-of-scope declarations +- **Data model** — entities, relationships, lifecycle/state transitions +- **Interaction & edge cases** — critical flows, error/empty states, conflict resolution +- **Non-functional attributes** — performance, security, observability — as measurable targets, not adjectives +- **Integration** — external dependencies and their failure modes +- **Constraints & tradeoffs** — technical constraints, explicitly rejected alternatives +- **Acceptance criteria testability** — can this actually be verified as written? + +Don't raise a question just because a category is imperfect — raise it only if the answer would materially change the spec, tasks, or acceptance criteria. A stylistic gap or a plan-level execution detail isn't worth a question here. + +## Ask, one at a time + +Cap at 5 questions. For each: propose your own best answer first (`**Recommended:** ... — `), then ask — the human can accept it with "yes" or override it. This is faster than an open question and still leaves the human in control. Never reveal queued questions in advance; stop early if remaining ambiguity is resolved or the human signals they're done. + +## Update after each answer, not at the end + +Fold each ratified answer into the spec immediately (not batched at the end) — promote it into Acceptance Criteria or Intent, in the section it actually belongs to. If it invalidates something already written, replace that text rather than leaving both versions in the file. Leave unresolved categories open rather than inventing an answer. The tool proposes; the human ratifies. diff --git a/.bold/commands/plan/critic.md b/.bold/commands/plan/critic.md new file mode 100644 index 0000000..6b802c0 --- /dev/null +++ b/.bold/commands/plan/critic.md @@ -0,0 +1,62 @@ +--- +command: bold.plan +subcommand: critic +description: Adversarial risk critique of the active feature's spec — what will fail in production, gated at Feature tier. +collector: collect-triage-context +args: + - name: --strict + description: Apply the full taxonomy even at tiers that don't require it by default. +--- + +# bold.plan critic + +Full gate pre-flight for Feature-tier work (§6, §7). Quick and Patch don't run this by default; `--strict` forces it regardless of tier. + +**Mindset**: adversarial, not neutral. Assume the team has limited experience with this stack, estimates are optimistic, and edge cases are underspecified. If it can fail, assume it will. + +## Definition of Done + +Done when the report is saved to `gates/critic.md` and every applicable risk category has either produced a finding or been explicitly marked inapplicable — never silently skipped. A clean report ("no findings") is only done if every category was actually evaluated against the spec, not assumed fine. + +## Not `bold.plan analyze` + +Critic and analyze are deliberately non-overlapping gates: + +| | `bold.plan analyze` | `bold.plan critic` | +|---|---|---| +| Question | Are the artifacts internally consistent? | What will fail in production? | +| Mindset | Neutral validator | Adversarial skeptic | +| Owns | Duplication, wording ambiguity, requirement↔task coverage | Achievability of stated targets, missing operational tasks, failure modes | + +A finding that fits analyze's lane (the spec contradicts itself) doesn't also get raised here — cross-reference it instead of duplicating. + +## What this is NOT + +Findings are hypotheses, not proven defects. This doesn't replace SAST, DAST, dependency scanning, code review, or real testing. A clean critic report means "no obvious traps in the spec," not "the system is safe." + +## Risk categories + +Evaluate the spec against whichever of these actually apply to the deliverable's domain — skip a category only when it's genuinely inapplicable (a chart-of-accounts spec has no concurrency category), and say so rather than silently omitting it: + +- **Trust boundaries / auth** — privileged operations without enforced boundaries, ambient authority, missing isolation +- **Secrets handling** — hardcoded secrets, no rotation, secrets reaching logs +- **Data loss / continuity** — no backup/restore, destructive ops without confirmation, missing transactional boundaries +- **Input validation** — untrusted input crossing a trust boundary unvalidated, injection vectors +- **Error handling / resilience** — swallowed errors, non-idempotent retries, missing timeouts +- **Concurrency** — race conditions, resource leaks, deadlocks (where the deliverable has any concurrent execution) +- **Scale bottlenecks** — unbounded result sets, N+1 access patterns, no path to horizontal scaling +- **Observability** — can a failure in the new behavior actually be detected in production? +- **Deployment / rollback** — no safe-release strategy, no migration plan, no rollback procedure +- **Dependency supply chain** — unpinned versions, abandoned packages, unreviewed transitive risk +- **Backward compatibility** — breaking changes to a contract other work depends on +- **Regulatory / privacy** — PII handling, retention, audit logging, where applicable + +## Severity + +Every backbone-principle violation is automatically a **blocker** (matches the backbone's `enforced` status — see `bold-docs/backbone.md`). Beyond that, a finding is a blocker if it threatens data loss, a security breach, or a production outage; otherwise it's a note. + +## Report + +Open with the Product Owner TL;DR (per §8 — critique output is a generated artifact like any other). Then one finding per category that has a real risk — not one per category regardless. Each finding states the risk, the spec section it traces to, and whether it's a blocker (must be addressed before `bold.build`) or a note (acceptable with acknowledgement). The human ratifies: fix the spec, accept the risk as a `- Waiver: ...` line under the spec's `## Waivers` heading (format: `source/commands/WAIVERS.md`), or escalate. + +Save the report to the feature's own `gates/critic.md`, replacing the prior run rather than appending — `bold.plan tasks` reads it to generate remediation tasks, and a stale duplicate would confuse that merge. diff --git a/.bold/commands/plan/default.md b/.bold/commands/plan/default.md new file mode 100644 index 0000000..c0086f8 --- /dev/null +++ b/.bold/commands/plan/default.md @@ -0,0 +1,72 @@ +--- +command: bold.plan +subcommand: default +description: Triage the request, get the tier ratified, and produce the tier-appropriate planning artifact. +collector: collect-triage-context +args: + - name: --tier + values: [patch, quick, feature] + description: Skip the triage proposal and plan directly at the given tier. Downgrade overrides are still recorded. +--- + +# bold.plan + +Every piece of work enters through this command. Before any planning artifact exists, propose a scope tier, get it ratified, and route to the matching flow. + +## Definition of Done + +Done when a tier is ratified (confirmed or overridden by the human) and the matching artifact — patch log entry, mini-spec, or spec — exists and carries that tier in its metadata. Not done if the artifact was produced before ratification, even if the tier turned out to be right. + +## Before you begin + +Load `bold-docs/backbone.md`, `bold-docs/project.json`, and the collector output referenced above. If `bold-docs/system/` contains docs describing intended behavior for the area this request touches, read them — they are evidence for the Patch signal below. + +If the collector's `stale_references` is non-empty, mention it once, briefly, before triage: which `system/` docs reference paths that no longer exist, and that `bold.ship harvest` reconciles them (§13). This is a notice, not a gate — never halt or edit those docs here. + +## Propose a tier + +Evaluate Feature signals first: any one of them forces Feature regardless of what else is true. + +**Feature** (any one forces this tier): +- Touches a contract, data model, or cross-module flow +- Implicates a backbone principle +- Introduces a new dependency, service, or persistent data +- Requires a decision future work will need recorded +- Contains ambiguity that clarification would need to resolve + +**Quick** (all must hold, and no Feature signal fired): +- New behavior, bounded to one module +- No change to API contracts, data model, or anything in `system/` +- No backbone principle implicated +- No new dependencies or configuration surface + +**Patch** (all must hold, and no Feature or Quick signal fired): +- Describes broken vs. intended behavior ("fix," "broken," "error," "regression") +- Intended behavior is already documented somewhere (spec, test, or a `system/` doc) +- Plausible change surface is a single module or file + +## Ratify + +State the proposed tier and name the specific signal that drove it — reasoning shown, not just an answer. Wait for the human to confirm or override before producing anything. + +- An override *below* your proposed tier is recorded in the work item's metadata — it matters later if the "quick fix" turns out not to be one. +- `--tier` skips the proposal; downgrade overrides from it are still recorded. +- The tool proposes; the human ratifies. Never produce the artifact below before ratification. + +## Route + +| Ratified tier | Produce | +|---|---| +| Patch | One-paragraph entry appended to the running `patches.md` log — a single *What/Why* line, not the full TL;DR block | +| Quick | `bold-docs/features/{id}/spec.md`, opening with the Product Owner TL;DR, then intent, acceptance criteria, affected files, inline task list | +| Feature | `bold-docs/features/{id}/spec.md`, opening with the Product Owner TL;DR, then intent, acceptance criteria, and open questions — `clarify` and `tasks` remain separate passes over it | + +Every tier above Patch writes to a file literally named `spec.md` — the collectors that populate `active_features` (`Get-ActiveFeatures` / `collect_active_features`) only recognize that filename. Quick's spec is simply lighter-weight content, not a different filename. + +For Quick and Feature, create and check out a feature branch before writing `spec.md` — name it after the feature id (e.g. `0001-ignition-landing-page`). `bold.ship`'s collector diffs the current branch against `main`; without a branch of its own, a Quick/Feature's commits land on `main` directly and there is nothing for `bold.ship` to diff. Patch stays on the current branch — it's small enough to ship inline with whatever's already in flight. + +Record the ratified tier in the work item's metadata; `bold.build` and `bold.ship` read it to select gate sets and harvest depth. + +## Escalation + +If work outgrows its ratified tier during `bold.build`, that command halts and routes back here at the higher tier — never silently continue under-scoped. Carry forward whatever work products already exist; nothing already produced is discarded. diff --git a/.bold/commands/plan/discover.md b/.bold/commands/plan/discover.md new file mode 100644 index 0000000..6bdb8ac --- /dev/null +++ b/.bold/commands/plan/discover.md @@ -0,0 +1,29 @@ +--- +command: bold.plan +subcommand: discover +description: Read-only brownfield archaeology — mine configs and history, infer conventions, play back findings for ratification. +collector: collect-brownfield-signals +args: [] +--- + +# bold.plan discover + +Read-only archaeology, then playback. Nothing here writes to the repo until the human ratifies in the final step. + +## Definition of Done + +Done when every candidate principle has been ratified, marked `adopting`, or dropped by the human — not merely proposed — and `project.json` reflects the discovered answers plus whatever the residual questionnaire resolved. + +## Mine + +1. **Config mining** — every config file the collector found (linters, CI workflows, test config, editorconfig, Dockerfiles, dependency manifests) is a codified decision someone already made. Translate each into a candidate backbone principle with `source: discovered()`. +2. **Convention inference** — sample code for naming, structure, error-handling, and logging patterns. Only state a rule where the sample is actually consistent; note where it isn't. +3. **History signals** — cite existing `CONTRIBUTING.md`, PR templates, and commit conventions rather than restating them as new principles. + +## Play back + +Open with the Product Owner TL;DR (per §8) — this playback is a generated artifact like any other, and it's the one a non-technical stakeholder is most likely to actually read. Then present findings as counted evidence: "N practices consistently followed (proposed); M inconsistent." The inconsistent ones are the highest-value output — they're conversations the team never had. For each candidate principle: ratify as `enforced`, mark `adopting` (new code complies, legacy grandfathered), or drop. + +## Write + +Discovered answers go into `project.json` with `source: discovered`. Whatever the mining pass didn't cover becomes a short residual questionnaire, same shape as greenfield's. diff --git a/.bold/commands/plan/init.md b/.bold/commands/plan/init.md new file mode 100644 index 0000000..8f2d4db --- /dev/null +++ b/.bold/commands/plan/init.md @@ -0,0 +1,54 @@ +--- +command: bold.plan +subcommand: init +description: Detect repo entry path (migrate / discover / greenfield) and route accordingly. +collector: collect-entry-path-context +args: [] +--- + +# bold.plan init + +Detect which of three entry paths applies, then route. All three converge on identical artifacts: a ratified backbone, a populated project genome, and a seeded `system/` taxonomy. + +## Definition of Done + +Done when a ratified backbone, a populated `project.json`, and a seeded `system/` all exist — regardless of which entry path produced them. For migrate specifically, done also requires meeting the acceptance bar below; for discover, route to `bold.plan discover` and its own Definition of Done applies. + +## Detect entry path + +Read the collector output. + +- `has_bold_docs` true → already initialized. Report status instead of re-running any path; if the human wants to redo it, that's a deliberate re-init, not entry-path detection's job. +- `has_devspark` or `has_documentation_dir` true → **Migrate** +- `total_files` ≤ 5 → **Greenfield**. A bare scaffold (`README.md`, `LICENSE`, `.gitignore`, maybe a CI stub) still reads as empty; this is a stated rule, not a judgment call, so the boundary doesn't shift run to run — a real repo with actual source and tooling clears it within a handful of files (validated against synthetic fixtures: a true greenfield scaffold sits at 3, a small real codebase already at 9). +- Otherwise → **Discover** + +## Migrate + +DevSpark migration is brownfield discovery with a richer evidence source: a DevSpark repo is a fully documented brownfield in the predecessor's dialect. A real DevSpark install has usually grown well beyond its original template — don't assume the collector's named fields are the whole story. + +1. **Collect** — run `collect-devspark-inventory`. Beyond its named fields (constitution, team scripts, spec dirs, multi-app config), it also reports a generic catch-all of anything under `.documentation/` it doesn't specifically recognize, whether a root `.archive/` already exists, legacy per-host adapter file counts (`.claude/commands/`, `.github/agents/`, `.github/prompts/`, `agents-registry.json`), and a tracked-file sweep for lingering "devspark" text. Every catch-all entry needs a classification in the report below — none of them are pre-decided. +2. **Reason** — produce a migration report following the shape in `templates/migration-report-template.md` (inventory table, "found beyond the standard mapping" table, proposed mapping table, numbered items needing a call, acceptance bar, execution order) — this is a structural template, not boilerplate to copy verbatim; every row must reflect what the collector and a manual cross-check actually found in this repo. Map DevSpark artifacts to Bold equivalents per bold-tool-plan.md §10.4's table: + - Constitution → `backbone.md`, restated *tersely* (`source: migrated(constitution.md)`, MANDATORY→`enforced` else `adopting`) with full sub-rule detail moved to a `system/` doc, not left in the backbone. + - Team scripts → `bold-docs/overrides/`, with a note that they likely don't match any current Bold script name (DevSpark's command surface is different) — flag for review, don't imply they're wired up. + - Feature dirs → `features/` (in-flight) or `.archive/` (complete, harvested on entry). + - Legacy per-host adapter files and `agents-registry.json` → deleted (superseded by Bold's own generated adapters + `AGENTS.md`). + - A pre-existing root `.archive/` → adopt in place, merge its history into the new `index.md`; don't overwrite it as a conflict. + - Every catch-all `.documentation/` entry → classify as durable (`bold-docs/system/`) or historical (`.archive/`) — it's real project content organized under a DevSpark-created folder name, not automatically DevSpark tooling. **Classifying a doc as durable is not the same as relocating it unread.** Any doc moving into `bold-docs/system/` must actually be read for DevSpark-specific operational content first — a "how to use the commands" section, a directory diagram naming `.devspark/`/`.documentation/`, an instruction to run `/devspark.*` — and that content rewritten to reflect Bold's current reality or explicitly marked superseded. A doc that still tells its reader to run a command that no longer exists is not "carried forward," it's actively wrong, and copying the file unedited does not satisfy this mapping step. + - Any editor/tooling config referencing DevSpark (`.vscode/settings.json`, `.claude/settings.local.json`, `.gitignore`, similar) → update to Bold equivalents or drop the DevSpark-specific entries only. `.gitignore` in particular tends to carry a `# DevSpark` labeled block that outlives the paths it once ignored — check whether the paths it names still exist post-migration, not just whether the block runs without error. +3. **Ratify** — the report's full text goes directly into your response — quote it, don't just say you wrote or read it. A `Read`/`Write` tool call renders as a collapsed action in most hosts; it is not equivalent to the human having seen the content, even if they explicitly ask to see it — that request must be answered by reproducing the text, not by re-running the same tool call differently. If the report is also saved as a working file (reasonable for a document this size), save it inside the target repo itself — gitignored, disposable, deleted or superseded once Execute finishes — never an OS-level temp path outside the repo the human is already working in; a document nobody knows how to find cannot be ratified, only nodded past. The human confirms the report as read; it then becomes the contract. +4. **Execute** — write the transformed artifacts, verify them, then delete `.devspark/` and legacy files. The deletion is the last step, never the first. Verification means re-running the collector's tracked-file text sweep and **individually accounting for every hit** outside `.archive/` and provenance notes — "N hits, all legitimate" is not a finding, each one needs a one-line reason (real project content, or a historical mention inside a doc that's actually historical). A hit inside a doc under `bold-docs/system/` is not automatically legitimate just because the doc as a whole was classified durable — check whether that specific line is history or a live instruction. + +**Acceptance bar**: after migration, the repo must be indistinguishable from one born on Bold — nothing in the finished repo carries legacy DevSpark naming or structure. A lingering historical mention in `.archive/` or a provenance note (`source: migrated(...)`) doesn't violate this; a live reference in an active file does — including inside a `bold-docs/system/` doc that was moved there without checking its own content for stale operational instructions. + +## Discover + +Route to `bold.plan discover`. + +## Greenfield + +1. **Starter/kit selection** — offer the catalog (`source/starters/`, `source/kits/`); a kit is a preset composition (starter + stacks + flavors + answer defaults), layers are the power-user path for picking stacks/flavors individually. +2. **Stack detection** from existing files, to suggest (not force) a starting selection. +3. **Compose** — run `compose-layers` with the chosen starter (+ stacks/flavors, or the chosen kit). **If it reports any `conflicts`, halt and ask**: name the colliding question `id` and which layers both defined it — never silently pick one (bold-tool-plan.md §12.3, "conflicts halt and ask, never silent last-wins"). This is the one thing in this step that isn't a judgment call. +4. **Questionnaire** — ask every question in `composed_questions`, in order, skipping any whose `when` names a layer not in this composition. For each, the effective default is `kit_answer_defaults[id]` if present, else the question's own `default` — Enter-mashing through all of them still yields a valid project. Write each answer into `project.json` under the genome key its `maps_to` names, with `source: asked`. +5. **Generate** a provenance-annotated backbone (cite each `backbone_fragments` entry's `source` layer), a seeded `system/`, and `AGENTS.md`. diff --git a/.bold/commands/plan/tasks.md b/.bold/commands/plan/tasks.md new file mode 100644 index 0000000..bc32544 --- /dev/null +++ b/.bold/commands/plan/tasks.md @@ -0,0 +1,49 @@ +--- +command: bold.plan +subcommand: tasks +description: Regenerate the task breakdown for the active feature's ratified spec, or merge in gate remediation on a re-run. +collector: none +args: [] +--- + +# bold.plan tasks + +No collector: the task breakdown derives entirely from the feature's own ratified spec (and, on a re-run, its `gates/` reports) — nothing outside those should influence the list. + +## Definition of Done + +First run: done when every Acceptance Criterion traces to at least one task, and every task traces back to a criterion — no orphans in either direction. Re-run: done when every selected gate finding has a matching remediation task appended, and nothing earlier in the file was touched. + +## Before you begin + +Load the active feature's `spec.md`. Refuse to proceed if Acceptance Criteria or Open Questions are still unresolved — regenerate after `clarify`, not instead of it. + +## Mode detection + +If the spec has no task list yet, generate (below). If it already has one, this is a re-run: go straight to gate remediation and stop there — don't regenerate from scratch. + +## Generate (first run) + +One task per unit of work that a single `bold.build` pass could complete and verify. Each task should trace to a specific Acceptance Criterion — if a task doesn't map to one, either the task is unnecessary or the spec is missing a criterion. Raise that, don't silently add scope. + +Format every task identically so `bold.build` can parse and mark them mechanically: + +``` +- [ ] T001 [P?] Description with an exact file path +``` + +`T001` is a sequential ID. `[P]` marks it parallelizable (different files, no dependency on an incomplete task) — omit it otherwise. Order the list so gate-relevant work (anything a backbone principle touches) isn't left until last. + +## Gate remediation merge (re-run only) + +If `gates/critic.md` and/or `gates/analyze.md` exist with open findings, merge them into new tasks rather than asking the human to translate reports into work by hand: + +1. Take only `open` findings; dedupe ones citing the same location. +2. Sort blockers before notes. +3. For each selected finding, append a task: `- [ ] T0NN [P?] Fix in (resolves: )`. +4. Don't touch already-checked tasks or earlier sections. +5. Recommend `bold.build`, then re-running `bold.plan critic`/`analyze` afterward to confirm the finding actually cleared. + +## Output + +An inline task list in the spec's task section. diff --git a/.bold/commands/plan/templates/migration-report-template.md b/.bold/commands/plan/templates/migration-report-template.md new file mode 100644 index 0000000..2df959f --- /dev/null +++ b/.bold/commands/plan/templates/migration-report-template.md @@ -0,0 +1,65 @@ +# {Repo Name} — DevSpark → Bold Migration Report + +> **TL;DR for the Product Owner** +> *What*: {one or two sentences — what this repo is, and the scale of what's converting: constitution principle count, shipped/in-flight feature count, legacy per-host file count, anything unusual the inventory turned up} +> *Why*: {why this repo, why now — e.g. "crucible Track 1, repo {n} of ~30" or "requested migration ahead of a new feature"} +> *Status*: Draft — awaiting your ratification. Nothing in {repo} has been touched yet. +> *Decision needed*: Confirm the mapping below{, especially the items flagged "needs your call" if any}. + +## Inventory (via `collect-devspark-inventory`, cross-checked by hand) + +| Artifact | Found | +|---|---| +| `.devspark/` | {file count, version, install method/date if known} | +| `.documentation/memory/constitution.md` | {principle count, MANDATORY vs. non-MANDATORY split, any extra sections like "Additional Standards" or "Implementation Gaps"} | +| `.documentation/scripts/` (team overrides) | {file count, bash/PowerShell/both} | +| `.documentation/specs/*` | {one row per spec dir — status: Complete / In-flight, what artifacts it has} | +| `devspark.json` (multi-app) | {found or not found — single-app vs. multi-app} | +| Waivers / Gate Acknowledgements | {found or none} | +| Stack (discovered, not from any config file) | {language/framework/build-tool/monorepo evidence actually observed} | + +**Found beyond the standard mapping table** (list only if the catch-all inventory or manual cross-check turned up more than the named fields above — real repos usually have accumulated more than a clean install): + +| Artifact | What it is | +|---|---| +| {e.g. `.claude/commands/devspark.*.md` (N files)} | {legacy per-host adapter format, superseded by which Bold adapter} | +| {e.g. `.claude/settings.local.json` / `.vscode/settings.json`} | {mixed file — some entries DevSpark-specific, some genuinely project-useful; call out the split, don't assume} | +| {e.g. pre-existing root `.archive/`} | {describe what's there and whether its policy already resembles Bold's} | +| {any generic `.documentation/` catch-all entry the collector couldn't classify} | {name it — durable candidate or historical, and why} | + +## Proposed mapping + +| DevSpark artifact | Bold equivalent | Action | +|---|---|---| +| `.devspark/` | `.bold/` (installed via manifest) | Delete — **last step**, after everything else is verified | +| `.documentation/memory/constitution.md` | `bold-docs/backbone.md` | Restate each principle tersely, `source: migrated(constitution.md)`, MANDATORY → `enforced` else `adopting`. Non-principle sections (e.g. "Implementation Gaps") → a `bold-docs/system/` doc, not the backbone | +| `.documentation/scripts/` | `bold-docs/overrides/` | Carry forward as-is; flag if bash/PowerShell parity is missing — that's a note for the maintainer, not a migration blocker | +| `.documentation/specs/{name}/` (Complete) | Harvest-on-entry | Durable knowledge → `bold-docs/system/`; work products → `.archive/`; one `.archive/index.md` entry | +| `.documentation/specs/{name}/` (In-flight) | `bold-docs/features/{id}/` | Carry forward as an active feature, tier inferred from its existing artifacts | +| {any general `.documentation/*.md` reference doc, e.g. Guide/Architecture/Deployment} | `bold-docs/system/` | **Read it first.** Rewrite any "how to use the commands" section, directory diagram, or instruction naming `.devspark/`/`.documentation/`/`/devspark.*` to reflect Bold's current reality, or mark it explicitly superseded — relocating the file unedited is not a completed mapping | +| {legacy per-host adapter files, one row per host format found} | *(deleted)* | Superseded by Bold-generated adapters (only after Bold's own install step has produced the replacements) | +| `agents-registry.json` | *(deleted)* | Superseded by generated `AGENTS.md` + adapters | +| {pre-existing root `.archive/`, if found} | `.archive/` (Bold's, same location) | Adopt in place if its policy already matches; call out any wording to reconcile rather than silently overwriting | +| Stack evidence (discovered) | `bold-docs/project.json` | `core.*`/`layer.*` populated with `source: discovered` | +| {anything the sweep found that isn't a DevSpark artifact} | N/A | Untouched — name it so the report shows it was seen, not missed | + +## Items needing your call before I execute anything + +{List only genuine judgment calls — ambiguous ownership, mixed-content files, wording choices on adopted conventions. Omit this section if there are none. Number them; each should be answerable in one sentence.} + +1. {} + +## Acceptance bar (bold-tool-plan.md §10.4, verbatim) + +*"After migration, the repo must be indistinguishable from one born on Bold — nothing in the finished repo carries legacy DevSpark naming or structure."* Re-run the collector's tracked-file text sweep after execution — list every hit outside `.archive/` here with a one-line reason it's fine (real, non-DevSpark project content, or a genuinely historical mention), not a bare "N hits, all legitimate" summary. A hit inside a `bold-docs/system/` doc needs the same individual check as anywhere else — being classified durable doesn't pre-clear its contents. + +## Execution order once ratified (collect → reason → ratify → execute, destructive last) + +1. Write `bold-docs/backbone.md`, `bold-docs/project.json`, seed `bold-docs/system/` +2. Harvest each completed spec: promote durable docs to `system/`, archive work products, one `.archive/index.md` entry per feature +3. Carry forward in-flight specs to `bold-docs/features/` +4. Carry `.documentation/scripts/` → `bold-docs/overrides/` +5. Install Bold proper (`.bold/`, generated adapters, `AGENTS.md`) — this produces the real adapter files that make step 6 safe +6. Delete the legacy per-host files and `agents-registry.json` +7. Verify: re-run the inventory collector's text sweep, confirm the acceptance bar +8. **Last**: delete `.devspark/`, `.documentation/` diff --git a/.bold/commands/ship/address.md b/.bold/commands/ship/address.md new file mode 100644 index 0000000..7dc5292 --- /dev/null +++ b/.bold/commands/ship/address.md @@ -0,0 +1,37 @@ +--- +command: bold.ship +subcommand: address +description: Respond to review findings on the active feature's open PR. +collector: collect-pr-review-context +args: [] +--- + +# bold.ship address + +## Definition of Done + +Done when every selected comment is either resolved in its own commit or explicitly deferred with a stated reason, and the two commit-isolation rules held for every resolution — no shared file paths between a fix commit and its tracking-update commit. + +## Before you begin + +Read the collector output. If `gh_available` is false or `pr_number` is null, there's no PR to address yet — ask the human to paste the feedback directly rather than guessing at it. + +## Address + +For each entry in `review_comments`, either: +- Make the change it asks for, and note which comment it resolves, or +- Explain why it isn't being addressed (out of scope, disagreement, needs a decision) and let the human ratify that response before it's posted + +Don't silently address a comment out of order if a later one depends on it — resolve in the order that keeps intermediate states coherent. + +## Commit isolation (mandatory) + +Code fixes and the record of what was fixed are two different kinds of change — commit them separately: + +1. Stage and commit only the code paths touched by the fixes. Never a broad `git add .`. +2. Separately, update whatever tracks the review (the PR description, a review note in the spec) to mark resolved comments — and commit that on its own. +3. The two commits must not share a single file path. If a fix and its own tracking update land in the same commit, a future re-review can't tell which commit actually changed behavior versus which just recorded that it did. + +## Boundary + +This subcommand responds to feedback already given. It doesn't re-review the whole diff — that's `bold.ship review`'s job. diff --git a/.bold/commands/ship/default.md b/.bold/commands/ship/default.md new file mode 100644 index 0000000..56b0c14 --- /dev/null +++ b/.bold/commands/ship/default.md @@ -0,0 +1,42 @@ +--- +command: bold.ship +subcommand: default +description: Draft a PR (or finalize the deliverable, for non-code domains) for the active feature. +collector: collect-ship-context +args: + - name: --draft + description: Open as a draft PR regardless of readiness. +--- + +# bold.ship + +This command is advisory, not a gate. A dirty working tree, an unresolved `bold.plan checklist` item, or an open waiver is something to surface and explain — never a hard block. The human decides whether to ship anyway, fix first, or stop. (Exception: this doesn't override `bold.build`'s own gates — see Boundary below.) + +## Definition of Done + +Done when the human has explicitly chosen create, update, adjust, or stop — and, for create/update, the PR actually reflects the confirmed draft. Drafting a body and never asking, or acting before confirmation, is not done. + +## Before you begin + +Read the collector output. If `has_uncommitted_changes` is true, say so as a warning, not a refusal — `bold.ship` drafts from committed history, so an uncommitted change simply won't be in the PR yet. + +## Draft + +Open the PR body with the Product Owner TL;DR (per §8) — a PR is a generated artifact like any other. Then: + +- **Summary** — the TL;DR's *What*, expanded to a paragraph if needed +- **Changes** — from `changed_files` and `commits_ahead`; scope to what actually changed, don't re-describe the whole feature if this is the third PR against it +- **Task completion** — N/M tasks done +- **Quality gates** — `bold.plan checklist`/`analyze`/`critic` status, or "no gate artifacts found" if the tier didn't require them +- **Waivers** — every entry in `active_features[].waivers`, plainly stated (a waiver is a visible tradeoff, not a way to make a gate quietly disappear) — omit the section if there are none +- **Reference** — path to the feature's spec + +For non-code domains, "draft a PR" means finalize the deliverable document instead — same content bar (what changed, why, what's still open), different container. + +## Confirm before acting + +Show the drafted title and body and ask explicitly: create, update the existing PR, adjust the draft, or stop. Don't open or update anything until the human confirms. + +## Boundary + +This step packages what's already been built. It doesn't re-run `bold.build`'s gates itself — if `bold.build` hasn't been run at all, route back to it rather than shipping ungated work. diff --git a/.bold/commands/ship/harvest.md b/.bold/commands/ship/harvest.md new file mode 100644 index 0000000..5c9a92c --- /dev/null +++ b/.bold/commands/ship/harvest.md @@ -0,0 +1,37 @@ +--- +command: bold.ship +subcommand: harvest +description: Classify feature artifacts — promote durable knowledge, archive work products, reconcile stale docs. +collector: collect-harvest-inventory +args: [] +--- + +# bold.ship harvest + +## Definition of Done + +Done when every file in every feature under `active_features` has been classified, every promotion and archival that classification implied has actually happened, and `.archive/index.md` has a new entry for this pass. A classification with no corresponding move isn't done, it's an opinion. + +## Classify + +For each feature in `active_features`, walk its files (from `feature_files`) and classify each as: +- **Durable knowledge** — contracts, decisions, rationale that the next feature's planning would need +- **Work product** — task lists, resolved critiques, clarification Q&As, superseded revisions + +The test: *would an agent planning the next feature need this, or would it mislead them?* + +## Promote + +Durable knowledge moves into `bold-docs/system/` (check `system_docs` first — merge into an existing doc rather than creating a near-duplicate). Every promoted `system/` doc opens with a Product Owner TL;DR (per §8), rewritten to describe the current system rather than the change that created it — carry the original TL;DR forward and re-date it, don't leave it stale. Candidate backbone principles go to the human for ratification, not straight into `backbone.md`. + +## Archive + +Work products move wholesale to `.archive/` (root-level, not under `bold-docs/`). Append one row to `.archive/index.md`'s existing table (`Date | Feature | Archived | Promoted to system/`) — add a row, don't restructure it. + +## Reconcile + +Check whether this feature's completion invalidates anything already in `system_docs` — if so, flag those docs for archival too rather than leaving them stale alongside the new truth. + +## Boundary + +This is the *only* command that ever writes to `.archive/` — writing is a deliberate, human-ratified act, not incidental. No command, including this one on a future run, ever reads `.archive/` back into context: it is committed history for human consumption only, with no exception for an explicit human pointer. Nothing should be written expecting a future prompt to see it again. diff --git a/.bold/commands/ship/review.md b/.bold/commands/ship/review.md new file mode 100644 index 0000000..3d060b7 --- /dev/null +++ b/.bold/commands/ship/review.md @@ -0,0 +1,54 @@ +--- +command: bold.ship +subcommand: review +description: Review pass over the active feature's changes before or after a PR is opened. +collector: collect-ship-context +args: [] +--- + +# bold.ship review + +Reuses `collect-ship-context` — review reads the same changed-file and backbone facts `bold.ship` (default) used to draft, and checks them rather than packaging them. + +## Definition of Done + +Done when the branch-sync gate has passed and every file at the highest blast-radius tier has been checked, with a finding reported for each real issue found (or none, if there aren't any). Stopping before the highest tier is fully covered isn't done, regardless of budget spent elsewhere. + +## Branch sync (hard gate) + +If `commits_behind` is greater than 0, stop before reviewing anything: "Blocked — this branch is `N` commits behind `base_branch`; sync it first." A review against a stale base is reviewing the wrong diff. This is the one rule in this command that isn't advisory. + +## Scrutiny scales with what's missing + +If the work has no ratified tier (nothing in `active_features` for it) or no spec at all, apply heightened scrutiny and say so plainly: an unratified change means requirements and acceptance criteria were never formally stated, so findings here may be undercounting real issues. + +## Prioritize by blast radius + +Triage `changed_files` before reviewing, and spend attention accordingly rather than reviewing every file at the same depth: + +| Tier | What | Depth | +|---|---|---| +| Highest | Auth, trust boundaries, data writes, anything a backbone principle touches | Full read, line by line | +| Middle | Business logic, data transformations | Read for correctness | +| Lowest | Tests, config, docs, formatting-only changes | Spot-check | + +Always cover the highest tier fully, even if that means spending the whole review budget there. + +## Review + +For each file, check it against: +- The active feature's spec (Acceptance Criteria it should satisfy) +- `backbone_principles` — any `enforced` principle the change touches +- Anything `bold.plan critic` already flagged as a blocker + +## Re-reviews + +If this is a re-review of the same feature, diff only what changed since the last review rather than re-reviewing everything: carry forward unresolved findings unchanged, mark previously-flagged ones resolved/still-present based on the new diff, and append only genuinely new findings. + +## Report + +Open with the Product Owner TL;DR (per §8 — a review report is a generated artifact like any other, same as `bold.plan critic`/`analyze`). Then one finding per real issue — a criterion not met, a backbone violation, an unaddressed critic blocker. Don't restate what's already correct. Each finding names the file and the specific criterion or principle it fails. Stop once you have high-confidence findings for the highest-blast-radius files rather than exhaustively re-deriving what's already clear. + +## Boundary + +Review reports; it doesn't fix. Findings route to `bold.ship address` (or back to `bold.build` if the fix is substantial) rather than being edited in place here. diff --git a/.bold/flavors/bootstrap/backbone.md b/.bold/flavors/bootstrap/backbone.md new file mode 100644 index 0000000..12e7bcf --- /dev/null +++ b/.bold/flavors/bootstrap/backbone.md @@ -0,0 +1,3 @@ +1. **Reach for a Bootstrap component before writing custom CSS** — a custom override needs a stated reason (Bootstrap genuinely can't do it), not just preference. + + **Status**: enforced diff --git a/.bold/flavors/bootstrap/flavor.json b/.bold/flavors/bootstrap/flavor.json new file mode 100644 index 0000000..6334875 --- /dev/null +++ b/.bold/flavors/bootstrap/flavor.json @@ -0,0 +1 @@ +{"name":"bootstrap","version":"1.0.0","description":"Bootstrap component-class styling."} diff --git a/.bold/flavors/tailwind/backbone.md b/.bold/flavors/tailwind/backbone.md new file mode 100644 index 0000000..5054515 --- /dev/null +++ b/.bold/flavors/tailwind/backbone.md @@ -0,0 +1,3 @@ +1. **Utility classes, not inline `style=` attributes or new bespoke CSS files** — a genuinely reusable pattern becomes a component, not a new class. + + **Status**: enforced diff --git a/.bold/flavors/tailwind/flavor.json b/.bold/flavors/tailwind/flavor.json new file mode 100644 index 0000000..68967de --- /dev/null +++ b/.bold/flavors/tailwind/flavor.json @@ -0,0 +1 @@ +{"name":"tailwind","version":"1.0.0","description":"Tailwind CSS utility-class styling."} diff --git a/.bold/kits/bold-webapp.json b/.bold/kits/bold-webapp.json new file mode 100644 index 0000000..a748244 --- /dev/null +++ b/.bold/kits/bold-webapp.json @@ -0,0 +1,7 @@ +{ + "name": "bold-webapp", + "version": "1.0.0", + "description": "Fullstack web app: .NET API + React frontend, Tailwind styling.", + "composition": { "starter": "fullstack", "stacks": ["dotnet", "react"], "flavors": ["tailwind"] }, + "answer_defaults": { "react.router": "tanstack", "react.state": "zustand" } +} diff --git a/.bold/scripts/bash/bold-which.sh b/.bold/scripts/bash/bold-which.sh new file mode 100644 index 0000000..a4f33b2 --- /dev/null +++ b/.bold/scripts/bash/bold-which.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Three-tier resolution shim (bold-tool-plan.md §9.3). Given a path relative +# to the tool's content root (e.g. commands/plan/critic.md), reports which +# tier would serve it — user (.bold-user/{git-user-name}/) > team +# (bold-docs/overrides/) > source (.bold/) — first hit wins, whole-file +# replacement only (no fragment merging). Doubles as the `bold which` +# diagnostic: it reports the full chain, not just the winner. +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$script_dir/lib/common.sh" + +usage() { + echo "Usage: bold-which.sh [--root ] " >&2 + exit 1 +} + +root_override="" +rel_path="" +while [ $# -gt 0 ]; do + case "$1" in + --root) root_override="$2"; shift 2 ;; + -*) usage ;; + *) rel_path="$1"; shift ;; + esac +done +[ -n "$rel_path" ] || usage + +if [ -n "$root_override" ]; then + repo_root="$root_override" +else + repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +fi + +user_path="$repo_root/.bold-user/$(bold_user_slug "$repo_root")/$rel_path" +team_path="$repo_root/bold-docs/overrides/$rel_path" +source_path="$repo_root/.bold/$rel_path" + +user_exists=false; [ -f "$user_path" ] && user_exists=true +team_exists=false; [ -f "$team_path" ] && team_exists=true +source_exists=false; [ -f "$source_path" ] && source_exists=true + +resolved_tier="none" +resolved_path="" +if [ "$user_exists" = true ]; then + resolved_tier="user"; resolved_path="$user_path" +elif [ "$team_exists" = true ]; then + resolved_tier="team"; resolved_path="$team_path" +elif [ "$source_exists" = true ]; then + resolved_tier="source"; resolved_path="$source_path" +fi + +printf '{"path":"%s","resolved_tier":"%s","resolved_path":"%s","tiers":[{"tier":"user","path":"%s","exists":%s},{"tier":"team","path":"%s","exists":%s},{"tier":"source","path":"%s","exists":%s}]}\n' \ + "$(json_escape "$rel_path")" "$resolved_tier" "$(json_escape "$resolved_path")" \ + "$(json_escape "$user_path")" "$user_exists" \ + "$(json_escape "$team_path")" "$team_exists" \ + "$(json_escape "$source_path")" "$source_exists" diff --git a/.bold/scripts/bash/collect-brownfield-signals.sh b/.bold/scripts/bash/collect-brownfield-signals.sh new file mode 100644 index 0000000..a42af7e --- /dev/null +++ b/.bold/scripts/bash/collect-brownfield-signals.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Collector for bold.plan discover. Emits deterministic facts about codified +# decisions already present in the repo — config files, CI workflows, +# dependency manifests, contribution docs, and recent commit history — so +# discover reasons over inventory instead of re-scanning the tree itself. +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$script_dir/lib/common.sh" + +root_override="" +while [ $# -gt 0 ]; do + case "$1" in + --root) root_override="$2"; shift 2 ;; + *) shift ;; + esac +done + +if [ -n "$root_override" ]; then + repo_root="$root_override" +else + repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +fi +cd "$repo_root" + +known_configs=(.editorconfig .eslintrc .eslintrc.json .eslintrc.js .eslintrc.cjs .eslintrc.yml \ + .prettierrc .prettierrc.json .prettierrc.js tsconfig.json pyproject.toml .flake8 setup.cfg \ + Dockerfile docker-compose.yml .pre-commit-config.yaml) +config_files_present=() +for f in "${known_configs[@]}"; do + [ -e "$f" ] && config_files_present+=("$f") +done + +known_manifests=(package.json requirements.txt Gemfile go.mod pom.xml) +dependency_manifests_present=() +for f in "${known_manifests[@]}"; do + [ -e "$f" ] && dependency_manifests_present+=("$f") +done +while IFS= read -r f; do + [ -n "$f" ] && dependency_manifests_present+=("$f") +done < <(find . -maxdepth 2 -name '*.csproj' -printf '%P\n' 2>/dev/null; printf '\n') + +ci_workflows=() +if [ -d .github/workflows ]; then + while IFS= read -r f; do + [ -n "$f" ] && ci_workflows+=(".github/workflows/$f") + done < <(find .github/workflows -type f -printf '%P\n' 2>/dev/null; printf '\n') +fi + +has_contributing=false +[ -f CONTRIBUTING.md ] && has_contributing=true + +has_pr_template=false +[ -f .github/PULL_REQUEST_TEMPLATE.md ] && has_pr_template=true + +recent_commit_messages=() +if [ -e "$repo_root/.git" ]; then + while IFS= read -r line; do + [ -n "$line" ] && recent_commit_messages+=("$line") + done < <(git -C "$repo_root" log -20 --pretty=format:'%s' 2>/dev/null; printf '\n') +fi + +printf '{"config_files_present":%s,"dependency_manifests_present":%s,"ci_workflows":%s,"has_contributing":%s,"has_pr_template":%s,"recent_commit_messages":%s}\n' \ + "$(json_array "${config_files_present[@]}")" \ + "$(json_array "${dependency_manifests_present[@]}")" \ + "$(json_array "${ci_workflows[@]}")" \ + "$has_contributing" "$has_pr_template" \ + "$(json_array "${recent_commit_messages[@]}")" diff --git a/.bold/scripts/bash/collect-devspark-inventory.sh b/.bold/scripts/bash/collect-devspark-inventory.sh new file mode 100644 index 0000000..c5913fb --- /dev/null +++ b/.bold/scripts/bash/collect-devspark-inventory.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Collector for bold.plan init's Migrate path. Inventories a DevSpark +# install so the migration report prompt reasons over structured ground +# truth instead of re-deriving it by hand. +# +# Learned from the TailwindSpark migration (2026-07-09, bold-tool-plan.md +# §15 crucible feedback): a real DevSpark repo accumulates far more than +# the predefined slots below -- guides, ADRs, release history, repo-story, +# legacy per-host adapter files, editor config referencing DevSpark. This +# collector now also emits a generic catch-all inventory of anything under +# .documentation/ it doesn't specifically recognize, detects a pre-existing +# root .archive/ (a real repo may already have one, worth adopting rather +# than treating as a conflict), counts legacy per-host adapter files, and +# scans tracked files for lingering "devspark" text references. It still +# doesn't *classify* any of this -- that's the migration report's job +# (Reason), not the collector's (Collect) -- it just makes sure nothing is +# silently invisible. +set -euo pipefail + +root_override="" +while [ $# -gt 0 ]; do + case "$1" in + --root) root_override="$2"; shift 2 ;; + *) shift ;; + esac +done + +if [ -n "$root_override" ]; then + repo_root="$root_override" +else + repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +fi +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=./lib/common.sh +source "$script_dir/lib/common.sh" + +entry_type_and_count() { + # $1 = path -> prints "type\tfile_count" + if [ -d "$1" ]; then + printf 'dir\t%s' "$(find "$1" -type f | wc -l | tr -d ' ')" + else + printf 'file\t1' + fi +} + +devspark_dir="$repo_root/.devspark" +has_devspark_dir=false +devspark_file_count=0 +if [ -d "$devspark_dir" ]; then + has_devspark_dir=true + devspark_file_count="$(find "$devspark_dir" -type f | wc -l | tr -d ' ')" +fi + +docs_dir="$repo_root/.documentation" +has_documentation_dir=false +[ -d "$docs_dir" ] && has_documentation_dir=true + +constitution_path="$docs_dir/memory/constitution.md" +has_constitution=false +principle_count=0 +if [ -f "$constitution_path" ]; then + has_constitution=true + principle_count="$(awk ' + /^## Core Principles/ { in_section=1; next } + in_section && /^## / { exit } + in_section && /^### / { count++ } + END { print count+0 } + ' "$constitution_path")" +fi + +team_scripts_dir="$docs_dir/scripts" +has_team_scripts_override=false +team_scripts_files=() +if [ -d "$team_scripts_dir" ]; then + has_team_scripts_override=true + while IFS= read -r f; do + [ -n "$f" ] || continue + team_scripts_files+=("${f#"$team_scripts_dir"/}") + done < <(find "$team_scripts_dir" -type f) +fi + +specs_dir="$docs_dir/specs" +spec_entries=() +if [ -d "$specs_dir" ]; then + while IFS= read -r dir; do + [ -n "$dir" ] || continue + name="$(basename "$dir")" + count="$(find "$dir" -type f | wc -l | tr -d ' ')" + spec_entries+=("{\"name\":\"$(json_escape "$name")\",\"file_count\":$count}") + done < <(find "$specs_dir" -mindepth 1 -maxdepth 1 -type d) +fi +spec_dirs_json="[$(IFS=,; echo "${spec_entries[*]}")]" +[ "${#spec_entries[@]}" -eq 0 ] && spec_dirs_json="[]" + +# Catch-all: anything under .documentation/ that isn't memory/scripts/specs. +doc_other_entries=() +if [ "$has_documentation_dir" = true ]; then + while IFS= read -r entry; do + [ -n "$entry" ] || continue + name="$(basename "$entry")" + case "$name" in + memory|scripts|specs) continue ;; + esac + IFS=$'\t' read -r etype ecount <<< "$(entry_type_and_count "$entry")" + doc_other_entries+=("{\"name\":\"$(json_escape "$name")\",\"type\":\"$etype\",\"file_count\":$ecount}") + done < <(find "$docs_dir" -mindepth 1 -maxdepth 1) +fi +doc_other_json="[$(IFS=,; echo "${doc_other_entries[*]}")]" +[ "${#doc_other_entries[@]}" -eq 0 ] && doc_other_json="[]" + +# A real DevSpark repo may already have its own root .archive/ convention -- +# worth detecting and proposing adoption, not treating as a conflict. +archive_dir="$repo_root/.archive" +has_existing_archive=false +archive_entries=() +if [ -d "$archive_dir" ]; then + has_existing_archive=true + while IFS= read -r entry; do + [ -n "$entry" ] || continue + name="$(basename "$entry")" + IFS=$'\t' read -r etype ecount <<< "$(entry_type_and_count "$entry")" + archive_entries+=("{\"name\":\"$(json_escape "$name")\",\"type\":\"$etype\",\"file_count\":$ecount}") + done < <(find "$archive_dir" -mindepth 1 -maxdepth 1) +fi +archive_entries_json="[$(IFS=,; echo "${archive_entries[*]}")]" +[ "${#archive_entries[@]}" -eq 0 ] && archive_entries_json="[]" + +# Legacy per-host adapter files -- a class the original mapping table never +# anticipated, since Bold's own adapter system postdates most DevSpark +# installs. Anything found here pre-migration is presumptively legacy (Bold +# hasn't installed its own adapters yet at collection time). +file_names() { + # $1 = dir -> json array of basenames + local dir="$1" names=() + [ -d "$dir" ] || { echo "[]"; return; } + while IFS= read -r f; do + [ -n "$f" ] || continue + names+=("$(basename "$f")") + done < <(find "$dir" -maxdepth 1 -type f -name '*.md') + json_array "${names[@]}" +} +legacy_claude_commands_json="$(file_names "$repo_root/.claude/commands")" +legacy_github_agents_json="$(file_names "$repo_root/.github/agents")" +legacy_github_prompts_json="$(file_names "$repo_root/.github/prompts")" +has_agents_registry=false +[ -f "$repo_root/agents-registry.json" ] && has_agents_registry=true + +# Multi-app config: named devspark.json per bold-tool-plan.md's own mapping +# table; not every repo has one (single-app is the common case). +has_multi_app_config=false +[ -f "$repo_root/devspark.json" ] && has_multi_app_config=true + +# Verification sweep: tracked files (git ls-files respects .gitignore and is +# far faster than a filesystem walk) mentioning "devspark", excluding +# .archive/ (legitimate historical references belong there). +devspark_ref_files=() +if [ -e "$repo_root/.git" ]; then + while IFS= read -r f; do + [ -n "$f" ] || continue + case "$f" in + .archive/*) continue ;; + esac + case "$f" in + *.md|*.json|*.yml|*.yaml|*.txt) ;; + *) continue ;; + esac + full="$repo_root/$f" + [ -f "$full" ] || continue + if grep -qil "devspark" "$full" 2>/dev/null; then + devspark_ref_files+=("$f") + fi + done < <(git -C "$repo_root" ls-files 2>/dev/null) +fi +devspark_ref_json="$(json_array "${devspark_ref_files[@]}")" + +printf '{"has_devspark_dir":%s,"devspark_dir_file_count":%s,"has_documentation_dir":%s,"has_constitution":%s,"constitution_principle_count":%s,"has_team_scripts_override":%s,"team_scripts_files":%s,"spec_dirs":%s,"documentation_other_entries":%s,"has_existing_archive":%s,"existing_archive_entries":%s,"legacy_claude_commands":%s,"legacy_github_agents":%s,"legacy_github_prompts":%s,"has_agents_registry":%s,"has_multi_app_config":%s,"devspark_reference_files":%s}' \ + "$has_devspark_dir" "$devspark_file_count" "$has_documentation_dir" "$has_constitution" \ + "$principle_count" "$has_team_scripts_override" "$(json_array "${team_scripts_files[@]}")" \ + "$spec_dirs_json" "$doc_other_json" "$has_existing_archive" "$archive_entries_json" \ + "$legacy_claude_commands_json" "$legacy_github_agents_json" "$legacy_github_prompts_json" \ + "$has_agents_registry" "$has_multi_app_config" "$devspark_ref_json" diff --git a/.bold/scripts/bash/collect-entry-path-context.sh b/.bold/scripts/bash/collect-entry-path-context.sh new file mode 100644 index 0000000..c820148 --- /dev/null +++ b/.bold/scripts/bash/collect-entry-path-context.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Collector for bold.plan init. Emits deterministic facts about which entry +# path applies — legacy methodology install present, existing bold install, +# and repo file counts — so init doesn't have to re-derive them by hand. +set -euo pipefail + +root_override="" +while [ $# -gt 0 ]; do + case "$1" in + --root) root_override="$2"; shift 2 ;; + *) shift ;; + esac +done + +if [ -n "$root_override" ]; then + repo_root="$root_override" +else + repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +fi + +has_devspark=false +[ -d "$repo_root/.devspark" ] && has_devspark=true + +has_documentation_dir=false +[ -d "$repo_root/.documentation" ] && has_documentation_dir=true + +has_bold_docs=false +[ -f "$repo_root/bold-docs/backbone.md" ] && has_bold_docs=true + +total_files=$(find "$repo_root" -type f -not -path "$repo_root/.git/*" | wc -l | tr -d ' ') +non_hidden_files=$(find "$repo_root" -type f -not -path "$repo_root/.git/*" -not -path '*/.*' | wc -l | tr -d ' ') + +printf '{"has_devspark":%s,"has_documentation_dir":%s,"has_bold_docs":%s,"total_files":%s,"non_hidden_files":%s}\n' \ + "$has_devspark" "$has_documentation_dir" "$has_bold_docs" "$total_files" "$non_hidden_files" diff --git a/.bold/scripts/bash/collect-gate-status.sh b/.bold/scripts/bash/collect-gate-status.sh new file mode 100644 index 0000000..1828fbf --- /dev/null +++ b/.bold/scripts/bash/collect-gate-status.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Collector for bold.build (default and status). Emits deterministic facts — +# per-feature ratified tier, backbone principle enforcement status, detected +# test-runner config, and working-tree cleanliness — so gates are decided +# from ground truth instead of re-derived by reading the tree by hand. +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$script_dir/lib/common.sh" + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +docs_dir="$repo_root/bold-docs" +cd "$repo_root" + +active_features="$(collect_active_features "$docs_dir")" +backbone_principles="$(collect_backbone_principles "$docs_dir")" +stale_references="$(collect_stale_references "$repo_root" "$docs_dir")" + +known_test_configs=(jest.config.js jest.config.ts pytest.ini tox.ini .mocharc.json .mocharc.yml karma.conf.js phpunit.xml) +test_config_present=() +for f in "${known_test_configs[@]}"; do + [ -e "$f" ] && test_config_present+=("$f") +done +while IFS= read -r f; do + [ -n "$f" ] && test_config_present+=("$f") +done < <(find . -maxdepth 2 -iname '*.tests.csproj' -printf '%P\n' 2>/dev/null; printf '\n') + +has_uncommitted_changes=false +if git rev-parse --git-dir >/dev/null 2>&1; then + [ -n "$(git status --porcelain 2>/dev/null)" ] && has_uncommitted_changes=true +fi + +printf '{"active_features":%s,"backbone_principles":%s,"stale_references":%s,"test_config_present":%s,"has_uncommitted_changes":%s}\n' \ + "$active_features" "$backbone_principles" "$stale_references" "$(json_array "${test_config_present[@]}")" "$has_uncommitted_changes" diff --git a/.bold/scripts/bash/collect-harvest-inventory.sh b/.bold/scripts/bash/collect-harvest-inventory.sh new file mode 100644 index 0000000..2abca7e --- /dev/null +++ b/.bold/scripts/bash/collect-harvest-inventory.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Collector for bold.ship harvest. Emits deterministic facts — active feature +# tiers/status, the file inventory inside each feature dir, and the current +# system/ doc set — so classification (durable vs work product) starts from +# a complete inventory instead of the agent re-listing directories. +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$script_dir/lib/common.sh" + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +docs_dir="$repo_root/bold-docs" + +active_features="$(collect_active_features "$docs_dir")" +system_docs="$(collect_system_docs "$repo_root" "$docs_dir")" + +feature_files="[]" +if [ -d "$docs_dir/features" ]; then + entries=() + for dir in "$docs_dir/features"/*/; do + [ -d "$dir" ] || continue + id="$(basename "$dir")" + files=() + while IFS= read -r f; do + [ -n "$f" ] && files+=("$f") + done < <(find "$dir" -type f -printf '%P\n' 2>/dev/null | sort; printf '\n') + entries+=("{\"id\":\"$(json_escape "$id")\",\"files\":$(json_array "${files[@]}")}") + done + if [ "${#entries[@]}" -gt 0 ]; then + feature_files="[$(IFS=,; echo "${entries[*]}")]" + fi +fi + +printf '{"active_features":%s,"system_docs":%s,"feature_files":%s}\n' \ + "$active_features" "$system_docs" "$feature_files" diff --git a/.bold/scripts/bash/collect-pr-review-context.sh b/.bold/scripts/bash/collect-pr-review-context.sh new file mode 100644 index 0000000..dd97a5b --- /dev/null +++ b/.bold/scripts/bash/collect-pr-review-context.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Collector for bold.ship address. Emits the current PR's number and review +# comments via the gh CLI, so responding to review feedback starts from the +# actual comment thread instead of a human re-pasting it. +set -euo pipefail + +gh_available=false +command -v gh >/dev/null 2>&1 && gh_available=true + +pr_number="null" +review_comments="[]" + +if [ "$gh_available" = true ]; then + if number="$(gh pr view --json number -q .number 2>/dev/null)" && [ -n "$number" ]; then + pr_number="$number" + review_comments="$(gh api "repos/{owner}/{repo}/pulls/$number/comments" \ + --jq '[.[] | {author: .user.login, body: .body}]' 2>/dev/null || echo '[]')" + [ -z "$review_comments" ] && review_comments="[]" + fi +fi + +printf '{"gh_available":%s,"pr_number":%s,"review_comments":%s}\n' \ + "$gh_available" "$pr_number" "$review_comments" diff --git a/.bold/scripts/bash/collect-ship-context.sh b/.bold/scripts/bash/collect-ship-context.sh new file mode 100644 index 0000000..ed9435f --- /dev/null +++ b/.bold/scripts/bash/collect-ship-context.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Collector for bold.ship (default and review). Emits deterministic facts — +# branch position relative to the base branch, changed-file inventory, +# active feature tiers, and backbone status — so drafting or reviewing a PR +# starts from current, structured ground truth. +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$script_dir/lib/common.sh" + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +docs_dir="$repo_root/bold-docs" +cd "$repo_root" + +base_branch="main" +current_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")" + +commits_ahead=0 +commits_behind=0 +changed_files=() +if git rev-parse --verify "$base_branch" >/dev/null 2>&1 && [ "$current_branch" != "$base_branch" ]; then + commits_ahead="$(git rev-list --count "$base_branch..HEAD" 2>/dev/null || echo 0)" + commits_behind="$(git rev-list --count "HEAD..$base_branch" 2>/dev/null || echo 0)" + while IFS= read -r f; do + [ -n "$f" ] && changed_files+=("$f") + done < <(git diff --name-only "$base_branch...HEAD" 2>/dev/null; printf '\n') +fi + +has_uncommitted_changes=false +[ -n "$(git status --porcelain 2>/dev/null)" ] && has_uncommitted_changes=true + +active_features="$(collect_active_features "$docs_dir")" +backbone_principles="$(collect_backbone_principles "$docs_dir")" + +printf '{"base_branch":"%s","current_branch":"%s","commits_ahead":%s,"commits_behind":%s,"changed_files":%s,"has_uncommitted_changes":%s,"active_features":%s,"backbone_principles":%s}\n' \ + "$(json_escape "$base_branch")" "$(json_escape "$current_branch")" "$commits_ahead" "$commits_behind" \ + "$(json_array "${changed_files[@]}")" "$has_uncommitted_changes" "$active_features" "$backbone_principles" diff --git a/.bold/scripts/bash/collect-triage-context.sh b/.bold/scripts/bash/collect-triage-context.sh new file mode 100644 index 0000000..0a0174c --- /dev/null +++ b/.bold/scripts/bash/collect-triage-context.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Collector for bold.plan (default/triage). Emits deterministic JSON facts — +# system doc inventory, active feature tiers/status, backbone principle +# status, and the project genome — so the triage prompt reasons over +# structured ground truth instead of re-deriving it from prose. +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$script_dir/lib/common.sh" + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +docs_dir="$repo_root/bold-docs" + +system_docs="$(collect_system_docs "$repo_root" "$docs_dir")" +active_features="$(collect_active_features "$docs_dir")" +backbone_principles="$(collect_backbone_principles "$docs_dir")" +stale_references="$(collect_stale_references "$repo_root" "$docs_dir")" + +genome="null" +if [ -f "$docs_dir/project.json" ]; then + genome="$(cat "$docs_dir/project.json")" +fi + +printf '{"system_docs":%s,"active_features":%s,"backbone_principles":%s,"stale_references":%s,"genome":%s}\n' \ + "$system_docs" "$active_features" "$backbone_principles" "$stale_references" "$genome" diff --git a/.bold/scripts/bash/compose-layers.sh b/.bold/scripts/bash/compose-layers.sh new file mode 100644 index 0000000..9afb292 --- /dev/null +++ b/.bold/scripts/bash/compose-layers.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# Collector for bold.plan init's greenfield questionnaire. Given a starter +# (+ optional stacks/flavors) or a kit, resolves the full layer set, merges +# each layer's questions.json in composition order (starter -> stacks -> +# flavors), and flags any question `id` that appears in more than one layer +# as a conflict. A kit's answer_defaults pass through unmodified as their own +# field -- applying the override is part of *asking*, which belongs to +# bold.plan init, not to this collector, which only measures and reports. +# +# Parses questions.json/kit.json with sed/grep tuned to the exact flat shape +# source/starters/FORMAT.md requires (at most one level of {} nesting) -- +# not a general JSON parser, same no-new-dependency discipline as every +# other bash collector in this repo. +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=./lib/common.sh +source "$script_dir/lib/common.sh" + +layers_root="" +kit="" +starter="" +stacks_csv="" +flavors_csv="" + +while [ $# -gt 0 ]; do + case "$1" in + --layers-root) layers_root="$2"; shift 2 ;; + --kit) kit="$2"; shift 2 ;; + --starter) starter="$2"; shift 2 ;; + --stacks) stacks_csv="$2"; shift 2 ;; + --flavors) flavors_csv="$2"; shift 2 ;; + *) echo "Unknown argument: $1" >&2; exit 1 ;; + esac +done +[ -n "$layers_root" ] || layers_root="$repo_root/source" + +# Field extractors, tuned to a single-line (newline-stripped) flat JSON +# object/array -- no general JSON parsing, per the format's own no-nesting +# rule (source/starters/FORMAT.md). +strip_nl() { tr -d '\n' < "$1"; } +extract_string() { printf '%s' "$1" | sed -n -E "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\1/p"; } +extract_bracket() { printf '%s' "$1" | sed -n -E "s/.*\"$2\"[[:space:]]*:[[:space:]]*\[([^]]*)\].*/\1/p"; } +extract_brace() { printf '%s' "$1" | sed -n -E "s/.*\"$2\"[[:space:]]*:[[:space:]]*\{([^{}]*)\}.*/\1/p"; } +# Turns `"a","b"` into a bash array via json_array's inverse -- strip quotes, split on comma. +csv_from_bracket() { + [ -n "$1" ] || return 0 + printf '%s' "$1" | tr ',' '\n' | sed -E 's/^[[:space:]]*"(.*)"[[:space:]]*$/\1/' +} + +kit_answer_defaults_json="{}" + +if [ -n "$kit" ]; then + kit_path="$layers_root/kits/$kit.json" + [ -f "$kit_path" ] || { echo "Kit not found: $kit_path" >&2; exit 1; } + kit_content="$(strip_nl "$kit_path")" + starter="$(extract_string "$kit_content" starter)" + stacks_csv="$(csv_from_bracket "$(extract_bracket "$kit_content" stacks)" | paste -sd, -)" + flavors_csv="$(csv_from_bracket "$(extract_bracket "$kit_content" flavors)" | paste -sd, -)" + answer_defaults_body="$(extract_brace "$kit_content" answer_defaults)" + kit_answer_defaults_json="{${answer_defaults_body:-}}" +fi + +[ -n "$starter" ] || { echo "Either --kit or --starter is required" >&2; exit 1; } + +stacks=() +flavors=() +[ -n "$stacks_csv" ] && IFS=',' read -ra stacks <<< "$stacks_csv" +[ -n "$flavors_csv" ] && IFS=',' read -ra flavors <<< "$flavors_csv" + +# Build ordered layer refs as type:name pairs. +layer_refs=("starter:$starter") +for s in "${stacks[@]}"; do [ -n "$s" ] && layer_refs+=("stack:$s"); done +for f in "${flavors[@]}"; do [ -n "$f" ] && layer_refs+=("flavor:$f"); done + +question_objs=() # each entry: the full {..."source":"type:name"} JSON object string +backbone_frags=() + +for ref in "${layer_refs[@]}"; do + type="${ref%%:*}" + name="${ref#*:}" + dir="$layers_root/${type}s/$name" + source_tag="$type:$name" + + questions_path="$dir/questions.json" + if [ -f "$questions_path" ]; then + flat="$(strip_nl "$questions_path")" + while IFS= read -r obj; do + [ -n "$obj" ] || continue + # Insert ,"source":"..." just before the object's closing brace. + tagged="${obj%\}},\"source\":\"$(json_escape "$source_tag")\"}" + question_objs+=("$tagged") + done < <(printf '%s' "$flat" | grep -oE '\{[^{}]*\}') + fi + + backbone_path="$dir/backbone.md" + if [ -f "$backbone_path" ]; then + content="$(cat "$backbone_path")" + backbone_frags+=("{\"source\":\"$(json_escape "$source_tag")\",\"content\":\"$(json_escape "$content")\"}") + fi +done + +composed_questions_json="[$(IFS=,; echo "${question_objs[*]}")]" +[ "${#question_objs[@]}" -eq 0 ] && composed_questions_json="[]" +backbone_fragments_json="[$(IFS=,; echo "${backbone_frags[*]}")]" +[ "${#backbone_frags[@]}" -eq 0 ] && backbone_fragments_json="[]" + +# Mechanical conflict detection: any id appearing in more than one layer. +declare -A id_sources=() +declare -a id_order=() +for obj in "${question_objs[@]}"; do + [ -n "$obj" ] || continue + id="$(printf '%s' "$obj" | sed -n -E 's/.*"id"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p')" + src="$(printf '%s' "$obj" | sed -n -E 's/.*"source"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p')" + [ -n "$id" ] || continue + if [ -z "${id_sources[$id]+x}" ]; then + id_order+=("$id") + id_sources[$id]="$src" + else + id_sources[$id]="${id_sources[$id]},$src" + fi +done + +conflict_entries=() +for id in "${id_order[@]}"; do + IFS=',' read -ra srcs <<< "${id_sources[$id]}" + if [ "${#srcs[@]}" -gt 1 ]; then + conflict_entries+=("{\"id\":\"$(json_escape "$id")\",\"sources\":$(json_array "${srcs[@]}")}") + fi +done +conflicts_json="[$(IFS=,; echo "${conflict_entries[*]}")]" +[ "${#conflict_entries[@]}" -eq 0 ] && conflicts_json="[]" + +stacks_json="$(json_array "${stacks[@]}")" +flavors_json="$(json_array "${flavors[@]}")" +kit_field="null" +[ -n "$kit" ] && kit_field="\"$(json_escape "$kit")\"" + +printf '{"composition":{"kit":%s,"starter":"%s","stacks":%s,"flavors":%s},"composed_questions":%s,"conflicts":%s,"backbone_fragments":%s,"kit_answer_defaults":%s}' \ + "$kit_field" "$(json_escape "$starter")" "$stacks_json" "$flavors_json" \ + "$composed_questions_json" "$conflicts_json" "$backbone_fragments_json" "$kit_answer_defaults_json" diff --git a/.bold/scripts/bash/lib/common.sh b/.bold/scripts/bash/lib/common.sh new file mode 100644 index 0000000..56d7553 --- /dev/null +++ b/.bold/scripts/bash/lib/common.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# Shared helpers for bold's bash collector scripts. + +bold_user_slug() { + # Filesystem-safe slug identifying the current contributor for + # .bold-user/{slug}/ (bold-tool-plan.md §17 #4 — committed, per-user + # tier). Falls back from git user.name to the email's local part, then + # to "shared" if neither is configured. $1 (optional) scopes the git + # config lookup to a specific repo root instead of the caller's cwd -- + # required, not cosmetic: a script invoked with --root pointing elsewhere + # must not silently read the invoking shell's own git identity instead. + local root="${1:-}" name email slug + if [ -n "$root" ]; then + name="$(git -C "$root" config user.name 2>/dev/null || true)" + else + name="$(git config user.name 2>/dev/null || true)" + fi + if [ -z "$name" ]; then + if [ -n "$root" ]; then + email="$(git -C "$root" config user.email 2>/dev/null || true)" + else + email="$(git config user.email 2>/dev/null || true)" + fi + name="${email%%@*}" + fi + [ -n "$name" ] || name="shared" + slug="$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')" + [ -n "$slug" ] || slug="shared" + printf '%s' "$slug" +} + +json_escape() { + # Escapes backslash/quote first (order matters), then newlines, CR, and + # tabs -- found via compose-layers.sh, the first caller to ever pass + # multi-line content (backbone.md fragments) through this helper. + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g; s/\r/\\r/g; s/\t/\\t/g' +} + +json_array() { + local parts=() + for item in "$@"; do + parts+=("\"$(json_escape "$item")\"") + done + if [ "${#parts[@]}" -eq 0 ]; then + echo "[]" + else + (IFS=,; echo "[${parts[*]}]") + fi +} + +# Emits a JSON array of repo-relative paths under bold-docs/system/ +collect_system_docs() { + local repo_root="$1" + local docs_dir="$2" + local files=() + if [ -d "$docs_dir/system" ]; then + mapfile -t files < <(find "$docs_dir/system" -type f ! -name '.gitkeep' | sed "s|^$repo_root/||" | sort) + fi + json_array "${files[@]}" +} + +# Emits a JSON array of {principle,reason,ratified_by,date}, one per +# `- Waiver: ...` line in the given spec file. See source/commands/WAIVERS.md +# for the line format. +collect_waivers_for_spec() { + local spec="$1" + local entries=() + local line principle reason ratified_by date + while IFS= read -r line; do + [ -n "$line" ] || continue + principle="$(printf '%s' "$line" | sed -E 's/.*principle=([0-9]+).*/\1/')" + reason="$(printf '%s' "$line" | sed -E 's/.*reason="([^"]*)".*/\1/')" + ratified_by="$(printf '%s' "$line" | sed -E 's/.*ratified_by="([^"]*)".*/\1/')" + date="$(printf '%s' "$line" | sed -E 's/.*date=([0-9-]+).*/\1/')" + entries+=("{\"principle\":${principle:-null},\"reason\":\"$(json_escape "$reason")\",\"ratified_by\":\"$(json_escape "$ratified_by")\",\"date\":\"$(json_escape "$date")\"}") + done < <(grep -oE '^- Waiver: .*' "$spec" 2>/dev/null; printf '\n') + if [ "${#entries[@]}" -eq 0 ]; then + echo "[]" + else + (IFS=,; echo "[${entries[*]}]") + fi +} + +# Emits a JSON array of {id,status,tier,waivers}, one per +# bold-docs/features/*/spec.md +collect_active_features() { + local docs_dir="$1" + local entries=() + if [ -d "$docs_dir/features" ]; then + for dir in "$docs_dir/features"/*/; do + [ -d "$dir" ] || continue + local id spec status tier waivers + id="$(basename "$dir")" + spec="$dir/spec.md" + [ -f "$spec" ] || continue + status="$(sed -n 's/^\*\*Status\*\*: //p' "$spec" | head -1)" + tier="$(sed -n 's/^\*\*Tier\*\*: //p' "$spec" | head -1)" + waivers="$(collect_waivers_for_spec "$spec")" + entries+=("{\"id\":\"$(json_escape "$id")\",\"status\":\"$(json_escape "${status:-unknown}")\",\"tier\":\"$(json_escape "${tier:-unknown}")\",\"waivers\":$waivers}") + done + fi + if [ "${#entries[@]}" -eq 0 ]; then + echo "[]" + else + (IFS=,; echo "[${entries[*]}]") + fi +} + +# Emits a JSON array of {doc,reference}, one per backtick-quoted, path-shaped +# reference in a bold-docs/system/ doc that doesn't resolve to a real file. +# Scoped to system/ only (§13 ambient staleness detection) -- feature specs +# are expected to reference code that doesn't exist yet. +collect_stale_references() { + local repo_root="$1" + local docs_dir="$2" + local system_dir="$docs_dir/system" + local entries=() + [ -d "$system_dir" ] || { echo "[]"; return; } + local doc rel_doc ref + while IFS= read -r doc; do + [ -n "$doc" ] || continue + rel_doc="${doc#"$repo_root"/}" + while IFS= read -r ref; do + [ -n "$ref" ] || continue + case "$ref" in + http://*|https://*|*\**) continue ;; + esac + if [ ! -e "$repo_root/$ref" ]; then + entries+=("{\"doc\":\"$(json_escape "$rel_doc")\",\"reference\":\"$(json_escape "$ref")\"}") + fi + done < <(grep -oE '`[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)+`' "$doc" 2>/dev/null | tr -d '`' | sort -u; printf '\n') + done < <(find "$system_dir" -type f ! -name '.gitkeep' 2>/dev/null | sort; printf '\n') + if [ "${#entries[@]}" -eq 0 ]; then + echo "[]" + else + (IFS=,; echo "[${entries[*]}]") + fi +} + +# Emits a JSON array of {n,status}, one per numbered principle in backbone.md +collect_backbone_principles() { + local docs_dir="$1" + local backbone_file="$docs_dir/backbone.md" + local entries=() + local n=0 + if [ -f "$backbone_file" ]; then + while IFS= read -r status; do + [ -n "$status" ] || continue + n=$((n+1)) + entries+=("{\"n\":$n,\"status\":\"$(json_escape "$status")\"}") + done < <(sed -n 's/^[[:space:]]*\*\*Status\*\*: //p' "$backbone_file"; printf '\n') + fi + if [ "${#entries[@]}" -eq 0 ]; then + echo "[]" + else + (IFS=,; echo "[${entries[*]}]") + fi +} diff --git a/.bold/scripts/powershell/bold-which.ps1 b/.bold/scripts/powershell/bold-which.ps1 new file mode 100644 index 0000000..f36f232 --- /dev/null +++ b/.bold/scripts/powershell/bold-which.ps1 @@ -0,0 +1,46 @@ +# Three-tier resolution shim (bold-tool-plan.md §9.3). Given a path relative +# to the tool's content root (e.g. commands/plan/critic.md), reports which +# tier would serve it — user (.bold-user/{git-user-name}/) > team +# (bold-docs/overrides/) > source (.bold/) — first hit wins, whole-file +# replacement only (no fragment merging). Doubles as the `bold which` +# diagnostic: it reports the full chain, not just the winner. +param( + [Parameter(Mandatory = $true)][string]$RelPath, + [string]$Root +) + +$ErrorActionPreference = 'Stop' + +. "$PSScriptRoot/lib/Common.ps1" + +if ($Root) { + $repoRoot = $Root +} else { + $repoRoot = git rev-parse --show-toplevel 2>$null + if (-not $repoRoot) { $repoRoot = (Get-Location).Path } +} + +$userPath = Join-Path $repoRoot ".bold-user/$(Get-BoldUserSlug -Root $repoRoot)/$RelPath" +$teamPath = Join-Path $repoRoot "bold-docs/overrides/$RelPath" +$sourcePath = Join-Path $repoRoot ".bold/$RelPath" + +$userExists = Test-Path $userPath -PathType Leaf +$teamExists = Test-Path $teamPath -PathType Leaf +$sourceExists = Test-Path $sourcePath -PathType Leaf + +$resolvedTier = 'none' +$resolvedPath = '' +if ($userExists) { $resolvedTier = 'user'; $resolvedPath = $userPath } +elseif ($teamExists) { $resolvedTier = 'team'; $resolvedPath = $teamPath } +elseif ($sourceExists) { $resolvedTier = 'source'; $resolvedPath = $sourcePath } + +[ordered]@{ + path = $RelPath + resolved_tier = $resolvedTier + resolved_path = $resolvedPath + tiers = @( + [ordered]@{ tier = 'user'; path = $userPath; exists = $userExists } + [ordered]@{ tier = 'team'; path = $teamPath; exists = $teamExists } + [ordered]@{ tier = 'source'; path = $sourcePath; exists = $sourceExists } + ) +} | ConvertTo-Json -Depth 5 -Compress diff --git a/.bold/scripts/powershell/collect-brownfield-signals.ps1 b/.bold/scripts/powershell/collect-brownfield-signals.ps1 new file mode 100644 index 0000000..c708371 --- /dev/null +++ b/.bold/scripts/powershell/collect-brownfield-signals.ps1 @@ -0,0 +1,51 @@ +# Collector for bold.plan discover. Emits deterministic facts about codified +# decisions already present in the repo — config files, CI workflows, +# dependency manifests, contribution docs, and recent commit history — so +# discover reasons over inventory instead of re-scanning the tree itself. + +param([string]$Root) + +$ErrorActionPreference = 'Stop' + +if ($Root) { + $repoRoot = $Root +} else { + $repoRoot = git rev-parse --show-toplevel 2>$null + if (-not $repoRoot) { $repoRoot = (Get-Location).Path } +} +Set-Location $repoRoot + +$knownConfigs = @('.editorconfig', '.eslintrc', '.eslintrc.json', '.eslintrc.js', '.eslintrc.cjs', + '.eslintrc.yml', '.prettierrc', '.prettierrc.json', '.prettierrc.js', 'tsconfig.json', + 'pyproject.toml', '.flake8', 'setup.cfg', 'Dockerfile', 'docker-compose.yml', '.pre-commit-config.yaml') +$configFilesPresent = @($knownConfigs | Where-Object { Test-Path $_ }) + +$knownManifests = @('package.json', 'requirements.txt', 'Gemfile', 'go.mod', 'pom.xml') +$dependencyManifestsPresent = @($knownManifests | Where-Object { Test-Path $_ }) +$dependencyManifestsPresent += @(Get-ChildItem -Path . -Filter '*.csproj' -Depth 1 -File -ErrorAction SilentlyContinue | + ForEach-Object { $_.Name }) + +$ciWorkflows = @() +if (Test-Path '.github/workflows') { + $ciWorkflows = @(Get-ChildItem -Path '.github/workflows' -File -Recurse | + ForEach-Object { $_.FullName.Substring($repoRoot.Length + 1) -replace '\\', '/' }) +} + +$hasContributing = Test-Path 'CONTRIBUTING.md' +$hasPrTemplate = Test-Path '.github/PULL_REQUEST_TEMPLATE.md' + +$recentCommitMessages = @() +if (Test-Path (Join-Path $repoRoot '.git')) { + try { + $recentCommitMessages = @(git -C $repoRoot log -20 --pretty=format:'%s' 2>$null) + } catch {} +} + +[ordered]@{ + config_files_present = $configFilesPresent + dependency_manifests_present = $dependencyManifestsPresent + ci_workflows = $ciWorkflows + has_contributing = $hasContributing + has_pr_template = $hasPrTemplate + recent_commit_messages = $recentCommitMessages +} | ConvertTo-Json -Compress diff --git a/.bold/scripts/powershell/collect-devspark-inventory.ps1 b/.bold/scripts/powershell/collect-devspark-inventory.ps1 new file mode 100644 index 0000000..504e044 --- /dev/null +++ b/.bold/scripts/powershell/collect-devspark-inventory.ps1 @@ -0,0 +1,160 @@ +# Collector for bold.plan init's Migrate path. Inventories a DevSpark +# install so the migration report prompt reasons over structured ground +# truth instead of re-deriving it by hand. +# +# Learned from the TailwindSpark migration (2026-07-09, bold-tool-plan.md +# §15 crucible feedback): a real DevSpark repo accumulates far more than +# the predefined slots below -- guides, ADRs, release history, repo-story, +# legacy per-host adapter files, editor config referencing DevSpark. This +# collector now also emits a generic catch-all inventory of anything under +# .documentation/ it doesn't specifically recognize, detects a pre-existing +# root .archive/ (a real repo may already have one, worth adopting rather +# than treating as a conflict), counts legacy per-host adapter files, and +# scans tracked files for lingering "devspark" text references. It still +# doesn't *classify* any of this -- that's the migration report's job +# (Reason), not the collector's (Collect) -- it just makes sure nothing is +# silently invisible. +param([string]$Root) + +$ErrorActionPreference = 'Stop' + +if ($Root) { + $repoRoot = $Root +} else { + $repoRoot = git rev-parse --show-toplevel 2>$null + if (-not $repoRoot) { $repoRoot = (Get-Location).Path } +} + +function Get-EntrySummary { + param([string]$Path) + if (Test-Path $Path -PathType Container) { + return [ordered]@{ type = 'dir'; file_count = (Get-ChildItem -Path $Path -File -Recurse).Count } + } + return [ordered]@{ type = 'file'; file_count = 1 } +} + +$devsparkDir = Join-Path $repoRoot '.devspark' +$hasDevsparkDir = Test-Path $devsparkDir +$devsparkFileCount = 0 +if ($hasDevsparkDir) { + $devsparkFileCount = (Get-ChildItem -Path $devsparkDir -File -Recurse).Count +} + +$docsDir = Join-Path $repoRoot '.documentation' +$hasDocumentationDir = Test-Path $docsDir + +$constitutionPath = Join-Path $docsDir 'memory/constitution.md' +$hasConstitution = Test-Path $constitutionPath +$principleCount = 0 +if ($hasConstitution) { + $lines = Get-Content $constitutionPath + $inCorePrinciples = $false + foreach ($line in $lines) { + if ($line -match '^## Core Principles') { $inCorePrinciples = $true; continue } + if ($inCorePrinciples -and $line -match '^## ') { break } + if ($inCorePrinciples -and $line -match '^### ') { $principleCount++ } + } +} + +$teamScriptsDir = Join-Path $docsDir 'scripts' +$hasTeamScriptsOverride = Test-Path $teamScriptsDir +$teamScriptsFiles = @() +if ($hasTeamScriptsOverride) { + $teamScriptsFiles = @(Get-ChildItem -Path $teamScriptsDir -File -Recurse | ForEach-Object { + $_.FullName.Substring($teamScriptsDir.Length + 1) -replace '\\', '/' + }) +} + +$specsDir = Join-Path $docsDir 'specs' +$specDirs = @() +if (Test-Path $specsDir) { + $specDirs = @(Get-ChildItem -Path $specsDir -Directory | ForEach-Object { + [ordered]@{ + name = $_.Name + file_count = (Get-ChildItem -Path $_.FullName -File -Recurse).Count + } + }) +} + +# Catch-all: anything under .documentation/ that isn't memory/scripts/specs. +# TailwindSpark had guides/, decisions/, releases/, repo-story/, templates/, +# copilot/, plus loose files (Guide.md, DEVSPARK_VERSION) -- none of that +# would have shown up anywhere above. +$documentationOtherEntries = @() +if ($hasDocumentationDir) { + $known = @('memory', 'scripts', 'specs') + $documentationOtherEntries = @(Get-ChildItem -Path $docsDir | Where-Object { $known -notcontains $_.Name } | ForEach-Object { + $summary = Get-EntrySummary -Path $_.FullName + [ordered]@{ name = $_.Name; type = $summary.type; file_count = $summary.file_count } + }) +} + +# A real DevSpark repo may already have its own root .archive/ convention -- +# worth detecting and proposing adoption, not treating as a conflict. +$archiveDir = Join-Path $repoRoot '.archive' +$hasExistingArchive = Test-Path $archiveDir +$existingArchiveEntries = @() +if ($hasExistingArchive) { + $existingArchiveEntries = @(Get-ChildItem -Path $archiveDir | ForEach-Object { + $summary = Get-EntrySummary -Path $_.FullName + [ordered]@{ name = $_.Name; type = $summary.type; file_count = $summary.file_count } + }) +} + +# Legacy per-host adapter files -- a class the original mapping table never +# anticipated, since Bold's own adapter system postdates most DevSpark +# installs. Anything found here pre-migration is presumptively legacy (Bold +# hasn't installed its own adapters yet at collection time). +function Get-FileNames { + param([string]$Path, [string]$Filter) + if (-not (Test-Path $Path)) { return ,@() } + return ,@(Get-ChildItem -Path $Path -File -Filter $Filter | ForEach-Object { $_.Name }) +} +$legacyClaudeCommands = Get-FileNames -Path (Join-Path $repoRoot '.claude/commands') -Filter '*.md' +$legacyGithubAgents = Get-FileNames -Path (Join-Path $repoRoot '.github/agents') -Filter '*.md' +$legacyGithubPrompts = Get-FileNames -Path (Join-Path $repoRoot '.github/prompts') -Filter '*.md' +$hasAgentsRegistry = Test-Path (Join-Path $repoRoot 'agents-registry.json') + +# Multi-app config: named devspark.json per bold-tool-plan.md's own mapping +# table; not every repo has one (single-app is the common case). +$multiAppConfigPath = Join-Path $repoRoot 'devspark.json' +$hasMultiAppConfig = Test-Path $multiAppConfigPath + +# Verification sweep: tracked files (git ls-files respects .gitignore and is +# far faster than a filesystem walk) mentioning "devspark", excluding +# .archive/ (legitimate historical references belong there). +$devsparkReferenceFiles = @() +if (Test-Path (Join-Path $repoRoot '.git')) { + $trackedFiles = git -C $repoRoot ls-files 2>$null + if ($trackedFiles) { + foreach ($f in $trackedFiles) { + if ($f -like '.archive/*') { continue } + $full = Join-Path $repoRoot $f + if (-not (Test-Path $full -PathType Leaf)) { continue } + if ($f -notmatch '\.(md|json|ya?ml|txt)$') { continue } + if (Select-String -Path $full -Pattern 'devspark' -SimpleMatch -Quiet -CaseSensitive:$false -ErrorAction SilentlyContinue) { + $devsparkReferenceFiles += $f + } + } + } +} + +[ordered]@{ + has_devspark_dir = $hasDevsparkDir + devspark_dir_file_count = $devsparkFileCount + has_documentation_dir = $hasDocumentationDir + has_constitution = $hasConstitution + constitution_principle_count = $principleCount + has_team_scripts_override = $hasTeamScriptsOverride + team_scripts_files = $teamScriptsFiles + spec_dirs = $specDirs + documentation_other_entries = $documentationOtherEntries + has_existing_archive = $hasExistingArchive + existing_archive_entries = $existingArchiveEntries + legacy_claude_commands = $legacyClaudeCommands + legacy_github_agents = $legacyGithubAgents + legacy_github_prompts = $legacyGithubPrompts + has_agents_registry = $hasAgentsRegistry + has_multi_app_config = $hasMultiAppConfig + devspark_reference_files = $devsparkReferenceFiles +} | ConvertTo-Json -Depth 10 -Compress diff --git a/.bold/scripts/powershell/collect-entry-path-context.ps1 b/.bold/scripts/powershell/collect-entry-path-context.ps1 new file mode 100644 index 0000000..b96f2cf --- /dev/null +++ b/.bold/scripts/powershell/collect-entry-path-context.ps1 @@ -0,0 +1,32 @@ +# Collector for bold.plan init. Emits deterministic facts about which entry +# path applies — legacy methodology install present, existing bold install, +# and repo file counts — so init doesn't have to re-derive them by hand. + +param([string]$Root) + +$ErrorActionPreference = 'Stop' + +if ($Root) { + $repoRoot = $Root +} else { + $repoRoot = git rev-parse --show-toplevel 2>$null + if (-not $repoRoot) { $repoRoot = (Get-Location).Path } +} + +$hasDevspark = Test-Path (Join-Path $repoRoot '.devspark') +$hasDocumentationDir = Test-Path (Join-Path $repoRoot '.documentation') +$hasBoldDocs = Test-Path (Join-Path $repoRoot 'bold-docs/backbone.md') + +$allFiles = Get-ChildItem -Path $repoRoot -File -Recurse -Force | + Where-Object { $_.FullName -notmatch '[\\/]\.git[\\/]' } + +$totalFiles = $allFiles.Count +$nonHiddenFiles = ($allFiles | Where-Object { $_.FullName -notmatch '[\\/]\.[^\\/]+[\\/]' -and $_.Name -notmatch '^\.' }).Count + +[ordered]@{ + has_devspark = $hasDevspark + has_documentation_dir = $hasDocumentationDir + has_bold_docs = $hasBoldDocs + total_files = $totalFiles + non_hidden_files = $nonHiddenFiles +} | ConvertTo-Json -Compress diff --git a/.bold/scripts/powershell/collect-gate-status.ps1 b/.bold/scripts/powershell/collect-gate-status.ps1 new file mode 100644 index 0000000..8e7f263 --- /dev/null +++ b/.bold/scripts/powershell/collect-gate-status.ps1 @@ -0,0 +1,32 @@ +# Collector for bold.build (default and status). Emits deterministic facts — +# per-feature ratified tier, backbone principle enforcement status, detected +# test-runner config, and working-tree cleanliness — so gates are decided +# from ground truth instead of re-derived by reading the tree by hand. + +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'lib/Common.ps1') + +$repoRoot = git rev-parse --show-toplevel 2>$null +if (-not $repoRoot) { $repoRoot = (Get-Location).Path } +$docsDir = Join-Path $repoRoot 'bold-docs' +Set-Location $repoRoot + +$activeFeatures = Get-ActiveFeatures -DocsDir $docsDir +$backbonePrinciples = Get-BackbonePrinciples -DocsDir $docsDir +$staleReferences = Get-StaleReferences -RepoRoot $repoRoot -DocsDir $docsDir + +$knownTestConfigs = @('jest.config.js', 'jest.config.ts', 'pytest.ini', 'tox.ini', '.mocharc.json', + '.mocharc.yml', 'karma.conf.js', 'phpunit.xml') +$testConfigPresent = @($knownTestConfigs | Where-Object { Test-Path $_ }) +$testConfigPresent += @(Get-ChildItem -Path . -Filter '*.Tests.csproj' -Depth 1 -File -ErrorAction SilentlyContinue | + ForEach-Object { $_.Name }) + +$hasUncommittedChanges = [bool](git status --porcelain 2>$null) + +[ordered]@{ + active_features = $activeFeatures + backbone_principles = $backbonePrinciples + stale_references = $staleReferences + test_config_present = $testConfigPresent + has_uncommitted_changes = $hasUncommittedChanges +} | ConvertTo-Json -Depth 10 -Compress diff --git a/.bold/scripts/powershell/collect-harvest-inventory.ps1 b/.bold/scripts/powershell/collect-harvest-inventory.ps1 new file mode 100644 index 0000000..a18bb70 --- /dev/null +++ b/.bold/scripts/powershell/collect-harvest-inventory.ps1 @@ -0,0 +1,32 @@ +# Collector for bold.ship harvest. Emits deterministic facts — active feature +# tiers/status, the file inventory inside each feature dir, and the current +# system/ doc set — so classification (durable vs work product) starts from +# a complete inventory instead of the agent re-listing directories. + +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'lib/Common.ps1') + +$repoRoot = git rev-parse --show-toplevel 2>$null +if (-not $repoRoot) { $repoRoot = (Get-Location).Path } +$docsDir = Join-Path $repoRoot 'bold-docs' + +$activeFeatures = Get-ActiveFeatures -DocsDir $docsDir +$systemDocs = Get-SystemDocs -RepoRoot $repoRoot -DocsDir $docsDir + +$featureFiles = @() +$featuresDir = Join-Path $docsDir 'features' +if (Test-Path $featuresDir) { + Get-ChildItem -Path $featuresDir -Directory | ForEach-Object { + $featureDir = $_ + $files = @(Get-ChildItem -Path $featureDir.FullName -File -Recurse | + ForEach-Object { $_.FullName.Substring($featureDir.FullName.Length + 1) -replace '\\', '/' } | + Sort-Object) + $featureFiles += [ordered]@{ id = $featureDir.Name; files = $files } + } +} + +[ordered]@{ + active_features = $activeFeatures + system_docs = $systemDocs + feature_files = $featureFiles +} | ConvertTo-Json -Depth 10 -Compress diff --git a/.bold/scripts/powershell/collect-pr-review-context.ps1 b/.bold/scripts/powershell/collect-pr-review-context.ps1 new file mode 100644 index 0000000..b173ef1 --- /dev/null +++ b/.bold/scripts/powershell/collect-pr-review-context.ps1 @@ -0,0 +1,28 @@ +# Collector for bold.ship address. Emits the current PR's number and review +# comments via the gh CLI, so responding to review feedback starts from the +# actual comment thread instead of a human re-pasting it. + +$ErrorActionPreference = 'Stop' + +$ghAvailable = [bool](Get-Command gh -ErrorAction SilentlyContinue) + +$prNumber = $null +$reviewComments = @() + +if ($ghAvailable) { + $number = gh pr view --json number -q .number 2>$null + if ($number) { + $prNumber = [int]$number + $raw = gh api "repos/{owner}/{repo}/pulls/$prNumber/comments" ` + --jq '[.[] | {author: .user.login, body: .body}]' 2>$null + if ($raw) { + $reviewComments = @(ConvertFrom-Json $raw) + } + } +} + +[ordered]@{ + gh_available = $ghAvailable + pr_number = $prNumber + review_comments = $reviewComments +} | ConvertTo-Json -Depth 10 -Compress diff --git a/.bold/scripts/powershell/collect-ship-context.ps1 b/.bold/scripts/powershell/collect-ship-context.ps1 new file mode 100644 index 0000000..4ddc713 --- /dev/null +++ b/.bold/scripts/powershell/collect-ship-context.ps1 @@ -0,0 +1,42 @@ +# Collector for bold.ship (default and review). Emits deterministic facts — +# branch position relative to the base branch, changed-file inventory, +# active feature tiers, and backbone status — so drafting or reviewing a PR +# starts from current, structured ground truth. + +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'lib/Common.ps1') + +$repoRoot = git rev-parse --show-toplevel 2>$null +if (-not $repoRoot) { $repoRoot = (Get-Location).Path } +$docsDir = Join-Path $repoRoot 'bold-docs' +Set-Location $repoRoot + +$baseBranch = 'main' +$currentBranch = git rev-parse --abbrev-ref HEAD 2>$null +if (-not $currentBranch) { $currentBranch = 'unknown' } + +$commitsAhead = 0 +$commitsBehind = 0 +$changedFiles = @() +$baseBranchExists = (git rev-parse --verify $baseBranch 2>$null) +if ($baseBranchExists -and $currentBranch -ne $baseBranch) { + $commitsAhead = [int](git rev-list --count "$baseBranch..HEAD" 2>$null) + $commitsBehind = [int](git rev-list --count "HEAD..$baseBranch" 2>$null) + $changedFiles = @(git diff --name-only "$baseBranch...HEAD" 2>$null | Where-Object { $_ }) +} + +$hasUncommittedChanges = [bool](git status --porcelain 2>$null) + +$activeFeatures = Get-ActiveFeatures -DocsDir $docsDir +$backbonePrinciples = Get-BackbonePrinciples -DocsDir $docsDir + +[ordered]@{ + base_branch = $baseBranch + current_branch = $currentBranch + commits_ahead = $commitsAhead + commits_behind = $commitsBehind + changed_files = $changedFiles + has_uncommitted_changes = $hasUncommittedChanges + active_features = $activeFeatures + backbone_principles = $backbonePrinciples +} | ConvertTo-Json -Depth 10 -Compress diff --git a/.bold/scripts/powershell/collect-triage-context.ps1 b/.bold/scripts/powershell/collect-triage-context.ps1 new file mode 100644 index 0000000..d8dd1ab --- /dev/null +++ b/.bold/scripts/powershell/collect-triage-context.ps1 @@ -0,0 +1,30 @@ +# Collector for bold.plan (default/triage). Emits deterministic JSON facts — +# system doc inventory, active feature tiers/status, backbone principle +# status, and the project genome — so the triage prompt reasons over +# structured ground truth instead of re-deriving it from prose. + +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'lib/Common.ps1') + +$repoRoot = git rev-parse --show-toplevel 2>$null +if (-not $repoRoot) { $repoRoot = (Get-Location).Path } +$docsDir = Join-Path $repoRoot 'bold-docs' + +$systemDocs = Get-SystemDocs -RepoRoot $repoRoot -DocsDir $docsDir +$activeFeatures = Get-ActiveFeatures -DocsDir $docsDir +$backbonePrinciples = Get-BackbonePrinciples -DocsDir $docsDir +$staleReferences = Get-StaleReferences -RepoRoot $repoRoot -DocsDir $docsDir + +$genome = $null +$genomeFile = Join-Path $docsDir 'project.json' +if (Test-Path $genomeFile) { + $genome = Get-Content $genomeFile -Raw | ConvertFrom-Json +} + +[ordered]@{ + system_docs = $systemDocs + active_features = $activeFeatures + backbone_principles = $backbonePrinciples + stale_references = $staleReferences + genome = $genome +} | ConvertTo-Json -Depth 10 -Compress diff --git a/.bold/scripts/powershell/compose-layers.ps1 b/.bold/scripts/powershell/compose-layers.ps1 new file mode 100644 index 0000000..9165382 --- /dev/null +++ b/.bold/scripts/powershell/compose-layers.ps1 @@ -0,0 +1,96 @@ +# Collector for bold.plan init's greenfield questionnaire. Given a starter +# (+ optional stacks/flavors) or a kit, resolves the full layer set, merges +# each layer's questions.json in composition order (starter -> stacks -> +# flavors), and flags any question `id` that appears in more than one layer +# as a conflict. A kit's answer_defaults pass through unmodified as their own +# field -- applying the override is part of *asking*, which belongs to +# bold.plan init, not to this collector, which only measures and reports. +# See source/starters/FORMAT.md. +param( + [string]$LayersRoot, + [string]$Kit, + [string]$Starter, + [string[]]$Stacks = @(), + [string[]]$Flavors = @() +) + +$ErrorActionPreference = 'Stop' + +$repoRoot = git rev-parse --show-toplevel 2>$null +if (-not $repoRoot) { $repoRoot = (Get-Location).Path } +if (-not $LayersRoot) { $LayersRoot = Join-Path $repoRoot 'source' } + +$kitName = $null +$kitAnswerDefaults = [ordered]@{} + +if ($Kit) { + $kitName = $Kit + $kitPath = Join-Path $LayersRoot "kits/$Kit.json" + if (-not (Test-Path $kitPath)) { throw "Kit not found: $kitPath" } + $kitDef = Get-Content $kitPath -Raw | ConvertFrom-Json + $Starter = $kitDef.composition.starter + $Stacks = @($kitDef.composition.stacks) + $Flavors = @($kitDef.composition.flavors) + if ($kitDef.answer_defaults) { + $kitDef.answer_defaults.PSObject.Properties | ForEach-Object { $kitAnswerDefaults[$_.Name] = $_.Value } + } +} + +if (-not $Starter) { throw "Either -Kit or -Starter is required" } + +$layerRefs = [System.Collections.Generic.List[object]]::new() +$layerRefs.Add([pscustomobject]@{ type = 'starter'; name = $Starter }) +foreach ($s in $Stacks) { $layerRefs.Add([pscustomobject]@{ type = 'stack'; name = $s }) } +foreach ($f in $Flavors) { $layerRefs.Add([pscustomobject]@{ type = 'flavor'; name = $f }) } + +$composedQuestions = [System.Collections.Generic.List[object]]::new() +$backboneFragments = [System.Collections.Generic.List[object]]::new() + +foreach ($ref in $layerRefs) { + $dir = Join-Path $LayersRoot "$($ref.type)s/$($ref.name)" + $source = "$($ref.type):$($ref.name)" + + $questionsPath = Join-Path $dir 'questions.json' + if (Test-Path $questionsPath) { + $questions = Get-Content $questionsPath -Raw | ConvertFrom-Json + foreach ($q in @($questions)) { + $entry = [ordered]@{} + $q.PSObject.Properties | ForEach-Object { $entry[$_.Name] = $_.Value } + $entry['source'] = $source + $composedQuestions.Add([pscustomobject]$entry) + } + } + + $backbonePath = Join-Path $dir 'backbone.md' + if (Test-Path $backbonePath) { + # Trim exactly one trailing newline to match bash's $(...) command + # substitution, which always strips trailing newlines -- keeps both + # collectors byte-identical on this field. + $backboneFragments.Add([ordered]@{ + source = $source + content = ((Get-Content $backbonePath -Raw) -replace '[\r\n]+$', '') + }) + } +} + +# Mechanical conflict detection: any id appearing in more than one layer. +$conflicts = [System.Collections.Generic.List[object]]::new() +$composedQuestions | Group-Object -Property id | Where-Object { $_.Count -gt 1 } | ForEach-Object { + $conflicts.Add([ordered]@{ + id = $_.Name + sources = @($_.Group | ForEach-Object { $_.source }) + }) +} + +[ordered]@{ + composition = [ordered]@{ + kit = $kitName + starter = $Starter + stacks = @($Stacks) + flavors = @($Flavors) + } + composed_questions = @($composedQuestions) + conflicts = @($conflicts) + backbone_fragments = @($backboneFragments) + kit_answer_defaults = $kitAnswerDefaults +} | ConvertTo-Json -Depth 10 -Compress diff --git a/.bold/scripts/powershell/lib/Common.ps1 b/.bold/scripts/powershell/lib/Common.ps1 new file mode 100644 index 0000000..2f03189 --- /dev/null +++ b/.bold/scripts/powershell/lib/Common.ps1 @@ -0,0 +1,128 @@ +# Shared helpers for bold's PowerShell collector scripts. + +# Filesystem-safe slug identifying the current contributor for +# .bold-user/{slug}/ (bold-tool-plan.md §17 #4 — committed, per-user +# tier). Falls back from git user.name to the email's local part, then +# to "shared" if neither is configured. -Root (optional) scopes the git +# config lookup to a specific repo instead of the caller's cwd -- required, +# not cosmetic: a script invoked with -Root pointing elsewhere must not +# silently read the invoking shell's own git identity instead. +function Get-BoldUserSlug { + param([string]$Root) + if ($Root) { + $name = git -C $Root config user.name 2>$null + } else { + $name = git config user.name 2>$null + } + if (-not $name) { + if ($Root) { + $email = git -C $Root config user.email 2>$null + } else { + $email = git config user.email 2>$null + } + if ($email) { $name = $email.Split('@')[0] } + } + if (-not $name) { $name = 'shared' } + $slug = ($name.ToLower() -replace '[^a-z0-9]+', '-').Trim('-') + if (-not $slug) { $slug = 'shared' } + return $slug +} + +# Emits an array of {principle,reason,ratified_by,date}, one per +# `- Waiver: ...` line in the given spec file. See +# source/commands/WAIVERS.md for the line format. +function Get-WaiversForSpec { + param([string]$SpecPath) + $waivers = @() + if (Test-Path $SpecPath) { + Get-Content $SpecPath | Select-String '^- Waiver: (.+)$' | ForEach-Object { + $line = $_.Matches.Groups[1].Value + $principleMatch = [regex]::Match($line, 'principle=(\d+)') + $reasonMatch = [regex]::Match($line, 'reason="([^"]*)"') + $ratifiedByMatch = [regex]::Match($line, 'ratified_by="([^"]*)"') + $dateMatch = [regex]::Match($line, 'date=([\d-]+)') + $waivers += [ordered]@{ + principle = if ($principleMatch.Success) { [int]$principleMatch.Groups[1].Value } else { $null } + reason = $reasonMatch.Groups[1].Value + ratified_by = $ratifiedByMatch.Groups[1].Value + date = $dateMatch.Groups[1].Value + } + } + } + return ,$waivers +} + +function Get-ActiveFeatures { + param([string]$DocsDir) + $features = @() + $featuresDir = Join-Path $DocsDir 'features' + if (Test-Path $featuresDir) { + Get-ChildItem -Path $featuresDir -Directory | ForEach-Object { + $specPath = Join-Path $_.FullName 'spec.md' + if (Test-Path $specPath) { + $content = Get-Content $specPath + $status = ($content | Select-String '^\*\*Status\*\*: (.+)$' | Select-Object -First 1).Matches.Groups[1].Value + $tier = ($content | Select-String '^\*\*Tier\*\*: (.+)$' | Select-Object -First 1).Matches.Groups[1].Value + $features += [ordered]@{ + id = $_.Name + status = if ($status) { $status } else { 'unknown' } + tier = if ($tier) { $tier } else { 'unknown' } + waivers = Get-WaiversForSpec -SpecPath $specPath + } + } + } + } + return ,$features +} + +function Get-SystemDocs { + param([string]$RepoRoot, [string]$DocsDir) + $docs = @() + $systemDir = Join-Path $DocsDir 'system' + if (Test-Path $systemDir) { + $docs = @(Get-ChildItem -Path $systemDir -File -Recurse | + Where-Object { $_.Name -ne '.gitkeep' } | + ForEach-Object { $_.FullName.Substring($RepoRoot.Length + 1) -replace '\\', '/' } | + Sort-Object) + } + return ,$docs +} + +# Emits an array of {doc,reference}, one per backtick-quoted, path-shaped +# reference in a bold-docs/system/ doc that doesn't resolve to a real file. +# Scoped to system/ only (§13 ambient staleness detection) -- feature specs +# are expected to reference code that doesn't exist yet. +function Get-StaleReferences { + param([string]$RepoRoot, [string]$DocsDir) + $stale = @() + $systemDir = Join-Path $DocsDir 'system' + if (Test-Path $systemDir) { + Get-ChildItem -Path $systemDir -File -Recurse | Where-Object { $_.Name -ne '.gitkeep' } | ForEach-Object { + $relDoc = $_.FullName.Substring($RepoRoot.Length + 1) -replace '\\', '/' + $refs = [regex]::Matches((Get-Content $_.FullName -Raw), '`([A-Za-z0-9_.-]+(?:/[A-Za-z0-9_.-]+)+)`') | + ForEach-Object { $_.Groups[1].Value } | Select-Object -Unique + foreach ($ref in $refs) { + if ($ref -match '^https?://' -or $ref -match '\*') { continue } + $refPath = Join-Path $RepoRoot $ref + if (-not (Test-Path $refPath)) { + $stale += [ordered]@{ doc = $relDoc; reference = $ref } + } + } + } + } + return ,$stale +} + +function Get-BackbonePrinciples { + param([string]$DocsDir) + $principles = @() + $backboneFile = Join-Path $DocsDir 'backbone.md' + if (Test-Path $backboneFile) { + $n = 0 + Get-Content $backboneFile | Select-String '^\s*\*\*Status\*\*: (.+)$' | ForEach-Object { + $n++ + $principles += [ordered]@{ n = $n; status = $_.Matches.Groups[1].Value } + } + } + return ,$principles +} diff --git a/.bold/stacks/dotnet/backbone.md b/.bold/stacks/dotnet/backbone.md new file mode 100644 index 0000000..47ad427 --- /dev/null +++ b/.bold/stacks/dotnet/backbone.md @@ -0,0 +1,7 @@ +1. **Nullable reference types are enabled** — no project opts back into implicit non-null. + + **Status**: enforced + +2. **Minimal APIs by default** — a controller-based MVC endpoint needs a stated reason, not just habit. + + **Status**: adopting diff --git a/.bold/stacks/dotnet/questions.json b/.bold/stacks/dotnet/questions.json new file mode 100644 index 0000000..b251d5d --- /dev/null +++ b/.bold/stacks/dotnet/questions.json @@ -0,0 +1,4 @@ +[ + {"id":"dotnet.target_framework","prompt":"Target framework?","type":"text","default":"net9.0","when":"dotnet","maps_to":"layer.dotnet.target_framework"}, + {"id":"dotnet.test_framework","prompt":"Test framework?","type":"select","options":["xunit","nunit"],"default":"xunit","when":"dotnet","maps_to":"layer.dotnet.test_framework"} +] diff --git a/.bold/stacks/dotnet/stack.json b/.bold/stacks/dotnet/stack.json new file mode 100644 index 0000000..35b7eab --- /dev/null +++ b/.bold/stacks/dotnet/stack.json @@ -0,0 +1 @@ +{"name":"dotnet","version":"1.0.0","description":".NET backend — minimal APIs or ASP.NET Core MVC."} diff --git a/.bold/stacks/node/backbone.md b/.bold/stacks/node/backbone.md new file mode 100644 index 0000000..1171274 --- /dev/null +++ b/.bold/stacks/node/backbone.md @@ -0,0 +1,7 @@ +1. **TypeScript, strict mode.** No new `.js` source files; `any` needs a stated reason, not a shortcut. + + **Status**: enforced + +2. **ESM modules only** — no new CommonJS `require`/`module.exports`. + + **Status**: enforced diff --git a/.bold/stacks/node/questions.json b/.bold/stacks/node/questions.json new file mode 100644 index 0000000..5af7a73 --- /dev/null +++ b/.bold/stacks/node/questions.json @@ -0,0 +1,4 @@ +[ + {"id":"node.package_manager","prompt":"Package manager?","type":"select","options":["npm","pnpm","yarn"],"default":"npm","when":"node","maps_to":"layer.node.package_manager"}, + {"id":"node.test_framework","prompt":"Test framework?","type":"select","options":["vitest","jest"],"default":"vitest","when":"node","maps_to":"layer.node.test_framework"} +] diff --git a/.bold/stacks/node/stack.json b/.bold/stacks/node/stack.json new file mode 100644 index 0000000..b7becd4 --- /dev/null +++ b/.bold/stacks/node/stack.json @@ -0,0 +1 @@ +{"name":"node","version":"1.0.0","description":"Node.js backend — TypeScript by default."} diff --git a/.bold/stacks/react/backbone.md b/.bold/stacks/react/backbone.md new file mode 100644 index 0000000..7bd1bd9 --- /dev/null +++ b/.bold/stacks/react/backbone.md @@ -0,0 +1,7 @@ +1. **Functional components only** — no new class components. + + **Status**: enforced + +2. **Tests live next to the component they test**, not in a parallel mirrored tree. + + **Status**: adopting diff --git a/.bold/stacks/react/questions.json b/.bold/stacks/react/questions.json new file mode 100644 index 0000000..0b7b1da --- /dev/null +++ b/.bold/stacks/react/questions.json @@ -0,0 +1,4 @@ +[ + {"id":"react.router","prompt":"Routing library?","type":"select","options":["tanstack","react-router"],"default":"react-router","when":"react","maps_to":"layer.react.router"}, + {"id":"react.state","prompt":"State management?","type":"select","options":["context","zustand","redux"],"default":"context","when":"react","maps_to":"layer.react.state"} +] diff --git a/.bold/stacks/react/stack.json b/.bold/stacks/react/stack.json new file mode 100644 index 0000000..e540294 --- /dev/null +++ b/.bold/stacks/react/stack.json @@ -0,0 +1 @@ +{"name":"react","version":"1.0.0","description":"React frontend."} diff --git a/.bold/starters/FORMAT.md b/.bold/starters/FORMAT.md new file mode 100644 index 0000000..286ddc5 --- /dev/null +++ b/.bold/starters/FORMAT.md @@ -0,0 +1,92 @@ +# Layer Package Format + +The schema every package under `source/{starters,stacks,flavors,kits}/` follows. Read this before adding a new starter, stack, flavor, or kit. Twin of `source/commands/FORMAT.md` — that one governs commands, this one governs the layer system behind `bold.plan init`'s greenfield questionnaire (bold-tool-plan.md §11, §12). + +## Layer types + +| Layer | Directory | Owns | Compatibility | +|---|---|---|---| +| **Starter** | `starters/{name}/` | Spec template, domain questionnaire, worked example, lifecycle interpretation | Declares `compatible_stacks` — the only layer that declares compatibility | +| **Stack** | `stacks/{name}/` | Tech-specific backbone principles, questionnaire branches | Doesn't redeclare compatibility back to starters | +| **Flavor** | `flavors/{name}/` | Targeted principle/convention overlays | Generic — compatible with any starter/stack | +| **Kit** | `kits/{name}.json` | Saved composition + answer defaults — pure metadata, one file, ~10 lines | N/A — a kit *is* a fixed composition | + +## Package contents + +``` +starters/{name}/ +├── starter.json # required +├── questions.json # optional — omit if the starter asks nothing of its own +├── backbone.md # optional — pre-answered principle fragment +├── spec-template.md # required — domain-shaped spec skeleton +├── example/ # required — one complete worked spec→plan→tasks trail +└── system-seed/ # optional — starting system/ taxonomy + +stacks/{name}/ +├── stack.json # required +├── questions.json # optional +└── backbone.md # optional + +flavors/{name}/ +├── flavor.json # required +├── questions.json # optional +└── backbone.md # optional + +kits/{name}.json # required, single file +``` + +`spec-template.md`, `example/`, and `system-seed/` are starter-only — per the layer-types table, stacks and flavors don't own a spec shape, they only contribute principles and questions to whatever starter they're composed with. + +## `starter.json` / `stack.json` / `flavor.json` + +```json +{ + "name": "api", + "version": "1.0.0", + "description": "One line — what this starter/stack/flavor is for.", + "compatible_stacks": ["dotnet", "node"] +} +``` + +`compatible_stacks` is required on `starter.json` only; omit it entirely from `stack.json`/`flavor.json`. + +## `kits/{name}.json` + +```json +{ + "name": "bold-webapp", + "version": "1.0.0", + "description": "Fullstack web app: .NET API + React frontend, Tailwind styling.", + "composition": { "starter": "fullstack", "stacks": ["dotnet", "react"], "flavors": ["tailwind"] }, + "answer_defaults": { "api.style": "REST" } +} +``` + +A kit doesn't own questions or backbone content of its own — it's a named preset over an existing starter+stacks+flavors composition, plus answer defaults that override whatever those layers' own `questions.json` defaults say. `compose-layers` passes `answer_defaults` through as its own `kit_answer_defaults` field rather than rewriting each question's `default` in place — applying the override is part of *asking*, which belongs to `bold.plan init`, not to a collector that only measures and reports. + +## `questions.json` + +An array of question objects, one shared shape for every layer. **Every object must be flat — no nested objects anywhere in this file** (arrays of strings are fine). This isn't just a style preference: `compose-layers.sh` parses `questions.json` without a JSON library, using the same single-line regex extraction the rest of Bold's bash tooling already relies on, and that only works because there's never more than one level of `{}` nesting to worry about. + +```json +[ + { "id": "api.style", "prompt": "REST or GraphQL?", "type": "select", "options": ["REST", "GraphQL"], "default": "REST", "maps_to": "core.api.style" }, + { "id": "dotnet.target_framework", "prompt": "Target framework?", "type": "text", "default": "net9.0", "when": "dotnet", "maps_to": "layer.dotnet.target_framework" } +] +``` + +| Field | Required | Notes | +|---|---|---| +| `id` | yes | **Must be unique across the whole composed set for a given run — not just within one layer.** This is the one mechanical conflict `compose-layers` detects. Namespace it (`dotnet.target_framework`, not `target_framework`) so composing with other layers never collides. | +| `prompt` | yes | The question text. | +| `type` | yes | `text`, `select`, `boolean`, or `number`. | +| `options` | when `type: select` | Enumerated choices — a flat array of strings. | +| `default` | yes | Every question has one — Enter-mashing through the whole questionnaire yields a valid project (bold-tool-plan.md §10.2). | +| `when` | no | A layer name (starter/stack/flavor) that must be part of the composition for this question to apply — a flat string, not an object. Omit for unconditional questions. | +| `maps_to` | yes | The genome key (`core.*` or `layer.{layer_id}.*`) this answer populates — see §11's namespace rules. | + +## Composition & conflicts + +Merge order: **starter → stack(s) → flavor(s) → kit answer-defaults** (bold-tool-plan.md §12.3). `compose-layers` (the collector; see `source/scripts/{bash,powershell}/compose-layers.{sh,ps1}`) concatenates each layer's `questions.json` in that order and its `backbone.md` with provenance tags. + +**Conflict scope is deliberately narrow — mechanical only.** The collector flags a conflict when the same `id` appears in more than one layer's `questions.json`; it does not attempt to detect semantic conflicts between backbone fragments (e.g. two layers implying contradictory principles). That's a judgment call for the human/agent during `bold.plan init`'s ratification step, not something a deterministic script should decide. Per Collect/Reason/Ratify, the collector only *reports* — `bold.plan init` is what actually halts and asks when `conflicts` is non-empty, the same pattern as every other halt condition in the neutral prompts. diff --git a/.bold/starters/api/backbone.md b/.bold/starters/api/backbone.md new file mode 100644 index 0000000..0f8a230 --- /dev/null +++ b/.bold/starters/api/backbone.md @@ -0,0 +1,7 @@ +1. **Every mutating endpoint requires authentication** unless `api.auth` is explicitly `none`. + + **Status**: enforced + +2. **A shipped API contract is versioned before it changes incompatibly** — a breaking change gets a new version, never an in-place mutation of a contract a caller already depends on. + + **Status**: enforced diff --git a/.bold/starters/api/example/spec.md b/.bold/starters/api/example/spec.md new file mode 100644 index 0000000..23ccfe1 --- /dev/null +++ b/.bold/starters/api/example/spec.md @@ -0,0 +1,44 @@ +# List Active Subscriptions + +> **TL;DR for the Product Owner** +> *What*: Adds an endpoint that returns a customer's currently active subscriptions. +> *Why*: Support staff currently have to query the database directly to answer "what is this customer paying for right now" — this makes it a normal API call. +> *Status*: Complete. +> *Decision needed*: none. + +**Tier**: Feature +**Status**: Complete + +## Intent + +Add `GET /customers/{id}/subscriptions?status=active` so any authenticated caller can retrieve a customer's active subscriptions without direct database access. Feature tier because this is a new contract a future caller will depend on (backbone principle 2: a shipped contract is versioned before it changes incompatibly — this is the *first* version, so nothing to version against yet, but the shape chosen here is what future versioning has to work around). + +## Contract + +- **Method & path**: `GET /customers/{id}/subscriptions` +- **Auth**: per `core.api.auth` (`oidc`) — caller must hold the `customers:read` scope +- **Request**: path param `id` (customer ID); optional query param `status` (`active` | `canceled` | `all`, default `active`) +- **Response** (200): `{ "customer_id": "...", "subscriptions": [{ "id": "...", "plan": "...", "started_at": "...", "status": "active" }] }` +- **Errors**: 404 if `id` doesn't exist; 403 if the caller lacks `customers:read`; 400 if `status` isn't one of the enumerated values +- **Versioning**: this is v1 of this contract — no prior version to be compatible with + +## Acceptance Criteria + +- [x] `GET /customers/{id}/subscriptions` with no `status` param returns only active subscriptions +- [x] `status=all` returns every subscription regardless of state +- [x] An unknown customer `id` returns 404, not an empty list +- [x] A caller without `customers:read` gets 403, not a 200 with empty results +- [x] An invalid `status` value returns 400 naming the allowed values + +## Affected Files + +- `src/routes/customers/subscriptions.{ext}` (new) +- `src/auth/scopes.{ext}` (add `customers:read` if not already defined) + +## Tasks + +- [x] T001 Define the response shape and error cases as a contract test fixture, `src/routes/customers/subscriptions.contract.test.{ext}` +- [x] T002 [P] Implement the route handler, `src/routes/customers/subscriptions.{ext}` +- [x] T003 [P] Wire the `customers:read` scope check into the route, `src/auth/scopes.{ext}` +- [x] T004 Implement the `status` query param validation and the 400 case, `src/routes/customers/subscriptions.{ext}` +- [x] T005 Write the 404/403/400 test cases against the contract fixture from T001 diff --git a/.bold/starters/api/questions.json b/.bold/starters/api/questions.json new file mode 100644 index 0000000..f41870a --- /dev/null +++ b/.bold/starters/api/questions.json @@ -0,0 +1,5 @@ +[ + {"id":"api.style","prompt":"REST or GraphQL?","type":"select","options":["REST","GraphQL"],"default":"REST","maps_to":"core.api.style"}, + {"id":"api.auth","prompt":"Authentication model?","type":"select","options":["none","api_key","oidc"],"default":"oidc","maps_to":"core.api.auth"}, + {"id":"api.versioning","prompt":"API versioning scheme?","type":"select","options":["url_path","header","none"],"default":"url_path","maps_to":"core.api.versioning"} +] diff --git a/.bold/starters/api/spec-template.md b/.bold/starters/api/spec-template.md new file mode 100644 index 0000000..871d962 --- /dev/null +++ b/.bold/starters/api/spec-template.md @@ -0,0 +1,35 @@ +# {Endpoint or Capability Name} + +> **TL;DR for the Product Owner** +> *What*: {one sentence — what can a caller now do} +> *Why*: {one sentence — what problem this solves} +> *Status*: Planning — no code written yet. +> *Decision needed*: {only if true} + +**Tier**: {Patch | Quick | Feature} +**Status**: Draft + +## Intent + +{What this endpoint/capability does and why it's shaped this way.} + +## Contract + +- **Method & path**: `{METHOD} /{path}` +- **Auth**: {per `core.api.auth` — none / api_key / oidc; state the required scope/role if any} +- **Request**: {shape, required vs. optional fields} +- **Response**: {success shape; status code} +- **Errors**: {each error case with its status code and when it fires} +- **Versioning**: {if this changes an existing contract, per backbone principle 2 — new version, not in-place mutation} + +## Acceptance Criteria + +- [ ] {Observable behavior a test could check} + +## Affected Files + +- {} + +## Tasks + +- [ ] T1: {} diff --git a/.bold/starters/api/starter.json b/.bold/starters/api/starter.json new file mode 100644 index 0000000..89e4824 --- /dev/null +++ b/.bold/starters/api/starter.json @@ -0,0 +1 @@ +{"name":"api","version":"1.0.0","description":"A standalone API service, no frontend.","compatible_stacks":["dotnet","node"]} diff --git a/.bold/starters/fullstack/backbone.md b/.bold/starters/fullstack/backbone.md new file mode 100644 index 0000000..c7f31e5 --- /dev/null +++ b/.bold/starters/fullstack/backbone.md @@ -0,0 +1,7 @@ +1. **The frontend never holds a third-party secret.** Any call that needs one goes through the backend, which holds the secret server-side. + + **Status**: enforced + +2. **A shipped API contract is versioned before it changes incompatibly** — same rule as the `api` starter: a breaking change gets a new version, never an in-place mutation of a contract the frontend already depends on. + + **Status**: enforced diff --git a/.bold/starters/fullstack/example/spec.md b/.bold/starters/fullstack/example/spec.md new file mode 100644 index 0000000..29636fc --- /dev/null +++ b/.bold/starters/fullstack/example/spec.md @@ -0,0 +1,43 @@ +# Dashboard: Today's Order Count Widget + +> **TL;DR for the Product Owner** +> *What*: Adds a widget to the dashboard showing how many orders have come in today. +> *Why*: Staff currently open a separate reporting tool every morning just to check this one number. +> *Status*: Complete. +> *Decision needed*: none. + +**Tier**: Feature +**Status**: Complete + +## Intent + +Add a dashboard widget that shows today's order count, refreshed on page load (not real-time — that's a larger feature this doesn't need to become). Feature tier because it introduces a new API contract the frontend depends on (backbone principle 2, same as the `api` starter) and a new piece of cross-module flow (UI → API → data). + +## Contract + +- **UI**: a card on the dashboard showing a number and the label "Orders today"; a loading state while the count is fetched; an inline error state (not a blocking one) if the fetch fails +- **API**: `GET /reports/orders/today` — returns `{ "count": 42, "as_of": "2026-07-09T14:00:00Z" }`. Auth per `core.api.auth` (`oidc`), same session as the rest of the dashboard, no new scope needed (read-only, already-visible data) +- **Data**: counts orders where `created_at` falls within the caller's local "today" — the API takes a `tz` query param (IANA timezone name) so "today" means the same thing to the user as it does on their clock, not the server's +- **Errors**: if the count query fails, the API returns 500; the widget shows "Couldn't load" with a retry button rather than crashing the dashboard + +## Acceptance Criteria + +- [x] Widget shows the correct count for orders created today in the caller's timezone +- [x] Missing/invalid `tz` falls back to UTC rather than erroring +- [x] Widget shows a loading state while the request is in flight +- [x] A failed request shows an inline retry, not a broken dashboard +- [x] Widget re-fetches on page load, not on a timer (explicitly out of scope: live updates) + +## Affected Files + +- `src/api/routes/reports/orders-today.{ext}` (new) +- `src/web/components/dashboard/OrdersTodayWidget.{ext}` (new) +- `src/web/components/dashboard/Dashboard.{ext}` (add the widget) + +## Tasks + +- [x] T001 Define the `/reports/orders/today` response contract as a fixture, `src/api/routes/reports/orders-today.contract.test.{ext}` +- [x] T002 [P] Implement the API route and the timezone-aware "today" query, `src/api/routes/reports/orders-today.{ext}` +- [x] T003 [P] Build `OrdersTodayWidget` with loading/error/success states, `src/web/components/dashboard/OrdersTodayWidget.{ext}` +- [x] T004 Wire the widget into `Dashboard`, `src/web/components/dashboard/Dashboard.{ext}` (depends on T003) +- [x] T005 Test the invalid/missing `tz` fallback against the contract fixture from T001 diff --git a/.bold/starters/fullstack/questions.json b/.bold/starters/fullstack/questions.json new file mode 100644 index 0000000..ad13409 --- /dev/null +++ b/.bold/starters/fullstack/questions.json @@ -0,0 +1,4 @@ +[ + {"id":"fullstack.api_style","prompt":"REST or GraphQL for the API layer?","type":"select","options":["REST","GraphQL"],"default":"REST","maps_to":"core.api.style"}, + {"id":"fullstack.auth","prompt":"Authentication model?","type":"select","options":["none","oidc"],"default":"oidc","maps_to":"core.api.auth"} +] diff --git a/.bold/starters/fullstack/spec-template.md b/.bold/starters/fullstack/spec-template.md new file mode 100644 index 0000000..b762de0 --- /dev/null +++ b/.bold/starters/fullstack/spec-template.md @@ -0,0 +1,34 @@ +# {Feature Name} + +> **TL;DR for the Product Owner** +> *What*: {one sentence — what a user can now do end to end} +> *Why*: {one sentence — what problem this solves} +> *Status*: Planning — no code written yet. +> *Decision needed*: {only if true} + +**Tier**: {Patch | Quick | Feature} +**Status**: Draft + +## Intent + +{What this feature does, end to end — UI through to data.} + +## Contract + +- **UI**: {what the user sees/does} +- **API**: `{METHOD} /{path}` — {request/response shape, auth requirement per `core.api.auth`} +- **Data**: {what's read or written, and where} +- **Errors**: {each error case, and what the UI shows for it} + +## Acceptance Criteria + +- [ ] {Observable behavior a test could check, UI side} +- [ ] {Observable behavior a test could check, API side} + +## Affected Files + +- {} + +## Tasks + +- [ ] T1: {} diff --git a/.bold/starters/fullstack/starter.json b/.bold/starters/fullstack/starter.json new file mode 100644 index 0000000..0cc4293 --- /dev/null +++ b/.bold/starters/fullstack/starter.json @@ -0,0 +1 @@ +{"name":"fullstack","version":"1.0.0","description":"A web app with both an API backend and a frontend.","compatible_stacks":["dotnet","node","react"]} diff --git a/.bold/version.json b/.bold/version.json new file mode 100644 index 0000000..a165236 --- /dev/null +++ b/.bold/version.json @@ -0,0 +1 @@ +{"installed_version":"0.1.0","installed_at":"2026-07-09T22:19:50Z","source":"https://ambitious-hill-09fe7f810.7.azurestaticapps.net"} \ No newline at end of file diff --git a/.claude/commands/devspark.add-application.md b/.claude/commands/devspark.add-application.md deleted file mode 100644 index 6cd0fb9..0000000 --- a/.claude/commands/devspark.add-application.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -description: Register a new application in the multi-app repository registry with guided metadata collection and automatic scaffolding. ---- - -## User Input - -```text -$ARGUMENTS -``` - -You **MUST** consider the user input before proceeding (if not empty). - -## Outline - -Register a new application in the DevSpark multi-app registry at `.documentation/devspark.json`. - -1. **Collect application metadata** from the user input or interactively: - - `id`: Unique, lowercase, path-safe identifier (e.g., `payments-api`) - - `name`: Human-readable application name - - `path`: Relative path from repo root (e.g., `apps/payments-api`) - - `kind`: Application type (e.g., `runtime-api`, `web-client`, `web-admin`, `library`, `qa-harness`) - - `purpose`: One-line description of the application's role - - `runtime`: Technology/framework (e.g., `dotnet`, `react`, `node`) - - `owner`: Team or individual responsible - - `criticality`: `high`, `medium`, or `low` - - `inherits`: List of profile names to inherit (must exist in registry) - - `dependsOn`: List of app IDs this app depends on (must exist in registry) - -2. **Validate inputs**: - - Check that the `id` is not already registered (fail with duplicate error if so) - - Check that all `inherits` profile references exist in the registry - - Check that all `dependsOn` app references exist in the registry - - Check that the `path` does not conflict with existing registered app paths - -3. **Update the registry**: - - Add the new application entry to the `apps` array in `.documentation/devspark.json` - - Ensure the registry passes full validation after the addition - -4. **Scaffold the application documentation** (always performed): - - Create `{path}/.documentation/` with standard subdirectories: - - `memory/` — for app-specific constitution - - `commands/` — for app-specific command overrides - - `scripts/` — for app-specific script overrides - - `templates/` — for app-specific template overrides - - `specs/` — for app-scoped feature specifications - - Do NOT create or modify `.devspark/` - -5. **Report results**: - - Show the new registry entry - - Confirm scaffolded directories - - Print scope summary - -## Constraints - -- If the registry file does not exist, create it with `version: 1`, `mode: "multi-app"`, empty `profiles`, and the new app as the first entry -- If the `id` already exists, fail with a clear duplicate error and do not modify the registry -- The command MUST NOT modify `.devspark/` (ownership boundary) diff --git a/.claude/commands/devspark.address-pr-review.md b/.claude/commands/devspark.address-pr-review.md deleted file mode 100644 index d5c6e7d..0000000 --- a/.claude/commands/devspark.address-pr-review.md +++ /dev/null @@ -1,171 +0,0 @@ ---- -description: Address open PR review findings with enforced commit isolation between code fixes and review-file updates -handoffs: - - label: Re-Review Updated PR - agent: devspark.pr-review - prompt: Run /devspark.pr-review UPDATE for this PR after fixes are committed ---- - -## User Input - -```text -$ARGUMENTS -``` - -You **MUST** consider the user input before proceeding (if not empty). - -## Overview - -This command is the **author-side companion** to `/devspark.pr-review`. It helps you address open findings in `/.documentation/specs/pr-review/pr-{PR_ID}.md` while enforcing commit isolation: - -1. Commit code fixes first. -2. Commit review-file updates second. - -**IMPORTANT**: The staging gates are mechanical and mandatory. Do not bypass them. - -## Prerequisites - -- Existing PR review file at `/.documentation/specs/pr-review/pr-{PR_ID}.md` -- Git repository with the PR source branch checked out -- PowerShell 7+ (`pwsh`) available for the gate helper script - -## Outline - -### Phase 0 — Load context - -> **Script Resolution**: Before running `.devspark/scripts/powershell/address-pr-review.ps1 -PrId $ARGUMENTS -Json`, apply the 2-tier override check for PowerShell only — if `.documentation/scripts/powershell/address-pr-review.ps1` exists on disk, run that file instead, preserving all arguments. Team override in `.documentation/scripts/powershell/` takes priority over `.devspark/scripts/powershell/`. - -1. Run `.devspark/scripts/powershell/address-pr-review.ps1 -PrId $ARGUMENTS -Json` with `-PrId {PR_ID} -Json`. -2. Fail fast if `/.documentation/specs/pr-review/pr-{PR_ID}.md` is missing. -3. Parse open findings from checklist lines matching: - - `- [ ] **C-##**` - - `- [ ] **H-##**` - - `- [ ] **M-##**` - - `- [ ] **L-##**` - - `- [ ] **CON-##**` -4. Confirm current branch equals the PR source branch. Refuse if mismatched. -5. Capture `git status --short`. -6. **Refuse to proceed** if any staged path matches `.documentation/specs/pr-review/pr-*.md`. - -If no open findings remain, print: `Nothing to address.` and stop. - -### Phase 1 — Plan - -1. Render open findings as a checklist with severity badges. -2. Ask which findings to address this iteration (`all` allowed). -3. Build an internal todo list with one item per selected finding. - -### Phase 2 — Fix loop (per finding) - -For each selected finding: - -1. Read the cited file/lines and confirm the issue. -2. Apply the recommended fix, or propose an alternative and show the diff. -3. Stage **only** code paths touched by that fix. -4. Never run `git add .`. -5. Never stage `/.documentation/specs/pr-review/pr-{PR_ID}.md` during this phase. - -### Phase 3 — Validate - -1. Re-run the **locked pytest scope** from the review file `Stats` table (reuse the same command; do not pick a new scope). -2. Re-run project-specific validators explicitly recorded in the review file. -3. If any validation fails, return to Phase 2. -4. Do not continue until validations pass. - -### Phase 4 — Commit code fixes (isolation gate #1) - -1. Run gate script with `-Gate code-only` before commit. -2. If the gate fails, **abort** and print offending staged paths. -3. Review staged diff and commit with: - -```text -fix(pr-{PR_ID}): address {M-02,M-04,M-05} -``` - -1. Capture the resulting short hash as `{FIX_SHA}`. - -### Phase 5 — Update the review file - -For each fixed finding: - -1. Flip `- [ ]` to `- [x]`. -2. Append `— *Fixed in {FIX_SHA}: {one-line how}*` to the finding heading line. -3. Do **not** change finding IDs, descriptions, or broken/fix code blocks. - -Then update metadata: - -1. Bump revision in the header table (`Rev N -> Rev N+1`). -2. Update `Stats` with current churn/test counts/commit snapshot. -3. Append a new row to `Revision Log` for this iteration. -4. Stage only `/.documentation/specs/pr-review/pr-{PR_ID}.md`. - -### Phase 6 — Commit review file (isolation gate #2) - -1. Run gate script with `-Gate review-only` before commit. -2. If the gate fails, **abort** and print offending staged paths. -3. Commit with: - -```text -review(pr-{PR_ID}): rev {N} — {X} fixed, {Y} remaining -``` - -1. Verify commit disjointness: - -```bash -git log HEAD~2..HEAD --name-only -``` - -Parse the two commits and assert they share zero file paths. - -### Phase 7 — Handoff - -1. Print both new commit hashes (`fix` and `review`). -2. Suggest focused re-review: - -```text -Run `/devspark.pr-review UPDATE {PR_URL}` to trigger a focused re-review. -``` - -## Guidelines - -### Commit Discipline (MUST) - -- A commit touching `/.documentation/specs/pr-review/pr-{PR_ID}.md` MUST NOT include any other path. -- Code fixes and review-file updates MUST be separate commits. -- Do not amend/squash these two commits together. - -### Gate Execution (MUST) - -- Use the helper script as the source of truth for staging gates. -- If a gate exits non-zero, stop and resolve staging before retrying. - -### Edit Scope (MUST) - -In review files, limit edits to: - -- finding checkbox state -- heading-line fixed-in suffix -- revision metadata (`Revision`, `Stats`, `Revision Log`) - -Everything else is immutable during addressing. - -## Context - -$ARGUMENTS - -## Shared Review Resolution Contract Output - -When emitting findings (review observations, issues, recommendations), structure each entry to include the shared resolution contract fields so downstream tools (/devspark.address-pr-review, telemetry, harvest) can act on them deterministically: - -```yaml -findings: - - finding_id: # e.g., analyze-001, clarify-002 - severity: critical | high | medium | low - description: <1-3 sentence problem statement> - recommended_action: - execution_mode: auto | selective | manual - status: open # set to `resolved` after remediation - outcome: "" # populated post-resolution by address-pr-review -``` - -inding_id MUST be stable across re-runs when the underlying issue is unchanged. xecution_mode MUST be one of: `auto` (safe to apply automatically), `selective` (apply with reviewer approval), `manual` (requires human implementation). The `status` and `outcome` fields are written by `/devspark.address-pr-review` (FR-028). diff --git a/.claude/commands/devspark.analyze.md b/.claude/commands/devspark.analyze.md deleted file mode 100644 index 84dfcc5..0000000 --- a/.claude/commands/devspark.analyze.md +++ /dev/null @@ -1,184 +0,0 @@ ---- -description: Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation. ---- - -## User Input - -```text -$ARGUMENTS -``` - -You **MUST** consider the user input before proceeding (if not empty). - -## Goal - -Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/devspark.tasks` has successfully produced a complete `tasks.md`. - -## Operating Constraints - -**STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually). - -**Constitution Authority**: The project constitution (`/.documentation/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/devspark.analyze`. - -## Execution Steps - -### 1. Initialize Analysis Context - -Run `.devspark/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths: - -- SPEC = FEATURE_DIR/spec.md -- PLAN = FEATURE_DIR/plan.md -- TASKS = FEATURE_DIR/tasks.md - -Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command). -For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). - -### 2. Load Artifacts (Progressive Disclosure) - -Load only the minimal necessary context from each artifact: - -**From spec.md:** - -- Overview/Context -- Functional Requirements -- Non-Functional Requirements -- User Stories -- Edge Cases (if present) - -**From plan.md:** - -- Architecture/stack choices -- Data Model references -- Phases -- Technical constraints - -**From tasks.md:** - -- Task IDs -- Descriptions -- Phase grouping -- Parallel markers [P] -- Referenced file paths - -**From constitution:** - -- Load `/.documentation/memory/constitution.md` for principle validation - -### 3. Build Semantic Models - -Create internal representations (do not include raw artifacts in output): - -- **Requirements inventory**: Each functional + non-functional requirement with a stable key (derive slug based on imperative phrase; e.g., "User can upload file" → `user-can-upload-file`) -- **User story/action inventory**: Discrete user actions with acceptance criteria -- **Task coverage mapping**: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases) -- **Constitution rule set**: Extract principle names and MUST/SHOULD normative statements - -### 4. Detection Passes (Token-Efficient Analysis) - -Focus on high-signal findings. Limit to 50 findings total; aggregate remainder in overflow summary. - -#### A. Duplication Detection - -- Identify near-duplicate requirements -- Mark lower-quality phrasing for consolidation - -#### B. Ambiguity Detection - -- Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria -- Flag unresolved placeholders (TODO, TKTK, ???, ``, etc.) - -#### C. Underspecification - -- Requirements with verbs but missing object or measurable outcome -- User stories missing acceptance criteria alignment -- Tasks referencing files or components not defined in spec/plan - -#### D. Constitution Alignment - -- Any requirement or plan element conflicting with a MUST principle -- Missing mandated sections or quality gates from constitution - -#### E. Coverage Gaps - -- Requirements with zero associated tasks -- Tasks with no mapped requirement/story -- Non-functional requirements not reflected in tasks (e.g., performance, security) - -#### F. Inconsistency - -- Terminology drift (same concept named differently across files) -- Data entities referenced in plan but absent in spec (or vice versa) -- Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note) -- Conflicting requirements (e.g., one requires Next.js while other specifies Vue) - -### 5. Severity Assignment - -Use this heuristic to prioritize findings: - -- **CRITICAL**: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality -- **HIGH**: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion -- **MEDIUM**: Terminology drift, missing non-functional task coverage, underspecified edge case -- **LOW**: Style/wording improvements, minor redundancy not affecting execution order - -### 6. Produce Compact Analysis Report - -Output a Markdown report (no file writes) with the following structure: - -## Specification Analysis Report - -| ID | Category | Severity | Location(s) | Summary | Recommendation | -|----|----------|----------|-------------|---------|----------------| -| A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version | - -(Add one row per finding; generate stable IDs prefixed by category initial.) - -**Coverage Summary Table:** - -| Requirement Key | Has Task? | Task IDs | Notes | -|-----------------|-----------|----------|-------| - -**Constitution Alignment Issues:** (if any) - -**Unmapped Tasks:** (if any) - -**Metrics:** - -- Total Requirements -- Total Tasks -- Coverage % (requirements with >=1 task) -- Ambiguity Count -- Duplication Count -- Critical Issues Count - -### 7. Provide Next Actions - -At end of report, output a concise Next Actions block: - -- If CRITICAL issues exist: Recommend resolving before `/devspark.implement` -- If only LOW/MEDIUM: User may proceed, but provide improvement suggestions -- Provide explicit command suggestions: e.g., "Run /devspark.specify with refinement", "Run /devspark.plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'" - -### 8. Offer Remediation - -Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.) - -## Operating Principles - -### Context Efficiency - -- **Minimal high-signal tokens**: Focus on actionable findings, not exhaustive documentation -- **Progressive disclosure**: Load artifacts incrementally; don't dump all content into analysis -- **Token-efficient output**: Limit findings table to 50 rows; summarize overflow -- **Deterministic results**: Rerunning without changes should produce consistent IDs and counts - -### Analysis Guidelines - -- **NEVER modify files** (this is read-only analysis) -- **NEVER hallucinate missing sections** (if absent, report them accurately) -- **Prioritize constitution violations** (these are always CRITICAL) -- **Use examples over exhaustive rules** (cite specific instances, not generic patterns) -- **Report zero issues gracefully** (emit success report with coverage statistics) - -## Context - -$ARGUMENTS diff --git a/.claude/commands/devspark.archive.md b/.claude/commands/devspark.archive.md deleted file mode 100644 index dec9bd1..0000000 --- a/.claude/commands/devspark.archive.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -description: Archive outdated documentation to .archive/, update Guide.md and CHANGELOG.md so .documentation stays current and clean -handoffs: - - label: Run a site audit after archiving - agent: devspark.site-audit - prompt: Run a site audit to confirm the project is in good health after archiving - - label: Evolve the constitution - agent: devspark.evolve-constitution - prompt: Review the constitution in light of the cleaned-up documentation ---- - -## User Input - -```text -$ARGUMENTS -``` - -You **MUST** consider the user input before proceeding (if not empty). If the user names specific files or directories, prioritize those as archive candidates. - -## Purpose - -Keep `.documentation/` current and authoritative. Move outdated, completed, or contradicting material to `.archive/` with a date-stamped folder. Before moving anything, extract important information into `CHANGELOG.md` and `.documentation/Guide.md` so no knowledge is lost. - -**CRITICAL: Never read from `.archive/` during this command or any other command.** The archive folder is write-only from an operational perspective. Past decisions are preserved for audit; they do not drive current work. - -## Prerequisites - -- Git repository (recommended but not required) -- `.documentation/` directory exists - -## Execution Steps - -### 1. Gather Context - -Run `.devspark/scripts/bash/archive-context.sh --json` from the repo root. Parse the JSON output: - -- `REPO_ROOT` — absolute path to the repository root -- `ARCHIVE_DIR` — target folder for today's archive (e.g., `.archive/2026-03-07`) -- `GUIDE_PATH` / `GUIDE_EXISTS` — location of the living guide -- `CHANGELOG_PATH` / `CHANGELOG_EXISTS` — location of the changelog -- `CANDIDATES` — categorised lists of potential archive candidates: - - `drafts` — files under `.documentation/drafts/` - - `session_docs` — files under `.documentation/copilot/` - - `implementation_plans` — historical `*-implementation-plan.md` and `*-plan.md` files - - `release_docs` — files under `.documentation/releases/` - - `quickfix_records` — files under `.documentation/quickfixes/` - - `pr_reviews` — files under `.documentation/specs/pr-review/` -- `CURRENT_DOCS` — top-level `.documentation/*.md` files (review for staleness) - -Treat script JSON as summary context: - -- Candidate arrays may be sampled; use `CANDIDATE_COUNTS` and `CURRENT_DOCS_COUNT` for totals. -- Do not force exhaustive review of every candidate when counts are large. -- Only request full inventory when explicitly needed. - -Execution limits (required): - -- Default max files to read in one pass: 40 -- Prioritize by category in this order: drafts, stale session docs, superseded plans/releases, then remaining categories -- Stop once enough evidence exists to archive clearly stale content safely -- If ambiguous, keep file and note why instead of broadening scope - -### 2. Review and Decide - -Read each candidate file. For each, make one of three decisions: - -| Decision | Criteria | -|----------|----------| -| **Archive** | Completed, superseded, contradicts current docs, or is working-notes noise (sausage-making) | -| **Keep** | Still actively referenced, contains unique current guidance, or is the canonical version of something | -| **Merge** | Has important content that should be folded into a current doc before the file is archived | - -If candidate counts exceed the one-pass limit, process the highest-signal subset first and report deferred items. - -**Always archive:** - -- Everything under `.documentation/drafts/` -- Session artifacts under `.documentation/copilot/session=*/` older than the current session -- Completed `*-implementation-plan.md` files whose features are already shipped -- Superseded release notes when a newer release exists - -**Be conservative with:** - -- Top-level `.documentation/*.md` files — only archive if clearly outdated or contradicted -- `.documentation/specs/` active feature directories — do not archive specs in progress - -### 3. Extract Knowledge Before Archiving - -For every file you decide to archive, scan it for: - -- **Decisions made** → add to `CHANGELOG.md` as historical record -- **How-to guidance still valid** → merge into `.documentation/Guide.md` -- **Architectural rationale** → add to `CHANGELOG.md` with context - -Do not copy content verbatim — summarise concisely. The goal is to preserve the signal, discard the noise. - -### 4. Update CHANGELOG.md - -The changelog lives at `CHANGELOG.md` (repo root). If it does not exist, create it. - -Format: - -```markdown -# Changelog - -## [Unreleased] - -## [YYYY-MM-DD] Archive run - -### Archived -- `path/to/file.md` — one-sentence reason -- ... - -### Key decisions preserved -- Decision or rationale extracted from archived docs -- ... -``` - -Prepend the new archive-run section above any existing entries. Do not remove existing changelog content. - -### 5. Update .documentation/Guide.md - -The guide lives at `.documentation/Guide.md`. It is the **living orientation document** for `.documentation/`. If it does not exist, create it. - -The guide must always reflect the **current** state. Update it to: - -1. **Directory map** — what each folder in `.documentation/` contains right now -2. **Key files** — brief description of each top-level `.documentation/*.md` file -3. **How to use** — which commands use which scripts and templates -4. **Constitution location** — `/.documentation/memory/constitution.md` -5. **What is in `.archive/`** — one-line summary: "Completed and historical docs. Do not read from here during normal operations." - -Do not add historical content to Guide.md — it describes the present, not the past. - -### 6. Create the Archive Folder and Move Files - -1. Create `ARCHIVE_DIR` if it does not exist (e.g., `.archive/2026-03-07/`). -2. Mirror the source directory structure inside the archive folder: - - `.documentation/drafts/foo.md` → `.archive/2026-03-07/.documentation/drafts/foo.md` -3. Move (do not copy) each file decided for archiving. -4. If moving a directory would leave an empty parent, remove the empty parent only if it has no remaining files. -5. **Do not move** `.documentation/memory/constitution.md` — it is never an archive candidate. -6. **Do not move** `.devspark/scripts/` (stock scripts) or `.documentation/scripts/` (team script overrides) — these are operational. -7. **Do not move** `.documentation/templates/` — these are operational. - -### 7. Update .archive/README.md - -Create or update `.archive/README.md`: - -```markdown -# Archive - -This folder contains completed and historical documentation that is no longer -part of the active `.documentation/` surface. - -**Do not reference files in this folder from prompts, scripts, or active docs.** -Files here are preserved for audit and traceability only. - -## Contents - -| Folder | Date | Description | -|--------|------|-------------| -| YYYY-MM-DD/ | YYYY-MM-DD | Brief description of this archive batch | -``` - -### 8. Report - -Output a summary to the user: - -```markdown -## Archive Complete - -**Archived**: N files → `.archive/YYYY-MM-DD/` -**CHANGELOG.md**: updated with N entries -**Guide.md**: updated - -### Files Archived -- `path/file.md` — reason - -### Files Kept -- `path/file.md` — reason - -### .documentation/ Current State -Brief description of what remains and why it is all current. -``` - -## Rules - -- **Never read `.archive/` in this or any other command.** -- **Never archive `.documentation/memory/constitution.md`.** -- **Never archive `.devspark/scripts/` or `.documentation/scripts/` — these are operational.** -- **Never archive active specs in `.documentation/specs/` unless they are explicitly completed and superseded.** -- Preserve directory structure inside `.archive/YYYY-MM-DD/` to maintain traceability. -- One archive folder per run, named by today's date. -- If `$ARGUMENTS` names specific files, archive those directly without requiring full review of all candidates. diff --git a/.claude/commands/devspark.checklist.md b/.claude/commands/devspark.checklist.md deleted file mode 100644 index 3e8a7fd..0000000 --- a/.claude/commands/devspark.checklist.md +++ /dev/null @@ -1,294 +0,0 @@ ---- -description: Generate a custom checklist for the current feature based on user requirements. ---- - -## Checklist Purpose: "Unit Tests for English" - -**CRITICAL CONCEPT**: Checklists are **UNIT TESTS FOR REQUIREMENTS WRITING** - they validate the quality, clarity, and completeness of requirements in a given domain. - -**NOT for verification/testing**: - -- ❌ NOT "Verify the button clicks correctly" -- ❌ NOT "Test error handling works" -- ❌ NOT "Confirm the API returns 200" -- ❌ NOT checking if code/implementation matches the spec - -**FOR requirements quality validation**: - -- ✅ "Are visual hierarchy requirements defined for all card types?" (completeness) -- ✅ "Is 'prominent display' quantified with specific sizing/positioning?" (clarity) -- ✅ "Are hover state requirements consistent across all interactive elements?" (consistency) -- ✅ "Are accessibility requirements defined for keyboard navigation?" (coverage) -- ✅ "Does the spec define what happens when logo image fails to load?" (edge cases) - -**Metaphor**: If your spec is code written in English, the checklist is its unit test suite. You're testing whether the requirements are well-written, complete, unambiguous, and ready for implementation - NOT whether the implementation works. - -## User Input - -```text -$ARGUMENTS -``` - -You **MUST** consider the user input before proceeding (if not empty). - -## Execution Steps - -1. **Setup**: Run `.devspark/scripts/bash/check-prerequisites.sh --json` from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS list. - - All file paths must be absolute. - - For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). - -2. **Clarify intent (dynamic)**: Derive up to THREE initial contextual clarifying questions (no pre-baked catalog). They MUST: - - Be generated from the user's phrasing + extracted signals from spec/plan/tasks - - Only ask about information that materially changes checklist content - - Be skipped individually if already unambiguous in `$ARGUMENTS` - - Prefer precision over breadth - - Generation algorithm: - 1. Extract signals: feature domain keywords (e.g., auth, latency, UX, API), risk indicators ("critical", "must", "compliance"), stakeholder hints ("QA", "review", "security team"), and explicit deliverables ("a11y", "rollback", "contracts"). - 2. Cluster signals into candidate focus areas (max 4) ranked by relevance. - 3. Identify probable audience & timing (author, reviewer, QA, release) if not explicit. - 4. Detect missing dimensions: scope breadth, depth/rigor, risk emphasis, exclusion boundaries, measurable acceptance criteria. - 5. Formulate questions chosen from these archetypes: - - Scope refinement (e.g., "Should this include integration touchpoints with X and Y or stay limited to local module correctness?") - - Risk prioritization (e.g., "Which of these potential risk areas should receive mandatory gating checks?") - - Depth calibration (e.g., "Is this a lightweight pre-commit sanity list or a formal release gate?") - - Audience framing (e.g., "Will this be used by the author only or peers during PR review?") - - Boundary exclusion (e.g., "Should we explicitly exclude performance tuning items this round?") - - Scenario class gap (e.g., "No recovery flows detected—are rollback / partial failure paths in scope?") - - Question formatting rules: - - If presenting options, generate a compact table with columns: Option | Candidate | Why It Matters - - Limit to A–E options maximum; omit table if a free-form answer is clearer - - Never ask the user to restate what they already said - - Avoid speculative categories (no hallucination). If uncertain, ask explicitly: "Confirm whether X belongs in scope." - - Defaults when interaction impossible: - - Depth: Standard - - Audience: Reviewer (PR) if code-related; Author otherwise - - Focus: Top 2 relevance clusters - - Output the questions (label Q1/Q2/Q3). After answers: if ≥2 scenario classes (Alternate / Exception / Recovery / Non-Functional domain) remain unclear, you MAY ask up to TWO more targeted follow‑ups (Q4/Q5) with a one-line justification each (e.g., "Unresolved recovery path risk"). Do not exceed five total questions. Skip escalation if user explicitly declines more. - -3. **Understand user request**: Combine `$ARGUMENTS` + clarifying answers: - - Derive checklist theme (e.g., security, review, deploy, ux) - - Consolidate explicit must-have items mentioned by user - - Map focus selections to category scaffolding - - Infer any missing context from spec/plan/tasks (do NOT hallucinate) - -4. **Load feature context**: Read from FEATURE_DIR: - - spec.md: Feature requirements and scope - - plan.md (if exists): Technical details, dependencies - - tasks.md (if exists): Implementation tasks - - **Context Loading Strategy**: - - Load only necessary portions relevant to active focus areas (avoid full-file dumping) - - Prefer summarizing long sections into concise scenario/requirement bullets - - Use progressive disclosure: add follow-on retrieval only if gaps detected - - If source docs are large, generate interim summary items instead of embedding raw text - -5. **Generate checklist** - Create "Unit Tests for Requirements": - - Create `FEATURE_DIR/checklists/` directory if it doesn't exist - - Generate unique checklist filename: - - Use short, descriptive name based on domain (e.g., `ux.md`, `api.md`, `security.md`) - - Format: `[domain].md` - - If file exists, append to existing file - - Number items sequentially starting from CHK001 - - Each `/devspark.checklist` run creates a NEW file (never overwrites existing checklists) - - **CORE PRINCIPLE - Test the Requirements, Not the Implementation**: - Every checklist item MUST evaluate the REQUIREMENTS THEMSELVES for: - - **Completeness**: Are all necessary requirements present? - - **Clarity**: Are requirements unambiguous and specific? - - **Consistency**: Do requirements align with each other? - - **Measurability**: Can requirements be objectively verified? - - **Coverage**: Are all scenarios/edge cases addressed? - - **Category Structure** - Group items by requirement quality dimensions: - - **Requirement Completeness** (Are all necessary requirements documented?) - - **Requirement Clarity** (Are requirements specific and unambiguous?) - - **Requirement Consistency** (Do requirements align without conflicts?) - - **Acceptance Criteria Quality** (Are success criteria measurable?) - - **Scenario Coverage** (Are all flows/cases addressed?) - - **Edge Case Coverage** (Are boundary conditions defined?) - - **Non-Functional Requirements** (Performance, Security, Accessibility, etc. - are they specified?) - - **Dependencies & Assumptions** (Are they documented and validated?) - - **Ambiguities & Conflicts** (What needs clarification?) - - **HOW TO WRITE CHECKLIST ITEMS - "Unit Tests for English"**: - - ❌ **WRONG** (Testing implementation): - - "Verify landing page displays 3 episode cards" - - "Test hover states work on desktop" - - "Confirm logo click navigates home" - - ✅ **CORRECT** (Testing requirements quality): - - "Are the exact number and layout of featured episodes specified?" [Completeness] - - "Is 'prominent display' quantified with specific sizing/positioning?" [Clarity] - - "Are hover state requirements consistent across all interactive elements?" [Consistency] - - "Are keyboard navigation requirements defined for all interactive UI?" [Coverage] - - "Is the fallback behavior specified when logo image fails to load?" [Edge Cases] - - "Are loading states defined for asynchronous episode data?" [Completeness] - - "Does the spec define visual hierarchy for competing UI elements?" [Clarity] - - **ITEM STRUCTURE**: - Each item should follow this pattern: - - Question format asking about requirement quality - - Focus on what's WRITTEN (or not written) in the spec/plan - - Include quality dimension in brackets [Completeness/Clarity/Consistency/etc.] - - Reference spec section `[Spec §X.Y]` when checking existing requirements - - Use `[Gap]` marker when checking for missing requirements - - **EXAMPLES BY QUALITY DIMENSION**: - - Completeness: - - "Are error handling requirements defined for all API failure modes? [Gap]" - - "Are accessibility requirements specified for all interactive elements? [Completeness]" - - "Are mobile breakpoint requirements defined for responsive layouts? [Gap]" - - Clarity: - - "Is 'fast loading' quantified with specific timing thresholds? [Clarity, Spec §NFR-2]" - - "Are 'related episodes' selection criteria explicitly defined? [Clarity, Spec §FR-5]" - - "Is 'prominent' defined with measurable visual properties? [Ambiguity, Spec §FR-4]" - - Consistency: - - "Do navigation requirements align across all pages? [Consistency, Spec §FR-10]" - - "Are card component requirements consistent between landing and detail pages? [Consistency]" - - Coverage: - - "Are requirements defined for zero-state scenarios (no episodes)? [Coverage, Edge Case]" - - "Are concurrent user interaction scenarios addressed? [Coverage, Gap]" - - "Are requirements specified for partial data loading failures? [Coverage, Exception Flow]" - - Measurability: - - "Are visual hierarchy requirements measurable/testable? [Acceptance Criteria, Spec §FR-1]" - - "Can 'balanced visual weight' be objectively verified? [Measurability, Spec §FR-2]" - - **Scenario Classification & Coverage** (Requirements Quality Focus): - - Check if requirements exist for: Primary, Alternate, Exception/Error, Recovery, Non-Functional scenarios - - For each scenario class, ask: "Are [scenario type] requirements complete, clear, and consistent?" - - If scenario class missing: "Are [scenario type] requirements intentionally excluded or missing? [Gap]" - - Include resilience/rollback when state mutation occurs: "Are rollback requirements defined for migration failures? [Gap]" - - **Traceability Requirements**: - - MINIMUM: ≥80% of items MUST include at least one traceability reference - - Each item should reference: spec section `[Spec §X.Y]`, or use markers: `[Gap]`, `[Ambiguity]`, `[Conflict]`, `[Assumption]` - - If no ID system exists: "Is a requirement & acceptance criteria ID scheme established? [Traceability]" - - **Surface & Resolve Issues** (Requirements Quality Problems): - Ask questions about the requirements themselves: - - Ambiguities: "Is the term 'fast' quantified with specific metrics? [Ambiguity, Spec §NFR-1]" - - Conflicts: "Do navigation requirements conflict between §FR-10 and §FR-10a? [Conflict]" - - Assumptions: "Is the assumption of 'always available podcast API' validated? [Assumption]" - - Dependencies: "Are external podcast API requirements documented? [Dependency, Gap]" - - Missing definitions: "Is 'visual hierarchy' defined with measurable criteria? [Gap]" - - **Content Consolidation**: - - Soft cap: If raw candidate items > 40, prioritize by risk/impact - - Merge near-duplicates checking the same requirement aspect - - If >5 low-impact edge cases, create one item: "Are edge cases X, Y, Z addressed in requirements? [Coverage]" - - **🚫 ABSOLUTELY PROHIBITED** - These make it an implementation test, not a requirements test: - - ❌ Any item starting with "Verify", "Test", "Confirm", "Check" + implementation behavior - - ❌ References to code execution, user actions, system behavior - - ❌ "Displays correctly", "works properly", "functions as expected" - - ❌ "Click", "navigate", "render", "load", "execute" - - ❌ Test cases, test plans, QA procedures - - ❌ Implementation details (frameworks, APIs, algorithms) - - **✅ REQUIRED PATTERNS** - These test requirements quality: - - ✅ "Are [requirement type] defined/specified/documented for [scenario]?" - - ✅ "Is [vague term] quantified/clarified with specific criteria?" - - ✅ "Are requirements consistent between [section A] and [section B]?" - - ✅ "Can [requirement] be objectively measured/verified?" - - ✅ "Are [edge cases/scenarios] addressed in requirements?" - - ✅ "Does the spec define [missing aspect]?" - -6. **Structure Reference**: Generate the checklist following the canonical template in `.documentation/templates/checklist-template.md` for title, meta section, category headings, and ID formatting. If template is unavailable, use: H1 title, purpose/created meta lines, `##` category sections containing `- [ ] CHK### ` lines with globally incrementing IDs starting at CHK001. - -7. **Report**: Output full path to created checklist, item count, and remind user that each run creates a new file. Summarize: - - Focus areas selected - - Depth level - - Actor/timing - - Any explicit user-specified must-have items incorporated - -**Important**: Each `/devspark.checklist` command invocation creates a checklist file using short, descriptive names unless file already exists. This allows: - -- Multiple checklists of different types (e.g., `ux.md`, `test.md`, `security.md`) -- Simple, memorable filenames that indicate checklist purpose -- Easy identification and navigation in the `checklists/` folder - -To avoid clutter, use descriptive types and clean up obsolete checklists when done. - -## Example Checklist Types & Sample Items - -**UX Requirements Quality:** `ux.md` - -Sample items (testing the requirements, NOT the implementation): - -- "Are visual hierarchy requirements defined with measurable criteria? [Clarity, Spec §FR-1]" -- "Is the number and positioning of UI elements explicitly specified? [Completeness, Spec §FR-1]" -- "Are interaction state requirements (hover, focus, active) consistently defined? [Consistency]" -- "Are accessibility requirements specified for all interactive elements? [Coverage, Gap]" -- "Is fallback behavior defined when images fail to load? [Edge Case, Gap]" -- "Can 'prominent display' be objectively measured? [Measurability, Spec §FR-4]" - -**API Requirements Quality:** `api.md` - -Sample items: - -- "Are error response formats specified for all failure scenarios? [Completeness]" -- "Are rate limiting requirements quantified with specific thresholds? [Clarity]" -- "Are authentication requirements consistent across all endpoints? [Consistency]" -- "Are retry/timeout requirements defined for external dependencies? [Coverage, Gap]" -- "Is versioning strategy documented in requirements? [Gap]" - -**Performance Requirements Quality:** `performance.md` - -Sample items: - -- "Are performance requirements quantified with specific metrics? [Clarity]" -- "Are performance targets defined for all critical user journeys? [Coverage]" -- "Are performance requirements under different load conditions specified? [Completeness]" -- "Can performance requirements be objectively measured? [Measurability]" -- "Are degradation requirements defined for high-load scenarios? [Edge Case, Gap]" - -**Security Requirements Quality:** `security.md` - -Sample items: - -- "Are authentication requirements specified for all protected resources? [Coverage]" -- "Are data protection requirements defined for sensitive information? [Completeness]" -- "Is the threat model documented and requirements aligned to it? [Traceability]" -- "Are security requirements consistent with compliance obligations? [Consistency]" -- "Are security failure/breach response requirements defined? [Gap, Exception Flow]" - -## Anti-Examples: What NOT To Do - -**❌ WRONG - These test implementation, not requirements:** - -```markdown -- [ ] CHK001 - Verify landing page displays 3 episode cards [Spec §FR-001] -- [ ] CHK002 - Test hover states work correctly on desktop [Spec §FR-003] -- [ ] CHK003 - Confirm logo click navigates to home page [Spec §FR-010] -- [ ] CHK004 - Check that related episodes section shows 3-5 items [Spec §FR-005] -``` - -**✅ CORRECT - These test requirements quality:** - -```markdown -- [ ] CHK001 - Are the number and layout of featured episodes explicitly specified? [Completeness, Spec §FR-001] -- [ ] CHK002 - Are hover state requirements consistently defined for all interactive elements? [Consistency, Spec §FR-003] -- [ ] CHK003 - Are navigation requirements clear for all clickable brand elements? [Clarity, Spec §FR-010] -- [ ] CHK004 - Is the selection criteria for related episodes documented? [Gap, Spec §FR-005] -- [ ] CHK005 - Are loading state requirements defined for asynchronous episode data? [Gap] -- [ ] CHK006 - Can "visual hierarchy" requirements be objectively measured? [Measurability, Spec §FR-001] -``` - -**Key Differences:** - -- Wrong: Tests if the system works correctly -- Correct: Tests if the requirements are written correctly -- Wrong: Verification of behavior -- Correct: Validation of requirement quality -- Wrong: "Does it do X?" -- Correct: "Is X clearly specified?" diff --git a/.claude/commands/devspark.clarify.md b/.claude/commands/devspark.clarify.md deleted file mode 100644 index 6100a68..0000000 --- a/.claude/commands/devspark.clarify.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -description: Identify underspecified areas in the current feature spec by asking up to 5 highly targeted clarification questions and encoding answers back into the spec. -handoffs: - - label: Build Technical Plan - agent: devspark.plan - prompt: Create a plan for the spec. I am building with... ---- - -## User Input - -```text -$ARGUMENTS -``` - -You **MUST** consider the user input before proceeding (if not empty). - -## Outline - -Goal: Detect and reduce ambiguity or missing decision points in the active feature specification and record the clarifications directly in the spec file. - -Note: This clarification workflow is expected to run (and be completed) BEFORE invoking `/devspark.plan`. If the user explicitly states they are skipping clarification (e.g., exploratory spike), you may proceed, but must warn that downstream rework risk increases. - -Execution steps: - -1. Run `.devspark/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields: - - `FEATURE_DIR` - - `FEATURE_SPEC` - - (Optionally capture `IMPL_PLAN`, `TASKS` for future chained flows.) - - If JSON parsing fails, abort and instruct user to re-run `/devspark.specify` or verify feature branch environment. - - For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). - -2. Load the current spec file. Perform a structured ambiguity & coverage scan using this taxonomy. For each category, mark status: Clear / Partial / Missing. Produce an internal coverage map used for prioritization (do not output raw map unless no questions will be asked). - - Functional Scope & Behavior: - - Core user goals & success criteria - - Explicit out-of-scope declarations - - User roles / personas differentiation - - Domain & Data Model: - - Entities, attributes, relationships - - Identity & uniqueness rules - - Lifecycle/state transitions - - Data volume / scale assumptions - - Interaction & UX Flow: - - Critical user journeys / sequences - - Error/empty/loading states - - Accessibility or localization notes - - Non-Functional Quality Attributes: - - Performance (latency, throughput targets) - - Scalability (horizontal/vertical, limits) - - Reliability & availability (uptime, recovery expectations) - - Observability (logging, metrics, tracing signals) - - Security & privacy (authN/Z, data protection, threat assumptions) - - Compliance / regulatory constraints (if any) - - Integration & External Dependencies: - - External services/APIs and failure modes - - Data import/export formats - - Protocol/versioning assumptions - - Edge Cases & Failure Handling: - - Negative scenarios - - Rate limiting / throttling - - Conflict resolution (e.g., concurrent edits) - - Constraints & Tradeoffs: - - Technical constraints (language, storage, hosting) - - Explicit tradeoffs or rejected alternatives - - Terminology & Consistency: - - Canonical glossary terms - - Avoided synonyms / deprecated terms - - Completion Signals: - - Acceptance criteria testability - - Measurable Definition of Done style indicators - - Misc / Placeholders: - - TODO markers / unresolved decisions - - Ambiguous adjectives ("robust", "intuitive") lacking quantification - - For each category with Partial or Missing status, add a candidate question opportunity unless: - - Clarification would not materially change implementation or validation strategy - - Information is better deferred to planning phase (note internally) - -3. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints: - - Maximum of 10 total questions across the whole session. - - Each question must be answerable with EITHER: - - A short multiple‑choice selection (2–5 distinct, mutually exclusive options), OR - - A one-word / short‑phrase answer (explicitly constrain: "Answer in <=5 words"). - - Only include questions whose answers materially impact architecture, data modeling, task decomposition, test design, UX behavior, operational readiness, or compliance validation. - - Ensure category coverage balance: attempt to cover the highest impact unresolved categories first; avoid asking two low-impact questions when a single high-impact area (e.g., security posture) is unresolved. - - Exclude questions already answered, trivial stylistic preferences, or plan-level execution details (unless blocking correctness). - - Favor clarifications that reduce downstream rework risk or prevent misaligned acceptance tests. - - If more than 5 categories remain unresolved, select the top 5 by (Impact * Uncertainty) heuristic. - -4. Sequential questioning loop (interactive): - - Present EXACTLY ONE question at a time. - - For multiple‑choice questions: - - **Analyze all options** and determine the **most suitable option** based on: - - Best practices for the project type - - Common patterns in similar implementations - - Risk reduction (security, performance, maintainability) - - Alignment with any explicit project goals or constraints visible in the spec - - Present your **recommended option prominently** at the top with clear reasoning (1-2 sentences explaining why this is the best choice). - - Format as: `**Recommended:** Option [X] - ` - - Then render all options as a Markdown table: - - | Option | Description | - |--------|-------------| - | A |