mandrel 1.69.0 → 1.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +7 -7
- package/.agents/docs/SDLC.md +4 -5
- package/.agents/docs/configuration.md +9 -9
- package/.agents/docs/workflows.md +4 -6
- package/.agents/schemas/qa-finding.schema.json +1 -1
- package/.agents/scripts/apply-quality-bootstrap.js +79 -0
- package/.agents/scripts/audit-labels-bootstrap.js +52 -30
- package/.agents/scripts/audit-to-stories.js +54 -0
- package/.agents/scripts/bootstrap.js +13 -3
- package/.agents/scripts/generate-config-docs.js +189 -94
- package/.agents/scripts/lib/audit-suite/findings.js +0 -4
- package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +99 -0
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +13 -5
- package/.agents/scripts/lib/baseline-snapshot.js +163 -4
- package/.agents/scripts/lib/baselines/refresh-service.js +0 -4
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +1 -1
- package/.agents/scripts/lib/config/baselines.js +0 -20
- package/.agents/scripts/lib/config/defaults.js +1 -1
- package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +0 -31
- package/.agents/scripts/lib/config-resolver.js +1 -1
- package/.agents/scripts/lib/crap-utils.js +281 -0
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -2
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -84
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +3 -4
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -4
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +101 -70
- package/.agents/scripts/lib/orchestration/spec-renderer.js +42 -14
- package/.agents/scripts/lib/orchestration/ticket-lease.js +3 -0
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +1 -1
- package/.agents/scripts/lib/qa/resolve-qa-contract.js +1 -1
- package/.agents/scripts/lib/story-body/story-body.js +110 -65
- package/.agents/scripts/lib/test-tiers.js +13 -7
- package/.agents/scripts/lib/wave-runner/tick.js +177 -53
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +226 -0
- package/.agents/scripts/mandrel-update-preflight.js +235 -0
- package/.agents/scripts/providers/github/issues.js +48 -0
- package/.agents/scripts/providers/github.js +1 -0
- package/.agents/scripts/sync-agentrc.js +2 -2
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +3 -3
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +4 -4
- package/.agents/workflows/git-deliver.md +298 -0
- package/.agents/workflows/helpers/epic-testing.md +6 -6
- package/.agents/workflows/helpers/{agents-sync-config.md → mandrel-sync-config.md} +5 -4
- package/.agents/workflows/{agents-update.md → mandrel-update.md} +210 -33
- package/.agents/workflows/qa-explore.md +1 -1
- package/.agents/workflows/{qa-run-harness.md → qa-run.md} +5 -5
- package/README.md +40 -0
- package/docs/CHANGELOG.md +43 -0
- package/lib/cli/registry.js +49 -6
- package/lib/cli/update.js +335 -332
- package/package.json +16 -11
- package/.agents/workflows/git-commit-all.md +0 -15
- package/.agents/workflows/git-pr-all.md +0 -281
- package/.agents/workflows/git-push.md +0 -63
package/.agents/README.md
CHANGED
|
@@ -333,7 +333,7 @@ See [`docs/SDLC.md` § Ticket hierarchy](docs/SDLC.md) for the diagram and execu
|
|
|
333
333
|
| Check registry authoring rules | [§ Self-healing checks](#self-healing-checks) |
|
|
334
334
|
| JSON Schema conventions | [§ Schemas](#schemas) |
|
|
335
335
|
| Bootstrap script (project + GitHub setup) | [`scripts/bootstrap.js`](scripts/bootstrap.js) |
|
|
336
|
-
| Adopt the QA workflows (`/qa-explore`, `/qa-assist`, `/qa-run
|
|
336
|
+
| Adopt the QA workflows (`/qa-explore`, `/qa-assist`, `/qa-run`) in your project | [§ Adopting the QA harness](#adopting-the-qa-harness) |
|
|
337
337
|
| Coordinate two operators on the same repo (lease model) | [§ Multi-developer coordination](#multi-developer-coordination) |
|
|
338
338
|
|
|
339
339
|
---
|
|
@@ -466,7 +466,7 @@ ticketing provider is GitHub, resolved by `provider-factory.js` from the
|
|
|
466
466
|
`orchestration.provider` config key. CLI scripts receive provider
|
|
467
467
|
instances from the SDK surface rather than importing provider
|
|
468
468
|
implementations directly. Execution is Claude-Code-in-session — there is
|
|
469
|
-
no separate adapter abstraction; `
|
|
469
|
+
no separate adapter abstraction; `manifest-builder.js` synthesizes the
|
|
470
470
|
dispatch record inline and the dispatch manifest (md + structured
|
|
471
471
|
comment) is the cross-runtime contract.
|
|
472
472
|
|
|
@@ -890,12 +890,12 @@ known scenario set:
|
|
|
890
890
|
`/qa-assist` when you hit something mid-flight and want it captured well
|
|
891
891
|
without breaking stride; reach for `/qa-explore` when you want the agent to
|
|
892
892
|
go hunt a named surface.
|
|
893
|
-
- **`/qa-run
|
|
893
|
+
- **`/qa-run <selector>`** — the **automated complement**: it drives a
|
|
894
894
|
consumer's Gherkin `.feature` scenarios through a real browser (the
|
|
895
895
|
`chrome-devtools` MCP surface), captures per-surface console/network into
|
|
896
896
|
structured `F#` findings, and drafts follow-up tickets for operator
|
|
897
897
|
sign-off. The end-to-end procedure is the SSOT in
|
|
898
|
-
[`workflows/qa-run
|
|
898
|
+
[`workflows/qa-run.md`](workflows/qa-run.md); the
|
|
899
899
|
instrumentation conventions live in the
|
|
900
900
|
[`skills/stack/qa/qa-harness`](skills/stack/qa/qa-harness/SKILL.md) skill; the
|
|
901
901
|
architectural overview (run pipeline, contract fields, finding shape) is in
|
|
@@ -904,7 +904,7 @@ known scenario set:
|
|
|
904
904
|
Reach for `/qa-explore` when you want the **agent** to hunt a freshly delivered
|
|
905
905
|
Story/Feature or run a structured bug-hunt captured into a triageable ledger;
|
|
906
906
|
reach for `/qa-assist` when **you** hit something mid-flight and want it
|
|
907
|
-
enriched into a single triage-ready ledger item; reach for `/qa-run
|
|
907
|
+
enriched into a single triage-ready ledger item; reach for `/qa-run` to
|
|
908
908
|
step a **known** scenario set through the browser for a regression pass.
|
|
909
909
|
|
|
910
910
|
Binding the QA contract is **opt-in**. All three workflows resolve the
|
|
@@ -989,8 +989,8 @@ environment, never inlined) and `{ signInSkill }` points at a per-persona
|
|
|
989
989
|
sign-in skill.
|
|
990
990
|
|
|
991
991
|
Once these three `qa.*` keys are in place, `/qa-explore <surface>`,
|
|
992
|
-
`/qa-assist`, and `/qa-run
|
|
993
|
-
operate against the bound surface. For `/qa-run
|
|
992
|
+
`/qa-assist`, and `/qa-run <selector>` all resolve the contract and
|
|
993
|
+
operate against the bound surface. For `/qa-run`, the `chrome-devtools`
|
|
994
994
|
MCP surface is a host-provided runtime dependency; when it is unavailable the
|
|
995
995
|
harness degrades with a clear error rather than falling back to a headless
|
|
996
996
|
runner. `/qa-explore` and `/qa-assist` read the same `qa.*` keys to scope their
|
package/.agents/docs/SDLC.md
CHANGED
|
@@ -1103,7 +1103,7 @@ for the acceptance tier is governed by
|
|
|
1103
1103
|
[`rules/gherkin-standards.md`](../rules/gherkin-standards.md).
|
|
1104
1104
|
|
|
1105
1105
|
The acceptance tier is executed and reported via
|
|
1106
|
-
[`workflows/qa-run
|
|
1106
|
+
[`workflows/qa-run.md`](../workflows/qa-run.md) and consumed as
|
|
1107
1107
|
epic evidence by
|
|
1108
1108
|
[`workflows/helpers/epic-testing.md`](../workflows/helpers/epic-testing.md).
|
|
1109
1109
|
|
|
@@ -1146,8 +1146,8 @@ set:
|
|
|
1146
1146
|
(`qa-ledger.schema.json`) and reuses the same `scripts/lib/qa/` and
|
|
1147
1147
|
`scripts/lib/findings/` decision seams, so a `/qa-assist` item flows through
|
|
1148
1148
|
the identical dedup, classification, and promotion machinery later.
|
|
1149
|
-
- **[`workflows/qa-run
|
|
1150
|
-
(`/qa-run
|
|
1149
|
+
- **[`workflows/qa-run.md`](../workflows/qa-run.md)**
|
|
1150
|
+
(`/qa-run`) — the **automated complement**: it steps a *known* set of
|
|
1151
1151
|
Gherkin `.feature` scenarios through a real browser, asserting `Then`
|
|
1152
1152
|
outcomes semantically against the accessibility snapshot and bundling
|
|
1153
1153
|
console/network problems into structured `F#` findings for operator sign-off.
|
|
@@ -1420,6 +1420,5 @@ For Stories already in flight, use one of the three options above.
|
|
|
1420
1420
|
| *helper* `workflows/helpers/epic-deliver-story` | Per-Story worker called by `/deliver`'s wave loop; not an operator slash command. See [`helpers/epic-deliver-story.md`](../workflows/helpers/epic-deliver-story.md). |
|
|
1421
1421
|
| *helper* `workflows/helpers/single-story-deliver` | Per-Story worker called by `/deliver`; not an operator slash command. See [`helpers/single-story-deliver.md`](../workflows/helpers/single-story-deliver.md). |
|
|
1422
1422
|
| *helper* `workflows/helpers/code-review.md` | Auto-invoked by `/deliver`'s `delivery.code-review` state (scope: epic); not a slash command. |
|
|
1423
|
-
| `/git-
|
|
1424
|
-
| `/git-push` | Stage, commit, and push to remote |
|
|
1423
|
+
| `/git-deliver` | Ad-hoc delivery of working-tree changes — detects the git setup and escalates to commit, commit + push, or commit + push + PR (auto-merge armed). |
|
|
1425
1424
|
| `epic-reconcile.js --explicit-delete` | Hard reset — close orphaned Epic-scoped issues per `.agents/epics/<id>.yaml` |
|
|
@@ -748,8 +748,8 @@ number of keys.
|
|
|
748
748
|
| `github.operatorHandle` | Committed as the `@[USERNAME]` placeholder; each contributor overrides it in gitignored `.agentrc.local.json` | `@[USERNAME]` | Schema-required, but per-contributor: the committed placeholder resolves to null and the lease guards fail closed until you set your own handle locally (see [Per-machine local overrides](#per-machine-local-overrides)). |
|
|
749
749
|
| `delivery.worktreeIsolation.nodeModulesStrategy` | `per-worktree` | `per-worktree` | npm-only repo (`package-lock.json`); worktree init runs `npm ci` per tree. |
|
|
750
750
|
|
|
751
|
-
When a consumer runs `/
|
|
752
|
-
[`
|
|
751
|
+
When a consumer runs `/mandrel-update`, the
|
|
752
|
+
[`mandrel-sync-config`](../workflows/helpers/mandrel-sync-config.md)
|
|
753
753
|
helper validates the project config against the schema, then adds any
|
|
754
754
|
template-introduced keys the project does not already define. Project-side
|
|
755
755
|
values that validate are preserved unconditionally — including optional keys
|
|
@@ -821,10 +821,10 @@ project-specific knob:
|
|
|
821
821
|
1. Confirm the key is **already declared in the schema** at
|
|
822
822
|
[`.agents/schemas/agentrc.schema.json`](../schemas/agentrc.schema.json)
|
|
823
823
|
— if it isn't, the AJV validators will reject it on the next
|
|
824
|
-
`/
|
|
824
|
+
`/mandrel-update`.
|
|
825
825
|
2. Set the key in `.agentrc.json`. Don't add it to the template unless it
|
|
826
826
|
should ship to all consumers.
|
|
827
|
-
3. Run `/
|
|
827
|
+
3. Run `/mandrel-update` to confirm the helper preserves the key on round-trip.
|
|
828
828
|
|
|
829
829
|
### Extending list-valued keys without losing template defaults
|
|
830
830
|
|
|
@@ -898,7 +898,7 @@ This is a framework-level change, not a project-level one. The path is:
|
|
|
898
898
|
[`config-resolver.js`](../scripts/lib/config-resolver.js).
|
|
899
899
|
4. Add tests under `tests/lib/config-*.test.js` and confirm
|
|
900
900
|
`tests/config-schema-mirror-drift.test.js` passes.
|
|
901
|
-
5. Document the key in this file and update `
|
|
901
|
+
5. Document the key in this file and update `mandrel-sync-config.md` only if
|
|
902
902
|
the merge semantics differ from the default (project-wins) rule.
|
|
903
903
|
|
|
904
904
|
---
|
|
@@ -979,10 +979,10 @@ allowlists drift project-to-project.
|
|
|
979
979
|
|
|
980
980
|
### Cadence
|
|
981
981
|
|
|
982
|
-
Run `/fewer-permission-prompts` **once per `/
|
|
982
|
+
Run `/fewer-permission-prompts` **once per `/mandrel-update`
|
|
983
983
|
invocation**, immediately after the package upgrade re-materializes
|
|
984
984
|
`.agents/` and before the bump commit lands. The cadence is codified in
|
|
985
|
-
[`/
|
|
985
|
+
[`/mandrel-update` Step 3.6](../workflows/mandrel-update.md). The
|
|
986
986
|
operator who just bumped `.agents/` has the freshest transcript context
|
|
987
987
|
in the active session, which is exactly what the skill scans, so this
|
|
988
988
|
is the cheapest time to surface new high-frequency calls.
|
|
@@ -1024,7 +1024,7 @@ on the next cadence run if they remain high-frequency, so the cost of
|
|
|
1024
1024
|
deferral is bounded.
|
|
1025
1025
|
|
|
1026
1026
|
Stage the accepted `.claude/settings.json` diff alongside the
|
|
1027
|
-
`/
|
|
1027
|
+
`/mandrel-update` bump commit so the reviewer sees the framework pointer
|
|
1028
1028
|
move and the allowlist response in the same diff.
|
|
1029
1029
|
|
|
1030
1030
|
---
|
|
@@ -1093,7 +1093,7 @@ mandrel uninstall --include-github # acknowledge GitHub-side follow-ups
|
|
|
1093
1093
|
- Resolver entry point —
|
|
1094
1094
|
[`config-resolver.js`](../scripts/lib/config-resolver.js)
|
|
1095
1095
|
- Sync helper —
|
|
1096
|
-
[`
|
|
1096
|
+
[`mandrel-sync-config.md`](../workflows/helpers/mandrel-sync-config.md)
|
|
1097
1097
|
- Bootstrap script —
|
|
1098
1098
|
[`bootstrap.js`](../scripts/bootstrap.js)
|
|
1099
1099
|
- Quality gates runbook (CRAP onboarding, MI ratchet, lint ratchet) —
|
|
@@ -25,11 +25,10 @@ by `node .agents/scripts/generate-workflows-doc.js`; `npm run docs:check`
|
|
|
25
25
|
fails when it drifts from the on-disk workflow set. To change a command’s
|
|
26
26
|
description, edit the workflow file’s front-matter and regenerate.
|
|
27
27
|
|
|
28
|
-
## Commands (
|
|
28
|
+
## Commands (25)
|
|
29
29
|
|
|
30
30
|
| Command | Description |
|
|
31
31
|
| --- | --- |
|
|
32
|
-
| `/agents-update` | npm-era upgrade wraparound for a Mandrel consumer. Runs `mandrel update` (resolve newest published version → install → re-materialize `.agents/` → migrate → doctor → surface changelog) as the single mechanical step, then walks the operator through the judgment wraparound the CLI deliberately leaves unowned: reconcile `.agentrc.json`, install the Epic #1386 quality-gate surface, refresh the harness permission allowlist, reconcile the consumer's `AGENTS.md` / runbooks against the surfaced changelog, and stage + commit the staged lockfile bump. |
|
|
33
32
|
| `/audit-architecture` | Audit architectural boundaries, module coupling, and layering violations; emit a structured findings report keyed to High/Medium/Low severity. |
|
|
34
33
|
| `/audit-clean-code` | Audit code smells, dead code, complexity hotspots, and maintainability-index outliers; emit a structured findings report. |
|
|
35
34
|
| `/audit-dependencies` | Audit `package.json` for unused, outdated, and major-version-stale dependencies; surface Node-engine drift and propose upgrade batches. |
|
|
@@ -48,11 +47,10 @@ description, edit the workflow file’s front-matter and regenerate.
|
|
|
48
47
|
| `/deliver` | Unified delivery entry point. Inspects the ticket type(s) and Epic-reference state of the supplied IDs, composes a sequential segment plan over any mix of Epics and standalone Stories, then delegates each segment to the Epic wave loop or the standalone multi-Story fan-out — preserving every flag and the parallel-delivery contract of the retired commands. |
|
|
49
48
|
| `/explain` | Walk the operator through a code change until they genuinely understand it. Targets a PR, a branch, or the working-tree diff, then drives the `core/knowledge-transfer` skill (restate-first, why-ladder, mastery gates, persistent checklist) with an operator-controlled stop at every checkpoint. |
|
|
50
49
|
| `/git-cleanup` | Tidy the local checkout in four phases: fast-forward `main`, prune stale remote-tracking refs, sweep merged branches (squash-aware), and triage `git stash` entries — each step gated by operator confirmation. |
|
|
51
|
-
| `/git-
|
|
50
|
+
| `/git-deliver` | Single ad-hoc delivery command for working-tree changes. Detects the git setup and escalates to the right terminal step — commit only, commit + push, or commit + push + open a PR with native auto-merge — picking the default from observable state and letting flags pin any level explicitly. Replaces the retired git-commit-all, git-push, and git-pr-all trio. |
|
|
52
51
|
| `/git-merge-pr` | Analyze, validate, resolve conflicts, and merge a given pull request by number. |
|
|
53
|
-
| `/
|
|
54
|
-
| `/git-push` | Commit all outstanding changes then push to the remote repository. |
|
|
52
|
+
| `/mandrel-update` | npm-era upgrade wraparound for a Mandrel consumer. Runs `npx mandrel update` (resolve newest published version → install → re-materialize `.agents/` → migrate → doctor → surface changelog) as the single mechanical step, then walks the operator through the judgment wraparound the CLI deliberately leaves unowned: reconcile `.agentrc.json`, install the Epic #1386 quality-gate surface, refresh the harness permission allowlist, reconcile the consumer's `AGENTS.md` / runbooks against the surfaced changelog, and stage + commit the staged lockfile bump. |
|
|
55
53
|
| `/plan` | Unified planning entry point. Routes a seed idea (via scope triage) or an existing Epic ID to the right planning path — the full Epic pipeline (PRD, Tech Spec, Acceptance Spec, decomposition) or the standalone-Story authoring path — and absorbs every planning flag. |
|
|
56
54
|
| `/qa-assist` | Human-led QA assist loop — set up, then ride a rolling multi-observation intake session. The operator reports observations in any order; the agent enriches each (repro + root-cause file:line + coverage verdict for bugs; analysis + options + recommendation for enhancements), asks clarifying questions only when ambiguous, and appends a redacted ledger item — recording, never planning — to a persistent, resumable session under temp/qa/. Only when the operator says they are done does it review the full ledger and hand off to /plan. |
|
|
57
55
|
| `/qa-explore` | Agent-led exploratory-QA loop — the agent Plans a surface with an explicit static-vs-drive method choice, drives it (browser MCP or static), and captures ledger items read-only, then Triages — a bounded per-surface session, HITL-gated at every phase transition, routed through the shared dedup/coverage/classification/missing-test/redaction/session core under temp/qa/ |
|
|
58
|
-
| `/qa-run
|
|
56
|
+
| `/qa-run` | Drive Gherkin scenarios through a real browser as an agent-driven QA sweep |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"title": "QaFinding",
|
|
4
|
-
"description": "Structured `F#` finding emitted by the agent-driven QA harness (`.agents/workflows/qa-run
|
|
4
|
+
"description": "Structured `F#` finding emitted by the agent-driven QA harness (`.agents/workflows/qa-run.md`, Epic #3214). One object per genuine problem surfaced while sweeping a consumer's Gherkin scenarios through chrome-devtools MCP: a non-allowlisted console error, a failed/error-status network request, or a gross design-token violation. Findings are validated against this schema before they are bundled by likely root cause into operator-approved follow-up ticket drafts (the harness never files tickets autonomously). The shape mirrors the F# convergence in Tech Spec #3285 and is produced in its console-derived subset by `.agents/scripts/lib/qa/console-allowlist.js`.",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
7
|
"required": [
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* apply-quality-bootstrap.js — Story #4171
|
|
5
|
+
* (refactor(mandrel-update): extract the quality-bootstrap heredoc into a
|
|
6
|
+
* tested script)
|
|
7
|
+
*
|
|
8
|
+
* Replaces the inline `node -e "Promise.all([...])"` heredoc that Step 3.5 of
|
|
9
|
+
* the `/mandrel-update` workflow used to carry. That shape was fragile in three
|
|
10
|
+
* ways: it broke across shells (PowerShell vs bash quoting / backticks), it
|
|
11
|
+
* had no test so it silently drifted when the two helper signatures moved, and
|
|
12
|
+
* it could not be invoked or dry-run independently.
|
|
13
|
+
*
|
|
14
|
+
* This script runs the two Epic #1386 quality-gate installs in order against
|
|
15
|
+
* the consumer repo root:
|
|
16
|
+
*
|
|
17
|
+
* 1. `applyQualityBootstrap` — copies the code-quality-guardrails helper,
|
|
18
|
+
* installs the `.husky/pre-commit` quality:preview line, backfills the
|
|
19
|
+
* `quality:preview` / `quality:watch` npm scripts, and seeds the
|
|
20
|
+
* `delivery.quality.{codingGuardrails,autoRefresh}` defaults.
|
|
21
|
+
* 2. `migrateBaselinesLayout` — relocates per-Epic baseline snapshots into
|
|
22
|
+
* the `temp/epic/<id>/baselines/` namespace.
|
|
23
|
+
*
|
|
24
|
+
* Both helpers are idempotent by contract — a second run reports `no-change`
|
|
25
|
+
* on every install path — so this wrapper is safe to re-run. It prints the
|
|
26
|
+
* **same JSON result shape** the heredoc did: `{ quality, baselines }` to
|
|
27
|
+
* stdout, so any tooling that parsed the old output keeps working.
|
|
28
|
+
*
|
|
29
|
+
* The effectful work is a thin pure function (`applyBootstrapAndMigration`)
|
|
30
|
+
* that takes the two helpers and the project root, so the test suite can
|
|
31
|
+
* drive it against a tmp directory without spawning a child process. The CLI
|
|
32
|
+
* wrapper wires the real helpers and `process.cwd()`.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import path from 'node:path';
|
|
36
|
+
import { migrateBaselinesLayout } from './lib/bootstrap/baselines-layout-migration.js';
|
|
37
|
+
import { applyQualityBootstrap } from './lib/bootstrap/quality-bootstrap.js';
|
|
38
|
+
import { runAsCli } from './lib/cli-utils.js';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Run the quality-bootstrap install and the baselines-layout migration
|
|
42
|
+
* against `projectRoot`, returning the combined `{ quality, baselines }`
|
|
43
|
+
* envelope. Pure relative to its injected helpers: the default helpers touch
|
|
44
|
+
* the filesystem under `projectRoot`, but tests can pass stubs to exercise
|
|
45
|
+
* the composition in isolation.
|
|
46
|
+
*
|
|
47
|
+
* @param {object} options
|
|
48
|
+
* @param {string} options.projectRoot Absolute consumer repo root.
|
|
49
|
+
* @param {typeof applyQualityBootstrap} [options.applyQualityBootstrap]
|
|
50
|
+
* @param {typeof migrateBaselinesLayout} [options.migrateBaselinesLayout]
|
|
51
|
+
* @returns {{ quality: object, baselines: object }}
|
|
52
|
+
*/
|
|
53
|
+
export function applyBootstrapAndMigration({
|
|
54
|
+
projectRoot,
|
|
55
|
+
applyQualityBootstrap: applyQuality = applyQualityBootstrap,
|
|
56
|
+
migrateBaselinesLayout: migrateBaselines = migrateBaselinesLayout,
|
|
57
|
+
}) {
|
|
58
|
+
const quality = applyQuality({ projectRoot });
|
|
59
|
+
const baselines = migrateBaselines({
|
|
60
|
+
baselinesDir: path.join(projectRoot, 'baselines'),
|
|
61
|
+
repoRoot: projectRoot,
|
|
62
|
+
});
|
|
63
|
+
return { quality, baselines };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
const projectRoot = process.cwd();
|
|
68
|
+
const result = applyBootstrapAndMigration({ projectRoot });
|
|
69
|
+
// Mirror the retired heredoc's output: pretty-printed `{ quality, baselines }`
|
|
70
|
+
// to stdout. Use process.stdout.write (not console.log) per the no-console
|
|
71
|
+
// enforcement boundary.
|
|
72
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
runAsCli(import.meta.url, main, {
|
|
77
|
+
source: 'apply-quality-bootstrap',
|
|
78
|
+
propagateExitCode: true,
|
|
79
|
+
});
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* audit-labels-bootstrap.js — Idempotently create the `audit::<
|
|
2
|
+
* audit-labels-bootstrap.js — Idempotently create the `audit::<lens>`
|
|
3
3
|
* label taxonomy in the configured GitHub repo.
|
|
4
4
|
*
|
|
5
5
|
* Run this once per repo before `/audit-to-stories` opens its first
|
|
6
6
|
* Story. Re-runs are safe — existing labels are skipped, only missing
|
|
7
7
|
* ones are created. Story #2583 acceptance criterion #6.
|
|
8
8
|
*
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* The lens list is the shared SSOT `AUDIT_LENSES`
|
|
10
|
+
* (`lib/audit-to-stories/audit-lenses.js`), one per `/audit-<lens>` workflow
|
|
11
|
+
* under `.agents/workflows/`. Sourcing the list from the same module that
|
|
12
|
+
* `build-story-body.js` derives `audit::<lens>` labels from guarantees the
|
|
13
|
+
* label producer (this bootstrap) and the label deriver (story-body) cannot
|
|
14
|
+
* drift — a finding from `audit-documentation-results.md` derives
|
|
15
|
+
* `audit::documentation`, and this bootstrap creates exactly that label
|
|
16
|
+
* (Story #4195). The per-lens colour/description metadata lives in
|
|
17
|
+
* `LENS_META` below; adding a new `audit-*` workflow means adding its lens to
|
|
18
|
+
* `AUDIT_LENSES` and (optionally) a `LENS_META` entry.
|
|
12
19
|
*
|
|
13
20
|
* Delegates to `gh label create` so the script works without any
|
|
14
21
|
* provider plumbing — `gh auth status` is the only prerequisite. Per
|
|
@@ -19,72 +26,87 @@
|
|
|
19
26
|
import process from 'node:process';
|
|
20
27
|
import { parseArgs } from 'node:util';
|
|
21
28
|
|
|
29
|
+
import { AUDIT_LENSES } from './lib/audit-to-stories/audit-lenses.js';
|
|
22
30
|
import { runAsCli } from './lib/cli-utils.js';
|
|
23
31
|
import { resolveConfig } from './lib/config-resolver.js';
|
|
24
32
|
import { gh as defaultGh, GhExecError } from './lib/gh-exec.js';
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Per-lens label presentation. Keyed by canonical lens name. A lens absent
|
|
36
|
+
* from this map falls back to {@link DEFAULT_LENS_META} so a newly-added
|
|
37
|
+
* `AUDIT_LENSES` entry still gets a label without a hard requirement to
|
|
38
|
+
* register colour/description here first.
|
|
39
|
+
*/
|
|
40
|
+
const LENS_META = Object.freeze({
|
|
41
|
+
architecture: {
|
|
29
42
|
color: '6f42c1',
|
|
30
43
|
description: 'Audit-sourced finding: architectural concerns',
|
|
31
44
|
},
|
|
32
|
-
{
|
|
33
|
-
name: 'clean-code',
|
|
45
|
+
'clean-code': {
|
|
34
46
|
color: '0e8a16',
|
|
35
47
|
description: 'Audit-sourced finding: clean-code / maintainability',
|
|
36
48
|
},
|
|
37
|
-
{
|
|
38
|
-
name: 'dependencies',
|
|
49
|
+
dependencies: {
|
|
39
50
|
color: 'd4c5f9',
|
|
40
51
|
description: 'Audit-sourced finding: dependencies / supply chain',
|
|
41
52
|
},
|
|
42
|
-
{
|
|
43
|
-
name: 'devops',
|
|
53
|
+
devops: {
|
|
44
54
|
color: 'fbca04',
|
|
45
55
|
description: 'Audit-sourced finding: DevOps / CI / CD',
|
|
46
56
|
},
|
|
47
|
-
{
|
|
48
|
-
|
|
57
|
+
documentation: {
|
|
58
|
+
color: '1d76db',
|
|
59
|
+
description: 'Audit-sourced finding: documentation staleness / gaps',
|
|
60
|
+
},
|
|
61
|
+
lighthouse: {
|
|
49
62
|
color: 'c5def5',
|
|
50
63
|
description: 'Audit-sourced finding: Lighthouse score regressions',
|
|
51
64
|
},
|
|
52
|
-
{
|
|
53
|
-
|
|
65
|
+
navigability: {
|
|
66
|
+
color: 'bfdadc',
|
|
67
|
+
description: 'Audit-sourced finding: route / nav reachability',
|
|
68
|
+
},
|
|
69
|
+
performance: {
|
|
54
70
|
color: 'b60205',
|
|
55
71
|
description: 'Audit-sourced finding: performance / latency',
|
|
56
72
|
},
|
|
57
|
-
{
|
|
58
|
-
name: 'privacy',
|
|
73
|
+
privacy: {
|
|
59
74
|
color: 'fef2c0',
|
|
60
75
|
description: 'Audit-sourced finding: privacy / data handling',
|
|
61
76
|
},
|
|
62
|
-
{
|
|
63
|
-
name: 'quality',
|
|
77
|
+
quality: {
|
|
64
78
|
color: '0052cc',
|
|
65
79
|
description: 'Audit-sourced finding: test quality / coverage gaps',
|
|
66
80
|
},
|
|
67
|
-
{
|
|
68
|
-
name: 'security',
|
|
81
|
+
security: {
|
|
69
82
|
color: 'b60205',
|
|
70
83
|
description: 'Audit-sourced finding: security / OWASP',
|
|
71
84
|
},
|
|
72
|
-
{
|
|
73
|
-
name: 'seo',
|
|
85
|
+
seo: {
|
|
74
86
|
color: 'fbca04',
|
|
75
87
|
description: 'Audit-sourced finding: SEO / discoverability',
|
|
76
88
|
},
|
|
77
|
-
{
|
|
78
|
-
name: 'sre',
|
|
89
|
+
sre: {
|
|
79
90
|
color: '0052cc',
|
|
80
91
|
description: 'Audit-sourced finding: SRE / observability / reliability',
|
|
81
92
|
},
|
|
82
|
-
{
|
|
83
|
-
name: 'ux-ui',
|
|
93
|
+
'ux-ui': {
|
|
84
94
|
color: 'd4c5f9',
|
|
85
95
|
description: 'Audit-sourced finding: UX / UI concerns',
|
|
86
96
|
},
|
|
87
|
-
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const DEFAULT_LENS_META = Object.freeze({
|
|
100
|
+
color: 'ededed',
|
|
101
|
+
description: 'Audit-sourced finding',
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const DIMENSIONS = Object.freeze(
|
|
105
|
+
AUDIT_LENSES.map((name) => ({
|
|
106
|
+
name,
|
|
107
|
+
...(LENS_META[name] ?? DEFAULT_LENS_META),
|
|
108
|
+
})),
|
|
109
|
+
);
|
|
88
110
|
|
|
89
111
|
async function labelExists(gh, owner, repo, name) {
|
|
90
112
|
try {
|
|
@@ -39,6 +39,7 @@ import { groupFindings } from './lib/audit-to-stories/group-findings.js';
|
|
|
39
39
|
import { parseAuditReports } from './lib/audit-to-stories/parse-audit-md.js';
|
|
40
40
|
import { buildEpicSeedMarkdown } from './lib/audit-to-stories/seed-epic-from-findings.js';
|
|
41
41
|
import { runAsCli } from './lib/cli-utils.js';
|
|
42
|
+
import { Logger } from './lib/Logger.js';
|
|
42
43
|
|
|
43
44
|
const SEVERITY_RANK = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
44
45
|
const DEFAULT_GLOB = 'temp/audits/audit-*-results.md';
|
|
@@ -109,6 +110,44 @@ async function loadProvider() {
|
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Render the loud, operator-visible warning emitted when the Phase 6 dedup
|
|
115
|
+
* does NOT run against real GitHub issues. Two distinct reasons:
|
|
116
|
+
*
|
|
117
|
+
* - `'no-provider-port'` — the configured provider resolved but exposes no
|
|
118
|
+
* `searchIssues` port (or `loadProvider()` threw). This is the
|
|
119
|
+
* silent-no-op the workflow's "Never open a duplicate Issue" contract
|
|
120
|
+
* was failing on: every group classifies `create` and the operator gets
|
|
121
|
+
* zero automated dedup signal. Surfacing it loudly is the whole point.
|
|
122
|
+
* - `'disabled'` — the operator passed `--no-provider`, intentionally
|
|
123
|
+
* skipping dedup. Still warned (so a re-run that opens duplicates is
|
|
124
|
+
* never a surprise), but framed as a deliberate choice.
|
|
125
|
+
*
|
|
126
|
+
* Pure: returns the message string so `buildPlan` owns the single
|
|
127
|
+
* `Logger.warn` write site and the text stays unit-testable.
|
|
128
|
+
*
|
|
129
|
+
* @param {'no-provider-port'|'disabled'} reason
|
|
130
|
+
* @returns {string}
|
|
131
|
+
*/
|
|
132
|
+
function dedupSkippedWarning(reason) {
|
|
133
|
+
if (reason === 'disabled') {
|
|
134
|
+
return (
|
|
135
|
+
'dedup skipped (--no-provider): every group is classified "create" ' +
|
|
136
|
+
'without checking GitHub for existing issues. A re-run may open ' +
|
|
137
|
+
'duplicates of already-tracked or already-closed findings. Drop ' +
|
|
138
|
+
'--no-provider to enable fingerprint dedup against real issues.'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return (
|
|
142
|
+
'dedup skipped (no provider port): the configured provider exposes no ' +
|
|
143
|
+
'searchIssues() port, so Phase 6 dedup did NOT run. Every group is ' +
|
|
144
|
+
'classified "create" and existing/closed issues are NOT checked — a run ' +
|
|
145
|
+
'that creates Stories from this plan WILL open duplicates of ' +
|
|
146
|
+
'already-tracked work. Verify `gh auth status` and the github.{owner,repo} ' +
|
|
147
|
+
'config so a real provider resolves.'
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
112
151
|
async function buildPlan({ glob: pattern, severity, useProvider }) {
|
|
113
152
|
const reportPaths = await collectReportPaths(pattern ?? DEFAULT_GLOB);
|
|
114
153
|
if (reportPaths.length === 0) {
|
|
@@ -143,6 +182,7 @@ async function buildPlan({ glob: pattern, severity, useProvider }) {
|
|
|
143
182
|
matchedFingerprints: [],
|
|
144
183
|
}));
|
|
145
184
|
let summary = { create: groups.length, skipOpen: 0, skipReoccurring: 0 };
|
|
185
|
+
let dedupApplied = false;
|
|
146
186
|
|
|
147
187
|
if (useProvider) {
|
|
148
188
|
const provider = await loadProvider();
|
|
@@ -150,7 +190,18 @@ async function buildPlan({ glob: pattern, severity, useProvider }) {
|
|
|
150
190
|
const result = await classifyGroupsAgainstGitHub({ groups, provider });
|
|
151
191
|
classifications = result.classifications;
|
|
152
192
|
summary = result.summary;
|
|
193
|
+
dedupApplied = true;
|
|
194
|
+
} else {
|
|
195
|
+
// The provider could not resolve a searchIssues port — the dedup gate
|
|
196
|
+
// is silently a no-op without this. Surface it loudly (stderr, so the
|
|
197
|
+
// --scan JSON on stdout stays clean) so the operator does not read a
|
|
198
|
+
// create-only plan as "no duplicates found".
|
|
199
|
+
Logger.warn(dedupSkippedWarning('no-provider-port'));
|
|
153
200
|
}
|
|
201
|
+
} else {
|
|
202
|
+
// Operator explicitly opted out via --no-provider. Still warn so a
|
|
203
|
+
// duplicate-opening re-run is never a surprise.
|
|
204
|
+
Logger.warn(dedupSkippedWarning('disabled'));
|
|
154
205
|
}
|
|
155
206
|
|
|
156
207
|
return {
|
|
@@ -165,6 +216,7 @@ async function buildPlan({ glob: pattern, severity, useProvider }) {
|
|
|
165
216
|
totalFindings: allFindings.length,
|
|
166
217
|
filtered: filtered.length,
|
|
167
218
|
tally: tallyBySeverity(filtered),
|
|
219
|
+
dedupApplied,
|
|
168
220
|
...summary,
|
|
169
221
|
},
|
|
170
222
|
};
|
|
@@ -188,6 +240,8 @@ export const __testing = {
|
|
|
188
240
|
meetsSeverity,
|
|
189
241
|
collectReportPaths,
|
|
190
242
|
buildPlan,
|
|
243
|
+
loadProvider,
|
|
244
|
+
dedupSkippedWarning,
|
|
191
245
|
};
|
|
192
246
|
|
|
193
247
|
async function main() {
|
|
@@ -534,6 +534,13 @@ export function buildQuestions(defaults, flags, env = process.env, lists = {}) {
|
|
|
534
534
|
const reposList = lists.reposList;
|
|
535
535
|
const projectsList = lists.projectsList;
|
|
536
536
|
const pickerOwner = (answers) => answers?.owner || owner;
|
|
537
|
+
// `owner` / `repo` are GitHub-side answers. When `--skip-github` suppresses
|
|
538
|
+
// the entire GitHub bootstrap, they are not required — this lets a
|
|
539
|
+
// non-interactive `--assume-yes --skip-github` run materialize and configure
|
|
540
|
+
// a fresh non-git directory (no inferable remote) without hard-failing on
|
|
541
|
+
// `missing required answers: owner, repo`. With GitHub bootstrap active they
|
|
542
|
+
// remain required (the target repo must be resolvable).
|
|
543
|
+
const skipGithub = Boolean(flags?.['skip-github']);
|
|
537
544
|
return [
|
|
538
545
|
{
|
|
539
546
|
key: 'owner',
|
|
@@ -541,7 +548,7 @@ export function buildQuestions(defaults, flags, env = process.env, lists = {}) {
|
|
|
541
548
|
env: 'GH_OWNER',
|
|
542
549
|
message: '\n\nGitHub repo owner',
|
|
543
550
|
default: defaults.owner,
|
|
544
|
-
required:
|
|
551
|
+
required: !skipGithub,
|
|
545
552
|
validate: (v) =>
|
|
546
553
|
/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(v) ? null : 'Invalid GitHub owner',
|
|
547
554
|
},
|
|
@@ -567,7 +574,7 @@ export function buildQuestions(defaults, flags, env = process.env, lists = {}) {
|
|
|
567
574
|
pickerMessage:
|
|
568
575
|
'GitHub repo name - Select existing or press ENTER to create',
|
|
569
576
|
default: defaults.repo,
|
|
570
|
-
required:
|
|
577
|
+
required: !skipGithub,
|
|
571
578
|
picker: {
|
|
572
579
|
list: (answers) => {
|
|
573
580
|
if (Array.isArray(reposList) && reposList.length > 0)
|
|
@@ -957,7 +964,10 @@ export async function collectAndConfirm(state) {
|
|
|
957
964
|
});
|
|
958
965
|
if (missing.length > 0) {
|
|
959
966
|
Logger.error(
|
|
960
|
-
`[Bootstrap] missing required answers: ${missing.join(', ')}
|
|
967
|
+
`[Bootstrap] missing required answers: ${missing.join(', ')}. ` +
|
|
968
|
+
'Pass them as flags (e.g. `--owner <name> --repo <name>`), or run ' +
|
|
969
|
+
'with `--skip-github` to configure the files/local setup only and ' +
|
|
970
|
+
'wire GitHub later.',
|
|
961
971
|
);
|
|
962
972
|
return { ok: false, exit: 1 };
|
|
963
973
|
}
|