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.
Files changed (57) hide show
  1. package/.agents/README.md +7 -7
  2. package/.agents/docs/SDLC.md +4 -5
  3. package/.agents/docs/configuration.md +9 -9
  4. package/.agents/docs/workflows.md +4 -6
  5. package/.agents/schemas/qa-finding.schema.json +1 -1
  6. package/.agents/scripts/apply-quality-bootstrap.js +79 -0
  7. package/.agents/scripts/audit-labels-bootstrap.js +52 -30
  8. package/.agents/scripts/audit-to-stories.js +54 -0
  9. package/.agents/scripts/bootstrap.js +13 -3
  10. package/.agents/scripts/generate-config-docs.js +189 -94
  11. package/.agents/scripts/lib/audit-suite/findings.js +0 -4
  12. package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +99 -0
  13. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +13 -5
  14. package/.agents/scripts/lib/baseline-snapshot.js +163 -4
  15. package/.agents/scripts/lib/baselines/refresh-service.js +0 -4
  16. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
  17. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +1 -1
  18. package/.agents/scripts/lib/config/baselines.js +0 -20
  19. package/.agents/scripts/lib/config/defaults.js +1 -1
  20. package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
  21. package/.agents/scripts/lib/config/temp-paths.js +0 -31
  22. package/.agents/scripts/lib/config-resolver.js +1 -1
  23. package/.agents/scripts/lib/crap-utils.js +281 -0
  24. package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -2
  25. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -84
  26. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +3 -4
  27. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -4
  28. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +101 -70
  29. package/.agents/scripts/lib/orchestration/spec-renderer.js +42 -14
  30. package/.agents/scripts/lib/orchestration/ticket-lease.js +3 -0
  31. package/.agents/scripts/lib/qa/qa-context-hydrator.js +1 -1
  32. package/.agents/scripts/lib/qa/resolve-qa-contract.js +1 -1
  33. package/.agents/scripts/lib/story-body/story-body.js +110 -65
  34. package/.agents/scripts/lib/test-tiers.js +13 -7
  35. package/.agents/scripts/lib/wave-runner/tick.js +177 -53
  36. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +226 -0
  37. package/.agents/scripts/mandrel-update-preflight.js +235 -0
  38. package/.agents/scripts/providers/github/issues.js +48 -0
  39. package/.agents/scripts/providers/github.js +1 -0
  40. package/.agents/scripts/sync-agentrc.js +2 -2
  41. package/.agents/skills/skills.index.json +2 -2
  42. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +3 -3
  43. package/.agents/skills/stack/qa/qa-harness/SKILL.md +4 -4
  44. package/.agents/workflows/git-deliver.md +298 -0
  45. package/.agents/workflows/helpers/epic-testing.md +6 -6
  46. package/.agents/workflows/helpers/{agents-sync-config.md → mandrel-sync-config.md} +5 -4
  47. package/.agents/workflows/{agents-update.md → mandrel-update.md} +210 -33
  48. package/.agents/workflows/qa-explore.md +1 -1
  49. package/.agents/workflows/{qa-run-harness.md → qa-run.md} +5 -5
  50. package/README.md +40 -0
  51. package/docs/CHANGELOG.md +43 -0
  52. package/lib/cli/registry.js +49 -6
  53. package/lib/cli/update.js +335 -332
  54. package/package.json +16 -11
  55. package/.agents/workflows/git-commit-all.md +0 -15
  56. package/.agents/workflows/git-pr-all.md +0 -281
  57. 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-harness`) in your project | [§ Adopting the QA harness](#adopting-the-qa-harness) |
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; `wave-dispatcher.js` synthesizes the
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-harness <selector>`** — the **automated complement**: it drives a
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-harness.md`](workflows/qa-run-harness.md); the
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-harness` to
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-harness <selector>` all resolve the contract and
993
- operate against the bound surface. For `/qa-run-harness`, the `chrome-devtools`
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
@@ -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-harness.md`](../workflows/qa-run-harness.md) and consumed as
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-harness.md`](../workflows/qa-run-harness.md)**
1150
- (`/qa-run-harness`) — the **automated complement**: it steps a *known* set of
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-commit-all` | Stage and commit all changes |
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 `/agents-update`, the
752
- [`agents-sync-config`](../workflows/helpers/agents-sync-config.md)
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
- `/agents-update`.
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 `/agents-update` to confirm the helper preserves the key on round-trip.
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 `agents-sync-config.md` only if
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 `/agents-update`
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
- [`/agents-update` Step 3.6](../workflows/agents-update.md). The
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
- `/agents-update` bump commit so the reviewer sees the framework pointer
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
- [`agents-sync-config.md`](../workflows/helpers/agents-sync-config.md)
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 (27)
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-commit-all` | Stage every untracked and modified file, then create a single conventional-commit on the current branch (no push). |
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
- | `/git-pr-all` | Stage all outstanding changes, commit, push to a feature branch, and open a pull request with native auto-merge enabled. |
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-harness` | Drive Gherkin scenarios through a real browser as an agent-driven QA sweep |
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-harness.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`.",
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::<dimension>`
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 dimension list mirrors the 12 audit-* workflows in
10
- * `.agents/workflows/`. Adding a new audit-* workflow should also add a
11
- * corresponding entry below.
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
- const DIMENSIONS = Object.freeze([
27
- {
28
- name: 'architecture',
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
- name: 'lighthouse',
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
- name: 'performance',
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: true,
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: true,
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
  }