@tekyzinc/gsd-t 4.16.10 → 4.18.10

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/CHANGELOG.md CHANGED
@@ -2,6 +2,41 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [4.18.10] - 2026-07-03
6
+
7
+ ### Added — Scan Consolidation Opportunities + type-grouped register; init-scan-setup repo adoption
8
+
9
+ The scan register is now organized by TD **type within each severity**, and a new **Consolidation** phase (opus + graph-assisted) appends a **🧩 Consolidation Opportunities** section listing high-confidence clusters of findings that share a root and should be fixed as one workstream. `/gsd-t-init-scan-setup` gains a **Step 0 "Adopt a Foreign Repo"** sanitization pass and a **Step 4a completeness check**, so repos built by others (or plain Claude) run correctly on this machine and an incomplete/hand-driven scan is caught and re-run.
10
+
11
+ - `templates/workflows/gsd-t-scan.workflow.js`: register `fmtChunks` groups by `typeOf(area)` within severity via a shared `orderedFindings` ordering (so consolidation TD-N ids match the register exactly); new `Consolidation` phase (opus, graph-assisted via `who-imports`/`who-calls`/`blast-radius`) emits `CG-N` groups (members/sharedRoot/recommendedAction/effort) appended at the end; `meta.phases` +1. Best-effort — any failure leaves the register intact.
12
+ - `commands/gsd-t-init-scan-setup.md`: Step 0 adoption (settings reconciled vs global, CLAUDE.md regenerated from fresh scan, no foreign/stale workflow-script execution) + Step 4a post-scan completeness check + 8-phase scan description + corrected graph note (`graphDB/`).
13
+ - `commands/gsd-t-scan.md`: synthesis/consolidation phase description.
14
+ - `.gsd-t/backlog.md`: split logging item #48 → #48 (trace) + #49 (audit) with full specs.
15
+ - Lints green: M71 runtime-native, M85 tier-policy, M76 ascii. Suite 2631/2631.
16
+
17
+ ## [4.17.10] - 2026-07-01
18
+
19
+ ### Changed — `/gsd-t-estimate` v2: generalized + supervised + adversarial, with a permanent Sheets service account
20
+
21
+ Reshaped the estimator from scan-only to a general work-document pricer, made it human-in-the-loop, added an operator-arbitrated Estimate Red Team, and replaced the throwaway-per-run Google service account with a permanent reusable one.
22
+
23
+ - **Input generalized** — scan register OR new-feature/new-app requirements doc OR PRD-in; line-items become findings or requirements (Step 0 classifies + picks id vocabulary).
24
+ - **Numbering hygiene** — renumber only when numbering is absent / non-sequential / doesn't start at 1; preserve hierarchical numbering (`1`/`1.1`/`1.1.1`), never flatten.
25
+ - **Estimate Adjustments** (Step 2.5) broadened from familiarization-only to familiarization + R&D/unknown-approach/spike risk, documented per-item; M→L 3× cliff caution retained.
26
+ - **Parameterized** — rate / hoursPerDay / sizeScale / totalMF / highFactor / sheet template / GCP project via `$ARGUMENTS` or `.gsd-t/estimate-config.json`; defaults are Tekyz values, always named in the report (no silent default).
27
+ - **Human-in-the-loop** — judgment phases (sizing, adjustments, PRD, Red Team) pause for operator review; mechanical phases flow-but-show.
28
+ - **NEW Step 8 Estimate Red Team** — adversarial pass on under-sizing / missing line-items / optimistic multipliers / cross-check integrity; **operator is the final arbiter** (argue until concede or a definitive ruling; Red Team then grudgingly accepts and may document overridden objections to `share/<Repo>-estimate-redteam-notes.md`).
29
+ - **Sheet write: permanent reusable service account** — Step 0 asks for the sheet URL first + extracts the id; Step 5 reuses a permanent SA (`gsd-t-sheets-writer@ai-estimator-415612.iam.gserviceaccount.com`, key at `~/.claude/gsd-t-secrets/…`, chmod 600, outside any repo) instead of creating+deleting a throwaway each run. Recreation breaks existing sheet shares, so the SA is never deleted; each sheet is shared with it once.
30
+
31
+ - `commands/gsd-t-estimate.md`: full rewrite (generalization, HITL, Step 8 Red Team, Step 0 URL-first, Step 5 permanent SA, config table).
32
+ - `templates/estimate-config.json`: new optional documented override (installed to `~/.claude/templates/` via `SHARED_TEMPLATES`, not auto-scaffolded into projects).
33
+ - `bin/gsd-t.js`: `estimate-config.json` added to `SHARED_TEMPLATES`.
34
+ - `templates/playbooks/tekyz-estimation-and-prd-playbook.md`: header + Phase 5 updated to match.
35
+ - `commands/gsd-t-help.md`, `README.md`, `GSD-T-README.md`: description + version-history ripple.
36
+ - `.gsd-t/contracts/graph-metrics-contract.md`: `doMetrics` line citation 4742→4743 (shifted by the `SHARED_TEMPLATES` edit; keeps the M99 T6 line-ref test green).
37
+
38
+ Also captured two HIGH-priority framework-default backlog items (#47 universal build-number rule, #48 universal trace + audit logging) — promote-later; no code yet.
39
+
5
40
  ## [4.16.10] - 2026-07-01
6
41
 
7
42
  ### Added — `/gsd-t-estimate`: Tekyz client estimate + PRD from a scan
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v4.16.10** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v4.18.10** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
4
4
 
5
5
  **Eliminates context rot** — task-level fresh dispatch (one subagent per task, ~10-20% context each) means compaction never triggers.
6
6
  **Compaction-proof debug loops** — `gsd-t headless --debug-loop` runs test-fix-retest cycles as separate `claude -p` sessions. A JSONL debug ledger persists all hypothesis/fix/learning history across fresh sessions. Anti-repetition preamble injection prevents retrying failed hypotheses. Escalation tiers (sonnet → opus → human) and a hard iteration ceiling enforced externally.
@@ -186,7 +186,7 @@ This will replace changed command files, back up your CLAUDE.md if customized, a
186
186
  | `/gsd-t-scan` | Deep codebase analysis → techdebt.md | Manual |
187
187
  | `/gsd-t-gap-analysis` | Requirements gap analysis — spec vs. existing code | Manual |
188
188
  | `/gsd-t-promote-debt` | Convert techdebt items to milestones | Manual |
189
- | `/gsd-t-estimate` | Turn a scan into a Tekyz client estimate (Google Sheet: T-Shirt Size + Team Mix) + matching PRD | Manual |
189
+ | `/gsd-t-estimate` | Turn any work doc (scan, requirements, feature/app spec) into a Tekyz client estimate (Google Sheet: T-Shirt Size + Team Mix) + matching PRD — supervised, with an operator-arbitrated Estimate Red Team | Manual |
190
190
  | `/gsd-t-populate` | Auto-populate docs from existing codebase | Manual |
191
191
  | `/gsd-t-design-decompose` | Decompose design into element/widget/page contracts | Manual |
192
192
 
package/bin/gsd-t.js CHANGED
@@ -1708,6 +1708,7 @@ const SHARED_TEMPLATES = [
1708
1708
  "page-contract.md",
1709
1709
  "design-contract.md",
1710
1710
  "shared-services-contract.md",
1711
+ "estimate-config.json",
1711
1712
  ];
1712
1713
 
1713
1714
  function installSharedTemplates() {
@@ -1,49 +1,87 @@
1
- # GSD-T: Estimate — Tekyz Client Estimate + PRD from a Scan
1
+ # GSD-T: Estimate — Tekyz Client Estimate + PRD from any Work Document
2
2
 
3
- You are turning a completed GSD-T tech-debt scan into a **Tekyz client estimate** (a Google Sheet with a T-Shirt-Size tab + a Team-Mix cross-check) and a matching **PRD deliverable**. `$ARGUMENTS` may carry a scope override (e.g. `--severity high`).
3
+ You are turning a **structured work document** into a **Tekyz client estimate** (a Google Sheet with a T-Shirt-Size tab + a Team-Mix cross-check) and a matching **PRD deliverable**. The input can be a GSD-T tech-debt scan register, a **new-feature requirements doc**, a **new-application requirements doc**, an existing PRD, or any comparable spec. `$ARGUMENTS` may carry the input path + a scope override (e.g. `--severity high`, `--input docs/requirements.md`).
4
4
 
5
- **Full proven procedure:** read `~/.claude/playbooks/tekyz-estimation-and-prd-playbook.md` if present, else the bundled copy `templates/playbooks/tekyz-estimation-and-prd-playbook.md` in the GSD-T package (7 phases, produced on the HILO Figma ATOS project: 21 criticals → 32.73 eng-days → $13,090–$16,362). Supporting memories (in the originating project's memory dir): `tekyz-estimation-method`, `tekyz-familiarization-bump`, `tekyz-client-prd-structure`, `tekyz-tech-debt-numbering-caution`, `google-sheets-service-account-workaround`. This skill ENCODES those; read them for edge-case depth.
5
+ **Full proven procedure:** read `~/.claude/playbooks/tekyz-estimation-and-prd-playbook.md` if present, else the bundled copy `templates/playbooks/tekyz-estimation-and-prd-playbook.md` in the GSD-T package (produced on the HILO Figma ATOS project: 21 criticals → 32.73 eng-days → $13,090–$16,362). Supporting memories (in the originating project's memory dir): `tekyz-estimation-method`, `tekyz-familiarization-bump`, `tekyz-client-prd-structure`, `tekyz-tech-debt-numbering-caution`, `google-sheets-service-account-workaround`. This skill ENCODES those; read them for edge-case depth.
6
6
 
7
7
  > **Client-billed work, not GSD-T build work.** This produces a paid client estimate — the "no cost estimates" rule (`feedback_no_human_hour_estimates`) governs GSD-T's OWN Max-funded build work, NOT client deliverables. Dollar figures here are correct and expected.
8
8
 
9
- ## Step 0: Inputs + Scope
9
+ ## Human-in-the-Loop (MANDATORY this command is SUPERVISED, not auto)
10
10
 
11
- 1. Read `.gsd-t/techdebt.md` (the register). If absent "No scan register found. Run `/gsd-t-scan` first." and stop.
12
- 2. **Scope** (from `$ARGUMENTS`): default = **all CRITICAL findings** ("close the critical gap"). `--severity high` (or `medium`/`low`/`all`) widens it. Confirm the scope + finding count with the user before sizing.
13
- 3. Confirm you have (or ask the user for):
14
- - A copy of the Tekyz estimate template Google Sheet (tabs: **Overview**, **T-Shirt Size Estimate**, **Team Mix**, **Technology Stack**) + its sheet ID.
15
- - The GCP project id for the service-account write path (Phase 5).
16
- 4. Decide whether this is a **new-team project** (triggers the familiarization bump, Step 3.5) — usually YES for a fresh client.
11
+ This process is judgment-heavy. **You (the operator) are the final arbiter of every estimate.** The command does NOT run end-to-end autonomously.
17
12
 
18
- ## Step 1: Renumber for the client (if scan TDs start high)
13
+ - **Judgment phases PAUSE for review** before advancing: **Step 2 (sizing)**, **Step 2.5 (adjustments)**, **Step 6 (PRD)**, **Step 8 (Red Team)**. Present the output, wait for the user's `continue` or corrections.
14
+ - **Mechanical phases FLOW but SHOW their result**: Step 1 (numbering), Step 5 (sheet write), Step 7 (reconcile). Don't block on these, but display what happened so nothing is invisible.
15
+ - **Escape hatch:** if the user says e.g. "run through sizing and grouping, then stop," batch those phases and pause where they asked.
19
16
 
20
- GSD-T continues TD numbering across scans (never resets) a fresh/crashed-and-rerun scan can start at e.g. TD-618, which looks bad to a client ("why does finding #1 start at 618?"). **For a new client project, renumber the deliverables to start at TD-1.** (`tekyz-tech-debt-numbering-caution`.)
17
+ ## Configuration (parameterizeddefaults are Tekyz values)
21
18
 
22
- Renumber SAFELY (numbering-only, zero value changes):
23
- - **Confirm the register IDs are contiguous** (`min..max`, no gaps) first, so a blind offset (`subtract base−1`) aligns.
24
- - **Range-bounded regex** — remap ONLY numbers in the register's actual range, so you never corrupt `DC-n`, another project's `TD-4`, or a different repo's numbering.
25
- - **Scope to CURRENT deliverables only:** register, plain-English companion, `.gsd-t/scan/*.md`, `docs/*.md`, README, the PRD, `share/*`, and the Google Sheet labels. **Leave `.gsd-t/scan/archive/`, transcripts, heartbeats UNTOUCHED** (historical run numbering).
26
- - **Second pass for non-`TD-` formats:** bare `| 618 |` table cells, slashed chains (`TD-2/623`, `626/629/631`), header text ("begins at TD-618"). A first-pass `TD-NNN` regex misses these.
27
- - **Back up files before the bulk edit.**
28
- - Offer this as an option; a client that wants the original numbering keeps it.
19
+ Read these from `$ARGUMENTS` or `.gsd-t/estimate-config.json` if present; otherwise use the Tekyz defaults. **Always name the active values in the report** (no silent defaults). This config is **optional and NOT auto-created** (it's Tekyz-estimate-specific, not every-project state) — to override the defaults, copy the documented template `templates/estimate-config.json` (in the GSD-T package, or `~/.claude/templates/estimate-config.json`) to `.gsd-t/estimate-config.json` and edit. Every field is individually optional; an omitted field falls back to its Tekyz default.
29
20
 
30
- ## Step 2: Size each finding (T-Shirt Size tab)
21
+ | Param | Default (Tekyz) | Meaning |
22
+ |-------|-----------------|---------|
23
+ | `rate` | `$50/hr` | Blended hourly rate for the LOW figure. |
24
+ | `hoursPerDay` | `8` | Hours per person-day. |
25
+ | `sizeScale` | `XS 0.25 · S 0.5 · M 1 · L 3 · XL 5 · XXL 7` | T-shirt → person-days. |
26
+ | `totalMF` | `0.7` | Overhead multiplier = QA 0.3 + PM 0.1 + Analysis 0.05 + Deployment 0.05 + Buffer 0.2. |
27
+ | `highFactor` | `1.25` | HIGH = LOW × this. |
28
+ | `sheetTemplateId` | (blank) | Optional template to clone; normally blank — operator pastes the target sheet URL at Step 0. |
29
+ | `gcpProject` | `ai-estimator-415612` | GCP project hosting the permanent Sheets-writer SA. |
30
+ | `serviceAccountEmail` | `gsd-t-sheets-writer@ai-estimator-415612.iam.gserviceaccount.com` | **Permanent** SA — share each sheet with this as Editor. |
31
+ | `serviceAccountKeyPath` | `~/.claude/gsd-t-secrets/gsd-t-sheets-writer-key.json` | SA key (chmod 600, outside any repo). |
31
32
 
32
- For each in-scope finding, build a row — cols **A** Module · **B** User Type · **C** Functionality (**include the `(TD-n)`**) · **D** Low-Level Requirement · **E** Phase (MVP) · **F** Web Portal (frontend) size · **G** Backend/API size. **Leave H–L (formulas) alone.**
33
+ ## Step 0: Inputs + Scope + Sheet
33
34
 
34
- - **Size each column INDEPENDENTLY** (FE and BE each get their own letter; blank = 0). Sizes: **XS 0.25 · S 0.5 · M 1 · L 3 · XL 5 · XXL 7** person-days.
35
- - The sheet computes: `Days = F+G` · `MFactor Days = Days × Total MF` · `Total Days = Days + MFactor` · `LOW $ = Total × 8hrs × $50/hr` · `HIGH $ = LOW × High Factor (1.25)`. **Ignore the Phase column.**
35
+ 1. **ASK FOR THE GOOGLE SHEET URL FIRST.** The operator ALWAYS provides an existing sheet to edit — this command never creates the sheet. Prompt: *"Paste the Google Sheet URL for this estimate."* Extract the **sheet ID** = the segment between `/d/` and the next `/` in `https://docs.google.com/spreadsheets/d/<ID>/edit`. Confirm the ID back to the operator.
36
+ 2. **Ensure the sheet is shared with the permanent estimate service account** (details in Step 5). The reusable SA email is **`gsd-t-sheets-writer@ai-estimator-415612.iam.gserviceaccount.com`**. Do a quick read-probe (Step 5's JWT→token→`GET spreadsheets/<id>`): if it returns `403`, the sheet isn't shared yet prompt the operator to share it with that email as **Editor** ("Notify people" unchecked), wait for confirmation, re-probe. If the SA/key doesn't exist yet, Step 5 provisions it once.
37
+ 3. **Resolve the input document** (from `$ARGUMENTS --input`, else default to `.gsd-t/techdebt.md`). If none found → "No input document found. Pass `--input <path>` or run `/gsd-t-scan` first." and stop.
38
+ 4. **Classify the input** so the parser + line-item vocabulary match:
39
+ - **Scan register** (`.gsd-t/techdebt.md`) → line-items are *findings* (`TD-n`), scoped by severity.
40
+ - **Requirements doc / feature spec / app spec / PRD-in** → line-items are *requirements* (`FR-n` or the doc's own numbering).
41
+ 5. **Scope** (from `$ARGUMENTS`): scan default = **all CRITICAL findings** ("close the critical gap"); `--severity high|medium|low|all` widens it. Requirements default = **all requirements** unless the user narrows. Confirm the scope + item count with the user before sizing.
42
+ 6. Confirm the active config values above (rate/MF/highFactor — from `.gsd-t/estimate-config.json` or Tekyz defaults).
43
+ 7. Decide whether this is a **new-team project** (triggers the familiarization adjustment, Step 2.5) — usually YES for a fresh client.
44
+
45
+ ## Step 1: Numbering hygiene (MECHANICAL — show result)
46
+
47
+ Client-facing line-items must carry **sequential, rational numbering starting at 1**. Requirements docs often have NO numbers, or hierarchical numbers, or (rarely) a scan that crashed-and-reran starts high (e.g. TD-618, which looks bad — "why does #1 start at 618?"). (`tekyz-tech-debt-numbering-caution`.)
48
+
49
+ **Renumber ONLY when the numbering is absent, non-sequential, or doesn't start at 1.** If it is already sequential and rational, LEAVE IT.
50
+
51
+ - **Unnumbered input** → assign sequential ids (`FR-1, FR-2, …` or `TD-1, …`).
52
+ - **Hierarchical numbering** (`1`, `1.1`, `1.1.1` = requirement / sub / sub-sub) → **preserve the hierarchy**; renumber only to make each level sequential-and-rational (no gaps, starts at 1 within its parent). Never flatten a hierarchy.
53
+ - **Already-clean numbering** → no change.
54
+ - When you DO renumber, do it SAFELY (numbering-only, zero value changes):
55
+ - Confirm the id range is contiguous first, so a blind offset aligns.
56
+ - **Range-bounded regex** — remap ONLY numbers in the doc's actual range, so you never corrupt `DC-n`, another project's `TD-4`, or a different repo's numbering.
57
+ - **Scope to CURRENT deliverables only:** the input doc, plain-English companion, `.gsd-t/scan/*.md`, `docs/*.md`, README, the PRD, `share/*`, and the Google Sheet labels. **Leave `.gsd-t/scan/archive/`, transcripts, heartbeats UNTOUCHED.**
58
+ - **Second pass for non-prefixed formats:** bare `| 618 |` table cells, slashed chains (`TD-2/623`), header text. A first-pass `PREFIX-NNN` regex misses these.
59
+ - **Back up files before the bulk edit.**
60
+ - A client that wants the original numbering keeps it — offer, don't force.
61
+
62
+ ## Step 2: Size each line-item (T-Shirt Size tab) — JUDGMENT · PAUSE FOR REVIEW
63
+
64
+ For each in-scope item, build a row — cols **A** Module · **B** User Type · **C** Functionality (**include the `(TD-n)` / `(FR-n)`**) · **D** Low-Level Requirement · **E** Phase (MVP) · **F** Web Portal (frontend) size · **G** Backend/API size. **Leave H–L (formulas) alone.**
65
+
66
+ - **Size each column INDEPENDENTLY** (FE and BE each get their own letter; blank = 0). Sizes per `sizeScale`: **XS 0.25 · S 0.5 · M 1 · L 3 · XL 5 · XXL 7** person-days.
67
+ - The sheet computes: `Days = F+G` · `MFactor Days = Days × totalMF` · `Total Days = Days + MFactor` · `LOW $ = Total × hoursPerDay × rate` · `HIGH $ = LOW × highFactor`. **Ignore the Phase column.**
36
68
  - **Cluster by fix-shape to size fast:** "add existing auth guard to N routes" (XS–S, repeated pattern) vs "new backend surface" (M, +FE) vs "config / single route" (XS). Size the cluster once, apply to its members.
37
- - **Tune the MF per project:** `Total MF` (default 0.7) = QA 0.3 + PM 0.1 + Analysis 0.05 + Deployment 0.05 + Buffer 0.2. Raise Buffer/QA when confidence is low; raise **High Factor** above 1.25 for more unknowns.
69
+ - **Tune the MF per project:** raise Buffer/QA when confidence is low; raise `highFactor` above 1.25 for more unknowns.
70
+ - **PAUSE:** present the sized rows (or the clusters + representative sizes) and the running total. Wait for `continue` or corrections before Step 2.5.
38
71
 
39
- ## Step 2.5: Familiarization bump (new-team projects only)
72
+ ## Step 2.5: Estimate Adjustments (familiarization + risk/unknowns) — JUDGMENT · PAUSE FOR REVIEW
40
73
 
41
- Base sizes assume *familiar* devs. For a team new to the codebase, add ramp by **bumping each item's SIZE in proportion to its complexity NOT the MF** (the Analysis MF is for a Business Analyst, not dev ramp). (`tekyz-familiarization-bump`.)
74
+ Base sizes assume *familiar* devs on *well-understood* work. Adjust for the two things that make real work heavier than the naive size. Document each adjustment per-item so the client sees **why** an item is heavier. (`tekyz-familiarization-bump`.)
42
75
 
76
+ **(a) New-team familiarization** — ramp for a team new to the codebase. Bump each item's SIZE in proportion to its complexity — **NOT the MF** (the Analysis MF is for a Business Analyst, not dev ramp).
43
77
  - Trivial config / single-route → **no bump**. Repeated-pattern guards, few routes → **+0–1 tier**. High-volume sweeps + new-surface builds → **+1 tier**.
44
- - **⚠️ The scale is NON-LINEAR. M→L is a 3× cliff (1 day → 3 days).** A blind one-tier bump across M→L doubles the total. **Never push an item across M→L unless it is genuinely multi-day.** Cap bumps at M for routine work.
45
- - Optionally add a one-time **"Codebase Onboarding & Downstream Analysis"** Common line (sized L–XL) for initial ramp on a large repo — separate from per-task build. Document it as an optional line the client can remove.
78
+ - Optionally add a one-time **"Codebase Onboarding & Downstream Analysis"** Common line (sized L–XL) document as optional, client can remove.
79
+
80
+ **(b) R&D / unknown-approach / spike risk** — an item needing research, an unproven approach, or an unknown integration gets an uplift for the uncertainty. Either bump its SIZE, or raise its per-item risk contribution (and consider raising `highFactor` for the whole estimate if unknowns dominate). Name the unknown explicitly ("requires spike: undocumented 3rd-party API").
81
+
82
+ **⚠️ The scale is NON-LINEAR. M→L is a 3× cliff (1 day → 3 days).** A blind one-tier bump across M→L doubles the total. **Never push an item across M→L unless it is genuinely multi-day.** Cap routine-work bumps at M.
46
83
  - Reference calibration: HILO 21 criticals = $8,700 familiar → $11,730 new-team (+35%, bumps capped at M) → $13,090 incl Project Setup.
84
+ - **PAUSE:** present every adjustment (item, reason, before→after size, total delta). Wait for `continue` or corrections.
47
85
 
48
86
  ## Step 3: Group by domain + blue headings (T-Shirt tab)
49
87
 
@@ -59,41 +97,92 @@ Reorder items into domains (**A–G to match the PRD sections**). Insert a **blu
59
97
  - **Check for hardcoded cells** breaking the formula chain (restore `=E×B`, `=J` where a static number was pasted — the Testing row is a known offender).
60
98
  - **Verify BOTH halves (F13, J13) equal the T-shirt total.**
61
99
 
62
- ## Step 5: Write to the Google Sheet (service-account path)
63
-
64
- **gcloud's `spreadsheets` scope is blocked by Google** use a throwaway service account (`google-sheets-service-account-workaround`):
65
-
66
- 1. Confirm Sheets API enabled: `gcloud services list --enabled --project=<proj> --filter="config.name:sheets.googleapis.com"` (else `gcloud services enable sheets.googleapis.com`).
67
- 2. Create a THROWAWAY SA (don't reuse a prod SA): `gcloud iam service-accounts create sheets-writer-tmp --project=<proj>`. **SA creation is eventually-consistent** — poll `describe` before minting the key.
68
- 3. Mint a key: `gcloud iam service-accounts keys create key.json --iam-account=sheets-writer-tmp@<proj>.iam.gserviceaccount.com`.
69
- 4. **PROMPT THE USER to share the sheet with the SA email as Editor** the robot has no access until shared. (If the SA is deleted+recreated the identity changes → must re-share even if the email string is identical.)
70
- 5. Sign a JWT (RS256 via `openssl dgst -sha256 -sign key.pem`), scope `https://www.googleapis.com/auth/spreadsheets`, exchange at `oauth2.googleapis.com/token`, call Sheets v4 REST (`values.../PUT?valueInputOption=USER_ENTERED` for cells; `:batchUpdate` for inserts/formatting/merges). Pure Python stdlib + openssl, no libs. **URL-encode sheet ranges** (spaces in tab names break the URL). Token expires in 1h — regenerate.
71
- 6. **CLEANUP:** `gcloud iam service-accounts delete` the throwaway SA (removes its keys) + `rm` the local key — but leave the SA alive until the user confirms they're done editing, to avoid re-share churn.
72
-
73
- ## Step 6: Generate the PRD
100
+ ## Step 5: Write to the Google Sheet (PERMANENT reusable service account) — MECHANICAL · show result
101
+
102
+ **gcloud's `spreadsheets` OAuth scope is blocked by Google** (browser shows "This app is blocked"; `gcloud auth print-access-token` is unusable for Sheets), so a **service account with a self-signed JWT** is the only working path (`google-sheets-service-account-workaround`). A **PERMANENT, reusable SA already exists** — do NOT create a throwaway per run.
103
+
104
+ **The permanent estimate SA (provisioned once, reused every run):**
105
+ - **Email (the sheet share-target):** `gsd-t-sheets-writer@ai-estimator-415612.iam.gserviceaccount.com`
106
+ - **GCP project:** `ai-estimator-415612` · **Sheets API:** enabled
107
+ - **Key file:** `~/.claude/gsd-t-secrets/gsd-t-sheets-writer-key.json` (chmod 600, outside any reponever commit an SA key)
108
+
109
+ 1. **Self-heal / provision-once (idempotent):** if the key file is missing, recreate the SA + mint a key before proceeding this is the ONLY create path, and it runs at most once ever:
110
+ ```bash
111
+ PROJECT=ai-estimator-415612
112
+ SA=gsd-t-sheets-writer@$PROJECT.iam.gserviceaccount.com
113
+ KEY=~/.claude/gsd-t-secrets/gsd-t-sheets-writer-key.json
114
+ gcloud services enable sheets.googleapis.com --project=$PROJECT
115
+ gcloud iam service-accounts describe "$SA" --project=$PROJECT >/dev/null 2>&1 || \
116
+ gcloud iam service-accounts create gsd-t-sheets-writer \
117
+ --display-name="GSD-T Estimate Sheets Writer (permanent, reusable)" --project=$PROJECT
118
+ # SA creation is eventually-consistent — poll describe before minting the key
119
+ [ -f "$KEY" ] || { mkdir -p ~/.claude/gsd-t-secrets && chmod 700 ~/.claude/gsd-t-secrets && \
120
+ gcloud iam service-accounts keys create "$KEY" --iam-account="$SA" --project=$PROJECT && chmod 600 "$KEY"; }
121
+ ```
122
+ 2. **Share check:** the SA has no access until the sheet is shared with it. If a read-probe (below) returns `403`, **prompt the operator to share the sheet with `gsd-t-sheets-writer@ai-estimator-415612.iam.gserviceaccount.com` as Editor** (uncheck "Notify people"), wait for confirmation, re-probe. Because the SA is permanent, this is a **one-time** share per sheet — no re-share churn.
123
+ 3. **Auth = self-signed JWT** (pure stdlib + openssl, no client libraries): read the key file, sign an RS256 JWT (`openssl dgst -sha256 -sign`), scope `https://www.googleapis.com/auth/spreadsheets`, exchange at `oauth2.googleapis.com/token` (grant `urn:ietf:params:oauth:grant-type:jwt-bearer`) for a Bearer token. JWT `exp = now + 3600`; **mint fresh each run** (1h expiry).
124
+ 4. **Write via Sheets v4 REST** with the Bearer token: `values/<range>/PUT?valueInputOption=USER_ENTERED` for cell values; `:batchUpdate` for inserts/formatting/merges. **Reads:** `GET spreadsheets/<id>?ranges=...&includeGridData=true` (used for the 403 share-probe too).
125
+ 5. **Gotcha fixes (all proven on HILO):**
126
+ - **403 quota-project** on the Sheets API → add header `X-Goog-User-Project: ai-estimator-415612`.
127
+ - **URL-encode every range** (`urllib.parse.quote`) — spaces in tab names (e.g. `'T-Shirt Size Estimate'!A19`) break the URL.
128
+ - **Hardcoded SUMIF/total ranges don't auto-expand** on a plain values-write → use `insertDimension` (which shifts ranges) when inserting rows (ties back to Step 3's WIDEN note).
129
+ - **`drive.file` scope 404s on a pre-existing sheet** → must use the full `spreadsheets` scope.
130
+ 6. **No cleanup / no delete.** The SA is PERMANENT and reused — never delete it, never delete the key. (Deleting + recreating an SA changes its internal id and silently breaks every existing sheet share even with an identical email → 403. Keeping it permanent is the whole point.)
131
+
132
+ ## Step 6: Generate the PRD — JUDGMENT · PAUSE FOR REVIEW
74
133
 
75
134
  **ONE document** (ATOS contractor-handoff template, sections 0–15) with **domain sub-sections (A–G) inside each numbered section** — not one PRD per item. (`tekyz-client-prd-structure`.)
76
135
 
77
136
  - **§0 Metadata + top-of-doc ⚠️ Estimate Basis & Disclaimer** — planning estimates, NOT a quote/bid/fixed price; will change; no not-to-exceed; no delivery guarantee. **Purge all quote/fixed/guarantee/binding language.**
78
- - **§3.0 FRTD crosswalk** — per-domain tables (Requirement · Finding · Fix). `FR-xN` (domain-sequenced requirement id) and `TD-n` (permanent scan finding id) do NOT run in parallel — always crosswalk them.
79
- - **§3.1 FR tables** — a **dedicated Finding column** (never bury `TD-n` in trailing prose).
80
- - **§3.2 NFR** — an **"Applies to" column listing EVERY `TD-n`** each cross-cutting NFR touches.
81
- - **§4 enforcement, §8 API, §10 estimate** — explicit `TD-n` refs (not bare numbers).
137
+ - **§3.0 requirementfinding crosswalk** — per-domain tables (Requirement · Finding · Fix). `FR-xN` (domain-sequenced requirement id) and `TD-n` (permanent scan finding id) do NOT run in parallel — always crosswalk them. (For a pure requirements input with no scan, the "Finding" column becomes the source requirement id.)
138
+ - **§3.1 FR tables** — a **dedicated Finding/Source column** (never bury the id in trailing prose).
139
+ - **§3.2 NFR** — an **"Applies to" column listing EVERY id** each cross-cutting NFR touches.
140
+ - **§4 enforcement, §8 API, §10 estimate** — explicit id refs (not bare numbers).
82
141
  - **§10 total MUST equal the live sheet rollup** — verify against the sheet's rollup cell, not memory. Watch that Project Setup carries its MF (M/M = 2 raw → 3.4 total, not 2.0). Point-in-time sync is fine; note it.
83
142
  - **§15 sign-off = "Approved to proceed (scope, not fixed cost)".**
84
143
  - Group tables **by domain with a bold header per group** (no repeating "Domain" column — reads as broken).
85
144
  - **Save to `share/<Repo-Name>-PRD-*.md`** (repo-name prefix, matching `/gsd-t-scan`'s `share/` convention).
145
+ - **PAUSE:** present the PRD (or its outline + disclaimer + §10 total) for review before finalizing.
146
+
147
+ ## Step 7: Verify — reconcile the three totals (MECHANICAL · show result)
148
+
149
+ **The three totals MUST agree:** T-Shirt Size total = Team Mix total (F13/J13) = PRD §10 total. If any differ, find the break (usually a hardcoded cell or an un-widened SUMIF range) and fix before advancing to the Red Team.
150
+
151
+ ## Step 8: Estimate Red Team (adversarial) — JUDGMENT · PAUSE · YOU ARE THE ARBITER
152
+
153
+ An independent adversarial pass that challenges the estimate before it reaches the client — the estimate-time analogue of GSD-T's build Red Team. **Its job is to protect Tekyz from a money-losing under-estimate AND to keep the estimate competitive** (paired realism, per `feedback_red_team_realism_gate` — don't pad every item to XXL "to be safe").
154
+
155
+ **What it attacks:**
156
+ - **Under-sized items** — "this 'S' implies a DB migration + backfill → really M; +2 days."
157
+ - **Missing line-items** — work a requirement implies but nothing sized (migrations, tests, auth, error states, rollout).
158
+ - **Optimistic multipliers** — Buffer/QA too low for the stated confidence; `highFactor` too tight for the unknowns.
159
+ - **Adjustment gaps** — an R&D/unknown item (Step 2.5b) sized as if it were routine.
160
+ - **Cross-check integrity** — do the three totals ACTUALLY reconcile, or is a hardcoded cell hiding a break.
161
+ - **Assumption / scope gaps** — unstated assumptions that would blow up mid-project.
162
+
163
+ **Verdict:** `FAIL` (material under-estimate or missing scope found) / `GRUDGING-PASS` (exhaustive search, nothing material).
164
+
165
+ ### The arbitration protocol (David is the final judge — NOT bot ping-pong)
166
+
167
+ When the Red Team returns `FAIL`, it does **NOT** loop back-and-forth with the skill until it grudgingly passes. It surfaces to **the operator**:
168
+
169
+ 1. **Present each objection PLAINLY:** *what* shouldn't pass, *why*, and *the estimate impact* (which item, before→after size, dollar delta). One clear list, ranked by dollar impact.
170
+ 2. **The operator decides, per objection:**
171
+ - **Agree** → operator says `continue` → apply the fix.
172
+ - **Disagree** → operator gives feedback → **the Red Team argues back.** The argument continues until **one side concedes** OR the operator ends it definitively.
173
+ 3. **Definitive-decision override:** if the operator says anything conclusive — e.g. *"No more argument. I've decided on X,"* or any clearly final ruling — the Red Team **MUST grudgingly accept the operator's decision immediately, regardless of how many rounds have passed.** It **MAY document its unresolved objection** (in the PRD as a footnote or a `share/<Repo>-estimate-redteam-notes.md` file) **for later consideration** — but it does not re-litigate.
174
+ 4. The Red Team **never self-satisfies into a pass** and **never overrides the operator.** It either persuades the operator or defers to them.
86
175
 
87
- ## Step 7: Verify + Deliver
176
+ **PAUSE** at every objection this phase is inherently interactive.
88
177
 
89
- **Reconcile the three totals — they MUST agree:** T-Shirt Size total = Team Mix total (F13/J13) = PRD §10 total. If any differ, find the break (usually a hardcoded cell or an un-widened SUMIF range) and fix before reporting done.
178
+ ## Step 9: Deliver
90
179
 
91
- All client-facing files land in `share/` with the repo-name prefix. Report: scope + finding count, the eng-days + LOW–HIGH dollar range, the sheet URL, and the PRD path.
180
+ All client-facing files land in `share/` with the repo-name prefix. Report: input type + scope + item count, the active config values (rate/MF/highFactor), the eng-days + LOW–HIGH dollar range, the sheet URL, the PRD path, the Red Team verdict, and any documented-but-overridden Red Team objections.
92
181
 
93
182
  ## Document Ripple
94
183
 
95
- - `share/<Repo>-PRD-*.md` (new PRD deliverable) + the Google Sheet (external).
96
- - If renumbering (Step 1) ran: the register + plain-English + `scan/*.md` + `docs/*` + README + `share/*` were remapped (archives untouched) — note it in the report so the numbering change is traceable.
184
+ - `share/<Repo>-PRD-*.md` (new PRD deliverable) + the Google Sheet (external) + optional `share/<Repo>-estimate-redteam-notes.md` (overridden objections).
185
+ - If renumbering (Step 1) ran: the input + plain-English + `scan/*.md` + `docs/*` + README + `share/*` were remapped (archives untouched) — note it in the report so the numbering change is traceable.
97
186
 
98
187
  ## ▶ Next Up
99
188
 
@@ -359,10 +359,10 @@ Use these when user asks for help on a specific command:
359
359
  - **Use when**: Ready to address technical debt items
360
360
 
361
361
  ### estimate
362
- - **Summary**: Turn a completed scan (`.gsd-t/techdebt.md`) into a Tekyz client estimate (Google Sheet: T-Shirt Size + Team Mix) and a matching PRD deliverable
362
+ - **Summary**: Turn any structured work document — a scan register, a new-feature or new-app requirements doc, or a PRD-in — into a Tekyz client estimate (Google Sheet: T-Shirt Size + Team Mix) and a matching PRD deliverable
363
363
  - **Auto-invoked**: No
364
- - **Updates**: the Tekyz estimate Google Sheet + `share/<Repo>-PRD-*.md` (and, if renumbered, the register/docs/scan files)
365
- - **Use when**: A scan is done and you need a client-facing paid estimate + PRD (T-shirt sizing, dollar range, sign-off). Encodes the 7-phase Tekyz playbook (`~/.claude/playbooks/tekyz-estimation-and-prd-playbook.md`)
364
+ - **Updates**: the Tekyz estimate Google Sheet + `share/<Repo>-PRD-*.md` (and, if renumbered, the source doc/docs/scan files) + optional `share/<Repo>-estimate-redteam-notes.md`
365
+ - **Use when**: You need a client-facing paid estimate + PRD (T-shirt sizing, dollar range, sign-off) from a scan OR a requirements/feature/app spec. **SUPERVISED** — judgment phases (sizing, adjustments, PRD, Red Team) pause for your review; **you are the final arbiter** of an Estimate Red Team that challenges the numbers. Rate + sheet template + factors are parameterized (default Tekyz). Encodes the Tekyz playbook (`~/.claude/playbooks/tekyz-estimation-and-prd-playbook.md`)
366
366
 
367
367
  ### populate
368
368
  - **Summary**: Auto-populate all living docs from existing codebase analysis
@@ -4,6 +4,48 @@ One command to fully onboard a project into GSD-T. Combines project directory se
4
4
 
5
5
  Can be run from anywhere — does not require being in the project folder first.
6
6
 
7
+ ## Entry States (READ FIRST — the command is IDEMPOTENT and SAFE to re-run)
8
+
9
+ This command behaves **identically and unambiguously** across all three entry states:
10
+
11
+ - **(A) Greenfield** — brand-new project, no code, no `.gsd-t/`.
12
+ - **(B) Existing, new to this machine** — has code and/or a `.gsd-t/` dir but is not in this machine's registry.
13
+ - **(C) Already-registered, being re-run.**
14
+
15
+ **Existing `.gsd-t/` artifacts are NEVER a reason to skip scan or setup.** The ONLY thing "already exists" skips is **re-creating directory structure** (Step 3). Steps 4 (scan) and 5 (setup) **ALWAYS run**, on every invocation, in every entry state.
16
+
17
+ **Re-running is expected and safe.** Archiving an existing `techdebt.md` and regenerating scan docs + CLAUDE.md is the **designed behavior** of this command — it is NOT a destructive action and does NOT require user approval. Stale scan docs from a prior milestone are the exact failure this command exists to prevent. Never stop to ask permission for the archive/regenerate steps.
18
+
19
+ ## Step 0: Adopt a Foreign Repo — Sanitize Inherited Context (MANDATORY, runs FIRST)
20
+
21
+ **Why:** projects are frequently imported that were built by others — with plain Claude Code, or with GSD-T on a *different* machine/context. Such a repo arrives carrying **foreign context**: a committed `.claude/settings.json`, a `CLAUDE.md` authored in another environment, and `.gsd-t/` state / git history from a prior GSD-T install. That inherited context leaks and makes the project behave unexpectedly on THIS machine (e.g. an agent hand-authoring a scan from a foreign session's pattern instead of invoking the installed Workflow). **Adoption severs that** so the repo runs the way THIS machine's GSD-T expects — independent of whoever built it.
22
+
23
+ Detect a foreign repo when ANY of these is true: the git remote is not the operator's own, the commit history shows GSD-T/Claude workflow commits (`milestone(...)`, `feat(...task-N)`, `chore(scan)`), there is a committed in-repo settings file, or `.gsd-t/` exists but the project is not in this machine's registry (entry state B).
24
+
25
+ ### 0a. Settings reconciliation (in-repo settings vs GLOBAL — global is source of truth)
26
+
27
+ For each in-repo settings file (`.claude/settings.json`, `.claude/settings.local.json`, or any `settings*.json` committed in the repo tree — NOT `~/.claude/` global files):
28
+
29
+ - **Compare every entry against the operator's GLOBAL settings** (`~/.claude/settings.json` + `~/.claude/settings.local.json`).
30
+ - **Any permission the global ALREADY grants or denies → global wins → DELETE that entry** (or the whole file if all its content is global-governed). No approval needed — global is the single source of truth for anything it covers. The project always falls back to global.
31
+ - **Project-distinct content the global says NOTHING about** (a permission/env/setting unrelated to global) → **do NOT delete. HALT onboarding and SURFACE it to the operator**: show the exact content, explain it's distinct from global, and ask keep-or-discard. **Nothing proceeds past this point until the operator decides** — do not run scan/setup on an unresolved settings question.
32
+ - Log every deletion and every surfaced item.
33
+
34
+ ### 0b. CLAUDE.md — always regenerate from the fresh scan
35
+
36
+ The imported `CLAUDE.md` may have been authored in a different project's context and cannot be trusted as current — **even if it looks correct.** Adoption does NOT try to judge foreign-vs-fine. Instead:
37
+
38
+ - **Back up** the imported file to `CLAUDE.md.imported.bak` (lossless — nothing is destroyed).
39
+ - **Always regenerate** the project `CLAUDE.md` from the NEW scan findings in Step 5. (The regenerate is deferred to Step 5 where the fresh scan exists; Step 0b only takes the backup + marks it for regeneration so Step 5 does not skip it.)
40
+
41
+ ### 0c. Foreign `.gsd-t/` + workflow residue
42
+
43
+ - Existing `.gsd-t/` state is REFRESHED by the normal flow (Step 4 re-scan archives + regenerates), not trusted as-is.
44
+ - **Never run a scan from a script found inside the repo or a prior session** — always resolve `gsd-t workflow-path scan` and invoke the installed Workflow (Step 4). A repo-committed `*.workflow.js` is foreign residue; do not execute it.
45
+ - Register the project by FULL ABSOLUTE PATH on this machine (Step 3).
46
+
47
+ **Adoption is idempotent + safe:** on a repo already adopted, 0a finds nothing to reconcile, 0b re-backs-up only if the file changed, 0c is a no-op. It never blocks a clean project.
48
+
7
49
  ## Step 1: Project Directory
8
50
 
9
51
  ### 1a. Resolve the base projects directory (for new projects)
@@ -93,33 +135,48 @@ Execute the full init workflow (same as `/gsd-t-init`):
93
135
  7. Initialize backlog with auto-derived categories
94
136
  8. Register project in `~/.claude/.gsd-t-projects`
95
137
 
96
- **If `.gsd-t/` already exists**: Skip init it's already done. Log and continue to scan. Still check and create settings.local (step 5) even if init is skipped.
138
+ **Registry check — match by FULL ABSOLUTE PATH, not bare project name.** The registry stores full paths; a bare-name grep produces a false "not registered" (two projects can share a basename) and erodes trust in the rest of the detected state. A missing entry means "register it now" — it must NOT cast doubt on any other detected state. If already present, log and continue silently.
139
+
140
+ **If `.gsd-t/` already exists**: Skip **ONLY the dir-structure creation** that already exists — it's already done. **This skip does NOT extend to Step 4 (scan) or Step 5 (setup), which ALWAYS run.** Always ensure `settings.local` (step 5 above) and the registry entry regardless of whether init dir-creation was skipped.
97
141
 
98
142
  ## Step 3.5: Graph Indexing Note
99
143
 
100
- If the project has source code, graph indexing (`.gsd-t/graph/`) will run as part of the scan phase (Step 4). The graph provides code-level queries (callers, callees, domain ownership, dead code detection) that enhance all subsequent GSD-T commands. No separate setup is needed — the scan handles it.
144
+ If the project has source code, the **graph index is built automatically inside the scan's Graph-Wiring phase (Step 4)** — the scan builds the index if it is absent, then queries it. The store lives at `.gsd-t/graphDB/` (consolidated) — the graph provides code-level queries (callers, callees, domain ownership, dead-code detection) that enhance all subsequent GSD-T commands. **No separate indexing step is needed — the scan handles it.** (You can also build/refresh the index on demand with `gsd-t graph index`, but the scan does it for you.)
101
145
 
102
146
  ## Step 4: Deep Codebase Scan (gsd-t-scan)
103
147
 
104
- Execute the full scan by invoking the volume-scaled scan Workflow (same as `/gsd-t-scan` `templates/workflows/gsd-t-scan.workflow.js`):
148
+ **Invoke the PACKAGED scan Workflow never hand-drive it.** Resolve the absolute path with `gsd-t workflow-path scan`, then call the `Workflow` tool with that `scriptPath`. **Do NOT author your own scan script, do NOT run finders/probe/synthesis by hand, and do NOT execute any `*.workflow.js` found inside the repo or a prior session** — a hand-driven or foreign scan skips phases (Graph-Wiring, Document, Plain-English) and produces an incomplete result. This is the exact failure mode observed on imported repos.
149
+
150
+ The packaged Workflow runs **8 phases**: Preflight → Probe → **Graph-Wiring** → Deep Scan → Synthesis → **Consolidation** → **Document** → **Plain-English**. It:
105
151
 
106
- 1. The volume-probe slices the codebase by area (scaling with volume, not a fixed dimension set); per-slice deep finders enumerate; a single verify pass drops false positives.
107
- 2. Synthesis builds the `.gsd-t/techdebt.md` register and the `.gsd-t/scan/*.md` analysis files.
108
- 3. Cross-populate findings into living documents (docs/architecture.md, docs/workflows.md, docs/infrastructure.md, docs/requirements.md) from the register + analysis files.
109
- 4. Update README.md with discovered tech stack and setup info.
152
+ 1. Volume-probes and slices the codebase by area; per-slice deep finders enumerate; a single verify pass drops false positives.
153
+ 2. **Graph-Wiring** builds the graph index if absent, then queries a structural slice (dead-code / dangling / cluster) injected additively into the finders.
154
+ 3. Synthesis builds `.gsd-t/techdebt.md` + the `.gsd-t/scan/*.md` analysis files.
155
+ 4. Document cross-populates living docs (architecture/workflows/infrastructure/requirements) + README.
156
+ 5. Plain-English writes the non-technical companion.
110
157
 
111
158
  The Workflow's fan-out scales with codebase volume automatically — no team-mode toggle.
112
159
 
113
- **If `.gsd-t/techdebt.md` already exists**: the Workflow archives it to `.gsd-t/techdebt_YYYY-MM-DD.md` and continues TD numbering.
160
+ **ALWAYS run the scan on every invocation, even on a fully-onboarded project.** Existing `techdebt.md` is archived to `techdebt_YYYY-MM-DD.md` and the scan regenerates it (TD numbering continues). **NEVER skip the scan because `.gsd-t/` or `scan/*.md` already exist — stale scan docs are the exact failure this command prevents.** Archiving is designed behavior, NOT a destructive action, and does NOT require user approval.
161
+
162
+ ### 4a. Post-scan completeness check (catches a hand-driven / incomplete scan)
163
+
164
+ After the scan returns, VERIFY the packaged 7-phase Workflow actually ran by confirming its expected outputs exist and are fresh (mtime within this run):
165
+
166
+ - `.gsd-t/techdebt.md` (regenerated) **and** at least the 5 `.gsd-t/scan/*.md` dimension files
167
+ - the merged living docs (architecture/workflows/infrastructure/requirements touched this run)
168
+ - the plain-English companion (`.gsd-t/techdebt_in_plain_english.md` or equivalent)
169
+
170
+ **If any are missing → LOUD failure, do not proceed:** "⛔ Incomplete scan — the packaged Workflow's Document / Plain-English / Graph-Wiring outputs are absent. This means the scan was hand-driven or a foreign/stale script ran instead of the installed Workflow. Re-run: resolve `gsd-t workflow-path scan` and invoke the `Workflow` tool." Then re-invoke correctly, once.
114
171
 
115
172
  ## Step 5: Generate Project CLAUDE.md (gsd-t-setup)
116
173
 
117
- Execute the full setup workflow (same as `/gsd-t-setup`):
174
+ Execute the full setup workflow (same as `/gsd-t-setup`). **ALWAYS run — regenerate/refresh the project CLAUDE.md from the NEW scan findings; do NOT assume an existing CLAUDE.md is current.** If Step 0b flagged an imported CLAUDE.md (backed up to `CLAUDE.md.imported.bak`), this regeneration is what replaces it with a machine-correct version — do NOT skip it.
118
175
 
119
176
  1. Read global `~/.claude/CLAUDE.md` to understand what's already covered
120
- 2. Use scan findings + auto-detection to populate project-specific sections
177
+ 2. Use the **fresh** scan findings + auto-detection to populate project-specific sections
121
178
  3. Remove any global duplicates from the project CLAUDE.md
122
- 4. Generate and write the optimized CLAUDE.md
179
+ 4. Generate and write the optimized CLAUDE.md (refresh the existing one — don't skip because a CLAUDE.md already exists)
123
180
 
124
181
  At Level 3: skip questions that were auto-detected — only ask what's truly unknown.
125
182
  At Level 1-2: ask all targeted questions per the setup workflow.
@@ -136,7 +193,11 @@ Project Onboarded: {project name}
136
193
  Scan: {N} tech debt items ({critical} critical, {high} high, {medium} medium, {low} low)
137
194
  Setup: CLAUDE.md generated ({N} sections)
138
195
  Registry: {registered | already registered}
196
+ ```
197
+
198
+ For an already-onboarded project (entry state B/C), the report reads as **"re-scanned and refreshed,"** never "nothing to do": show the archived `techdebt_YYYY-MM-DD.md` path, the fresh finding counts, and that CLAUDE.md was regenerated.
139
199
 
200
+ ```
140
201
  Next steps:
141
202
  → Review .gsd-t/techdebt.md for critical items
142
203
  → /gsd-t-milestone to define your first milestone
@@ -145,7 +206,7 @@ Next steps:
145
206
 
146
207
  ### Autonomy Behavior
147
208
 
148
- **Level 3 (Full Auto)**: Run all steps without pausing. Only stop if git remote is needed (requires user input) or if a scan reveals critical security blockers.
209
+ **Level 3 (Full Auto)**: Run all steps without pausing. Only stop if git remote is needed (requires user input) or if a scan reveals critical security blockers. **"Already onboarded" NEVER means "nothing to do" — it means "re-scan and refresh."** Do NOT pause to ask permission for the archive/regenerate steps; re-running is expected and safe.
149
210
 
150
211
  **Level 1-2**: Pause after each major step (init, scan, setup) for user review before continuing.
151
212
 
@@ -41,7 +41,7 @@ The current scan architecture (enumerate + per-file deep-finders) is **KEPT FULL
41
41
  preflight → volume-probe → pipeline(per-slice deep finder → single verify) → synthesis → document → render
42
42
  ```
43
43
 
44
- **Why volume-scaled (M66):** the legacy scan hard-coded exactly 5 teammates (one per dimension) with zero volume scaling — a 5-file repo and a 1,809-file repo both got 5 agents, so a single `quality` agent sampled the top ~5 issues across the whole codebase and stopped. The volume-probe stage now measures the codebase (files, routes, ORM tables, components, top-level dirs) and carves it into **narrow slices** — one deep-finder agent per area, scaling from 1-3 slices on a tiny repo to 15-40 on a large one. Each finder OWNS its slice and is mandated to *enumerate, not sample*. A single verify pass confirms each finding against the real code and drops false positives. Synthesis dedups/merges/re-ranks into `techdebt.md` and continues TD numbering from the archived prior register.
44
+ **Why volume-scaled (M66):** the legacy scan hard-coded exactly 5 teammates (one per dimension) with zero volume scaling — a 5-file repo and a 1,809-file repo both got 5 agents, so a single `quality` agent sampled the top ~5 issues across the whole codebase and stopped. The volume-probe stage now measures the codebase (files, routes, ORM tables, components, top-level dirs) and carves it into **narrow slices** — one deep-finder agent per area, scaling from 1-3 slices on a tiny repo to 15-40 on a large one. Each finder OWNS its slice and is mandated to *enumerate, not sample*. A single verify pass confirms each finding against the real code and drops false positives. Synthesis dedups/merges/re-ranks into `techdebt.md` (items **grouped by type within each severity** — Security/Vulnerability, Dead Code, Duplication, etc.) and continues TD numbering from the archived prior register. A **Consolidation** phase (opus + graph-assisted) then appends a **🧩 Consolidation Opportunities** section at the END of the register: high-confidence clusters of TDs that share a root and should be fixed as ONE workstream (duplicate-function families, batch dead-code deletions, a guard missing across N sites), each a candidate for a single consolidation milestone.
45
45
 
46
46
  **Deep document stage (M67):** after the register lands, a `document` phase fans out **one agent per document**, each drawing on the same slices + verified findings, to deterministically produce the full living-document set — `docs/architecture.md` (component map per feature-domain slice), `docs/workflows.md` (a user journey per slice), `docs/infrastructure.md`, `docs/requirements.md`, `README.md` (all merge-not-overwrite) — plus the five `.gsd-t/scan/*.md` dimension files (`architecture`, `security`, `quality`, `business-rules`, `contract-drift`) in the renderer's parsed formats. This replaces M66's non-deterministic "lead-agent follow-on" so the documents are as thorough as the register. The deterministic `bin/scan-*.js` renderers (schema extraction, diagrams, HTML report) run last, reading the deep `.gsd-t/scan/architecture.md`.
47
47
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "4.16.10",
3
+ "version": "4.18.10",
4
4
  "description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
5
5
  "author": "Tekyz, Inc.",
6
6
  "license": "MIT",
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "Optional config for /gsd-t-estimate. Copy to .gsd-t/estimate-config.json and edit. ALL fields are optional — any omitted field falls back to the Tekyz default shown here. The active values are always named in the estimate report (no silent defaults).",
3
+
4
+ "rate": 50,
5
+ "_rate": "Blended hourly rate (USD) used for the LOW dollar figure. Tekyz default: 50.",
6
+
7
+ "hoursPerDay": 8,
8
+ "_hoursPerDay": "Hours per person-day. Default: 8.",
9
+
10
+ "sizeScale": { "XS": 0.25, "S": 0.5, "M": 1, "L": 3, "XL": 5, "XXL": 7 },
11
+ "_sizeScale": "T-shirt size -> person-days. NON-LINEAR: M->L is a 3x cliff (1 -> 3 days). Do not push routine work across M->L.",
12
+
13
+ "totalMF": 0.7,
14
+ "_totalMF": "Overhead multiplier applied to raw Days. Tekyz default 0.7 = QA 0.3 + PM 0.1 + Analysis 0.05 + Deployment 0.05 + Buffer 0.2. Raise Buffer/QA when confidence is low.",
15
+
16
+ "mfBreakdown": { "qa": 0.3, "pm": 0.1, "analysis": 0.05, "deployment": 0.05, "buffer": 0.2 },
17
+ "_mfBreakdown": "Optional itemized breakdown of totalMF (must sum to totalMF). Tune per project; e.g. raise buffer for low-confidence scope.",
18
+
19
+ "highFactor": 1.25,
20
+ "_highFactor": "HIGH dollar figure = LOW x this. Raise above 1.25 when unknowns dominate (many R&D / spike items).",
21
+
22
+ "currency": "USD",
23
+ "_currency": "Currency label for the report. Default: USD.",
24
+
25
+ "sheetTemplateId": "",
26
+ "_sheetTemplateId": "Optional. Google Sheet ID of a template to clone. Normally BLANK — the operator pastes the target sheet URL at run start (Step 0) and the command extracts the ID.",
27
+
28
+ "gcpProject": "ai-estimator-415612",
29
+ "_gcpProject": "GCP project hosting the PERMANENT reusable Sheets-writer service account. Default: ai-estimator-415612 (AI Estimator).",
30
+
31
+ "serviceAccountEmail": "gsd-t-sheets-writer@ai-estimator-415612.iam.gserviceaccount.com",
32
+ "_serviceAccountEmail": "The PERMANENT reusable SA — share each target sheet with THIS email as Editor. Never deleted/recreated (recreation breaks existing shares).",
33
+
34
+ "serviceAccountKeyPath": "~/.claude/gsd-t-secrets/gsd-t-sheets-writer-key.json",
35
+ "_serviceAccountKeyPath": "Local path to the SA JSON key (chmod 600, outside any repo — NEVER commit). Step 5 self-provisions the SA+key here if missing.",
36
+
37
+ "newTeamDefault": true,
38
+ "_newTeamDefault": "Whether to assume a new-team familiarization adjustment (Step 2.5a) by default. Usually true for a fresh client."
39
+ }
@@ -1,9 +1,17 @@
1
1
  # Tekyz Estimation + PRD Playbook
2
2
 
3
3
  > Reusable procedure for producing a Tekyz client estimate (Google Sheet: T-Shirt
4
- > Size + Team Mix) and a matching PRD deliverable, from a GSD-T tech-debt scan.
5
- > Proven on the HILO Figma ATOS project (21 criticals 32.73 eng-days →
6
- > $13,090–$16,362). Apply to E-Learning and any other GSD-T client project.
4
+ > Size + Team Mix) and a matching PRD deliverable, from **any structured work
5
+ > document** a GSD-T tech-debt scan register, a new-feature or new-application
6
+ > requirements doc, or an existing PRD. Proven on the HILO Figma ATOS project
7
+ > (21 criticals → 32.73 eng-days → $13,090–$16,362). Apply to E-Learning and any
8
+ > other GSD-T client project.
9
+ >
10
+ > **This process is SUPERVISED and JUDGMENT-HEAVY.** The operator reviews the
11
+ > judgment phases (sizing, adjustments, PRD) and is the FINAL ARBITER of the
12
+ > Estimate Red Team (Phase 8). It does not run autonomously. Rate + sheet template
13
+ > + factors are parameterized (defaults are the Tekyz values below). See
14
+ > `commands/gsd-t-estimate.md` for the authoritative, current phase list.
7
15
 
8
16
  Related memory: `tekyz-estimation-method`, `tekyz-familiarization-bump`,
9
17
  `tekyz-client-prd-structure`, `tekyz-tech-debt-numbering-caution`,
@@ -81,10 +89,20 @@ cells does NOT auto-expand hardcoded ranges; only `insertDimension` shifts them.
81
89
 
82
90
  ## Phase 5 — Writing to the Google Sheet (auth)
83
91
 
84
- gcloud's `spreadsheets` scope is **blocked by Google**. Use a **throwaway service
85
- account** (see `google-sheets-service-account-workaround` memory): create SA → mint
86
- key user shares sheet with SA email as Editor → JWT (RS256 via openssl) → token →
87
- Sheets v4 REST (`values PUT` / `:batchUpdate`). Clean up the SA + key when done.
92
+ gcloud's `spreadsheets` OAuth scope is **blocked by Google** (user ADC unusable).
93
+ Use the **PERMANENT reusable service account** (see
94
+ `google-sheets-service-account-workaround` memory + `commands/gsd-t-estimate.md`
95
+ Step 5 for the authoritative runbook):
96
+ - **SA (share-target):** `gsd-t-sheets-writer@ai-estimator-415612.iam.gserviceaccount.com`
97
+ - **Project:** `ai-estimator-415612` · **Key:** `~/.claude/gsd-t-secrets/gsd-t-sheets-writer-key.json` (chmod 600).
98
+
99
+ Flow: operator pastes sheet URL → extract id → shares sheet with the SA email as
100
+ Editor (one-time per sheet — SA is permanent) → JWT (RS256 via openssl) → token →
101
+ Sheets v4 REST (`values PUT` / `:batchUpdate`; reads via `GET ...includeGridData`).
102
+ Gotchas: `X-Goog-User-Project` header for 403 quota; `urllib.parse.quote` every
103
+ range; `insertDimension` to shift hardcoded SUMIF ranges; full `spreadsheets` scope
104
+ (not `drive.file`); JWT `exp=now+3600`, mint fresh each run.
105
+ **NEVER delete/recreate the SA** — recreation breaks every existing sheet share.
88
106
 
89
107
  ---
90
108
 
@@ -34,7 +34,8 @@ export const meta = {
34
34
  { title: "Probe", detail: "volume probe → per-area slice list", model: "sonnet" },
35
35
  { title: "Graph-Wiring", detail: "M94-D6: build index if absent + query structural slice (dead-code/dangling/cluster) → inject into finders ADDITIVELY; fallback announced on graph-unavailable", model: "haiku" },
36
36
  { title: "Deep Scan", detail: "pipeline: per-slice deep finder (graph-augmented when wired) → single verify" },
37
- { title: "Synthesis", detail: "archive prior + write fresh register + git", model: "opus" },
37
+ { title: "Synthesis", detail: "archive prior + write fresh register (type-grouped within severity) + git", model: "opus" },
38
+ { title: "Consolidation", detail: "opus + graph-assisted: high-confidence clusters of TDs to fix as one workstream → appended to register", model: "opus" },
38
39
  { title: "Document", detail: "living docs + 5 dimension files (per-doc fan-out)" },
39
40
  { title: "Plain-English", detail: "non-technical companion: batched gen + severity-grouped chunked write" },
40
41
  ],
@@ -785,6 +786,35 @@ function ascii(s) {
785
786
  .replace(/…/g, "...") // ellipsis
786
787
  .replace(/[ \t]+\n/g, "\n"); // tidy trailing whitespace
787
788
  }
789
+
790
+ // Register ordering — SINGLE source of truth so the formatter's TD-numbering and the
791
+ // consolidation stage's TD references stay identical. Within each severity, findings
792
+ // are sub-grouped by TYPE (derived from `area`). typeOf() maps free-text area→bucket;
793
+ // unmatched → "Other". `orderedFindings` is the final, numbered sequence (TD-tdStart..).
794
+ function typeOf(f) {
795
+ const a = String(f.area || "").toLowerCase() + " " + String(f.title || "").toLowerCase();
796
+ if (/(vuln|idor|auth|injection|xss|csrf|secret|privilege|tenant|security|rce|ssrf)/.test(a)) return "Security / Vulnerability";
797
+ if (/(dead.?code|unreachable|unused|orphan|never (called|used|imported))/.test(a)) return "Dead Code";
798
+ if (/(duplicat|redundant|reimplement|copy)/.test(a)) return "Duplication";
799
+ if (/(data.?integrity|race|concurren|transaction|idempoten|double|constraint)/.test(a)) return "Data Integrity / Concurrency";
800
+ if (/(perf|n\+1|slow|latency|batch|query.?count)/.test(a)) return "Performance";
801
+ if (/(contract.?drift|schema.?drift|api.?mismatch|404|endpoint.*(missing|not exist))/.test(a)) return "Contract Drift";
802
+ if (/(test|coverage|shallow|flaky)/.test(a)) return "Testing";
803
+ return "Other";
804
+ }
805
+ const TYPE_ORDER = ["Security / Vulnerability", "Dead Code", "Duplication", "Data Integrity / Concurrency", "Performance", "Contract Drift", "Testing", "Other"];
806
+ const SEV_ORDER2 = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
807
+ // Stable sort: severity, then type, then original index. Computed ONCE; both consumers use it.
808
+ const orderedFindings = finalFindings
809
+ .map((f, i) => ({ f, i, t: typeOf(f) }))
810
+ .sort((a, b) => {
811
+ const sv = (SEV_ORDER2[a.f.severity] ?? 9) - (SEV_ORDER2[b.f.severity] ?? 9);
812
+ if (sv !== 0) return sv;
813
+ const tv = TYPE_ORDER.indexOf(a.t) - TYPE_ORDER.indexOf(b.t);
814
+ if (tv !== 0) return tv;
815
+ return a.i - b.i;
816
+ });
817
+
788
818
  function fmtChunks(today) {
789
819
  const sevHead = { CRITICAL: "🔴 Critical", HIGH: "🟠 High", MEDIUM: "🟡 Medium", LOW: "🟢 Low" };
790
820
  const head = [];
@@ -820,11 +850,17 @@ function fmtChunks(today) {
820
850
 
821
851
  const CHUNK_MAX = 30000;
822
852
  const chunks = [head.join("\n")];
823
- let buf = "", n = tdStart, lastSev = null;
853
+ let buf = "", n = tdStart, lastSev = null, lastType = null;
824
854
  const flush = () => { if (buf) { chunks.push(buf); buf = ""; } };
825
- for (const f of finalFindings) {
855
+ // Consume the shared `orderedFindings` (severity type → original-index) so the
856
+ // TD numbers assigned here are IDENTICAL to those the consolidation stage references.
857
+ for (const { f, t } of orderedFindings) {
826
858
  let piece = "";
827
- if (f.severity !== lastSev) { piece += `\n## ${sevHead[f.severity] || f.severity} Priority\n\n`; lastSev = f.severity; }
859
+ if (f.severity !== lastSev) { piece += `\n## ${sevHead[f.severity] || f.severity} Priority\n\n`; lastSev = f.severity; lastType = null; }
860
+ // Type sub-heading uses a bold marker line (NOT `###`) so it never collides with
861
+ // the `### TD-N` item headings that downstream tools grep for. ASCII hyphens only
862
+ // (M76: no em/en-dashes in fmtChunks literals).
863
+ if (t !== lastType) { piece += `**-- ${t} --**\n\n`; lastType = t; }
828
864
  piece += itemMd(f, n++);
829
865
  if (buf.length + piece.length > CHUNK_MAX) flush();
830
866
  buf += piece;
@@ -889,6 +925,99 @@ log(`register written in ${chunkOk}/${chunks.length} chunks (${counts.total} fin
889
925
  const synthesis = { status: "written", counts, archivePath, tdRange: `TD-${tdStart}..TD-${lastTd}`, finalFindings };
890
926
  log(`register written: ${JSON.stringify(synthesis.counts)} (${synthesis.tdRange})`);
891
927
 
928
+ // ── Consolidation Opportunities (opus + graph-assisted) ────────────────────────
929
+ // After the register is written (TD numbers final), cluster HIGH-CONFIDENCE groups
930
+ // of TDs that share a root and should be fixed as ONE workstream (duplicate-function
931
+ // families, batch dead-code deletions, a guard-pattern missing across N sites, god-file
932
+ // splits). Only tight clusters — ungrouped TDs stay standalone (user directive). The
933
+ // section is APPENDED to the END of the register, after all individual items.
934
+ // Graph-assisted: an agent may run `gsd-t graph who-imports/who-calls/blast-radius`
935
+ // (project-local bin first, else global) on a TD's files to confirm two TDs truly share
936
+ // code before grouping — grounds the clustering in real relationships, not just prose
937
+ // similarity. Best-effort: any failure leaves the register intact (no section).
938
+ phase("Consolidation");
939
+ try {
940
+ // Number from the SHARED `orderedFindings` so each TD-N here is EXACTLY the id the
941
+ // register wrote (severity → type → original-index — the single ordering source).
942
+ let _n = tdStart;
943
+ const consInput = orderedFindings.map(({ f }) => {
944
+ const td = _n++;
945
+ return `TD-${td} [${f.severity}] (${ascii(f.area) || "?"}) ${ascii(f.title)} @ ${(f.files && f.files[0]) || "?"}`;
946
+ }).join("\n");
947
+ const CONS_SCHEMA = {
948
+ type: "object", required: ["groups"], additionalProperties: false,
949
+ properties: {
950
+ groups: {
951
+ type: "array",
952
+ items: {
953
+ type: "object", required: ["title", "members", "sharedRoot", "recommendedAction"], additionalProperties: false,
954
+ properties: {
955
+ title: { type: "string" },
956
+ members: { type: "array", items: { type: "string" } }, // e.g. ["TD-75","TD-143"]
957
+ sharedRoot: { type: "string" },
958
+ recommendedAction: { type: "string" },
959
+ effort: { type: "string" }, // GSD-T-native units
960
+ },
961
+ },
962
+ },
963
+ notes: { type: "string" },
964
+ },
965
+ };
966
+ const consAgent = await agent(
967
+ [
968
+ `You are identifying CONSOLIDATION OPPORTUNITIES in a tech-debt register — sets of findings that share a ROOT and should be fixed as ONE workstream, not one-by-one.`,
969
+ `Below are ${finalFindings.length} findings as "TD-N [SEVERITY] (area) title @ location".`,
970
+ ``,
971
+ `Group ONLY HIGH-CONFIDENCE clusters (user directive — tight, not loose themes):`,
972
+ ` • duplicate-functionality families (same logic implemented 2+ times / diverging copies)`,
973
+ ` • batch dead-code deletions (multiple never-used modules/components removable together)`,
974
+ ` • a single missing pattern repeated across N sites (e.g. the same auth guard absent on many routes)`,
975
+ ` • god-file / god-object splits touching several TDs`,
976
+ ` • the SAME remediation applied in multiple places`,
977
+ `Findings with no strong shared root MUST be left OUT (they stay standalone — do not force weak groupings).`,
978
+ ``,
979
+ `GRAPH-ASSIST (optional, strengthens confidence): to confirm two TDs truly share code, you MAY run the code graph via Bash on their files — try a project-local binary first then the global one:`,
980
+ ` \`${projectDir}/bin/gsd-t-graph-query-cli.cjs who-imports <file>\` OR \`gsd-t graph who-imports <file>\` (also who-calls / blast-radius). If the graph is unavailable, fall back to the location/area text — do NOT fail.`,
981
+ ``,
982
+ `Return "groups": each = { title, members: ["TD-N",...] (>=2), sharedRoot, recommendedAction (which to keep/delete/extract), effort (GSD-T-native units: domain/wave/spawn count — NEVER human-hours) }. If nothing clusters with high confidence, return groups: [].`,
983
+ ``,
984
+ consInput.slice(0, 140000),
985
+ ].join("\n"),
986
+ { label: "consolidation:cluster", phase: "Consolidation", schema: CONS_SCHEMA, model: "opus" }
987
+ ).catch((e) => { log(`consolidation cluster failed (non-fatal): ${e && e.message}`); return null; });
988
+
989
+ const groups = (consAgent && Array.isArray(consAgent.groups)) ? consAgent.groups : [];
990
+ if (groups.length) {
991
+ const cs = [
992
+ ``, `---`, ``, `## 🧩 Consolidation Opportunities`, ``,
993
+ `> High-confidence clusters of findings that share a root cause and should be addressed as ONE workstream (candidates for a single consolidation milestone). Findings not listed here have no strong shared root and stand alone. Effort is in GSD-T-native units.`, ``,
994
+ ];
995
+ groups.forEach((g, gi) => {
996
+ cs.push(`### CG-${gi + 1} - ${ascii(g.title)}`);
997
+ cs.push(`- **Members:** ${(g.members || []).map((m) => ascii(m)).join(", ")}`);
998
+ cs.push(`- **Shared root:** ${ascii(g.sharedRoot)}`);
999
+ cs.push(`- **Recommended action:** ${ascii(g.recommendedAction)}`);
1000
+ if (g.effort) cs.push(`- **Effort:** ${ascii(g.effort)}`);
1001
+ cs.push("");
1002
+ });
1003
+ if (consAgent.notes) cs.push(`> Notes: ${ascii(consAgent.notes)}`, "");
1004
+ // Append via a bounded agent Bash heredoc (orchestrator has no fs).
1005
+ const consBlock = cs.join("\n");
1006
+ await agent(
1007
+ [
1008
+ `APPEND EXACTLY the content between the markers to the END of \`${regPath}\` (append — do NOT overwrite existing content). Use a Bash heredoc: \`cat >> ${regPath} <<'GSDTEOF'\` … \`GSDTEOF\`. After writing, reply ONLY "OK".`,
1009
+ ``, `<<<CHUNK>>>`, consBlock, `<<<END_CHUNK>>>`,
1010
+ ].join("\n"),
1011
+ { label: "consolidation:write", phase: "Consolidation", model: "haiku" }
1012
+ ).catch((e) => log(`consolidation append uncertain (non-fatal): ${e && e.message}`));
1013
+ log(`consolidation: ${groups.length} high-confidence group(s) appended to register`);
1014
+ } else {
1015
+ log(`consolidation: no high-confidence clusters found (register unchanged)`);
1016
+ }
1017
+ } catch (e) {
1018
+ log(`consolidation phase failed (non-fatal — register intact): ${e && e.message}`);
1019
+ }
1020
+
892
1021
  // Document — per-doc fan-out. Each agent writes its file via Write/Edit (its tools).
893
1022
  // The orchestrator passes the findings + (for plain-english) tells the agent to
894
1023
  // READ the just-written register itself, since the orchestrator can't read it.