@tekyzinc/gsd-t 4.4.11 → 4.6.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.
@@ -14,7 +14,32 @@ The agent defines the milestone — origin, goal, falsifiable success criteria
14
14
 
15
15
  Read `.gsd-t/progress.md` (current version + completed milestones), `docs/requirements.md`, and `docs/architecture.md` so the new milestone is framed against existing state.
16
16
 
17
- ## Step 2: Invoke the phase Workflow
17
+ ## Step 2: Resolve the active model profile (M86 — invoke-time injection)
18
+
19
+ Before calling the Workflow, resolve the active model profile to build the `overrides` map:
20
+
21
+ ```bash
22
+ # Run via Bash at invoke time:
23
+ gsd-t model-profile resolve --json
24
+ # Bare form (NO --profile flag): reads .gsd-t/model-profile.json — profile AND stageOverrides
25
+ # (set-stage overrides MUST win — contract precedence; --profile is a config-blind diagnostic
26
+ # form that ZEROES stageOverrides and must never be used for invocation — Red Team M86 r3)
27
+ ```
28
+
29
+ **Resolver-failure handling (M86 — pre-mortem c2 #2):** if the resolve call fails, do NOT
30
+ silently proceed on the premium fallback. Either HALT with `blocked-needs-human`, or proceed
31
+ ONLY with a loud, surfaced warning:
32
+ ```
33
+ ⚠ model-profile resolver unavailable — running on PREMIUM fallback literals
34
+ (configured profile unknown; stale global binary may lack model-profile subcommand)
35
+ ```
36
+
37
+ Also surface a SUCCESSFUL resolve that carries a `configError` field (the resolver returns a
38
+ named default + `configError` for malformed/hand-edited configs — Red Team M86): print the
39
+ `configError` as a visible warning naming the effective profile before proceeding. A clean-looking
40
+ run on a posture the user did not configure is the same silent-spend failure class.
41
+
42
+ ## Step 3: Invoke the phase Workflow
18
43
 
19
44
  ```js
20
45
  {
@@ -25,7 +50,10 @@ Read `.gsd-t/progress.md` (current version + completed milestones), `docs/requir
25
50
  args: {
26
51
  phase: "milestone",
27
52
  projectDir: ".",
28
- userInput: "$ARGUMENTS"
53
+ userInput: "$ARGUMENTS",
54
+ // M86: inject the resolved overrides map (probe + judge use this).
55
+ // Pass {} when the resolver failed AND you chose the loud-warning path (not halt).
56
+ overrides: { /* ...from resolver result.overrides, or {} on failure */ }
29
57
  // M84 Competition Mode is AUTOMATIC — do NOT pass `competition` by default.
30
58
  // The workflow probes (opus) and self-decides; milestone decomposition is the
31
59
  // highest-altitude decision, so it competes whenever ≥2 genuinely different
@@ -37,7 +65,7 @@ Read `.gsd-t/progress.md` (current version + completed milestones), `docs/requir
37
65
 
38
66
  **Competition Mode (automatic).** Milestone decomposition auto-competes when the probe finds ≥2 genuinely different strategies. Because a decomposition is a *coupled thesis*, the judge selects one winner whole (pick-one) and salvages only non-overlapping good line-items from the losers — it never Frankensteins. No flag needed; override with `--no-competition` / `--competition N` on explicit request. See `.gsd-t/contracts/competition-mode-contract.md`.
39
67
 
40
- ## Step 3: Interpret the result
68
+ ## Step 4: Interpret the result
41
69
 
42
70
  The Workflow returns `{ status, artifacts, summary, decisions }` (plus `competition: { n, winner, ranked }` when Competition Mode ran).
43
71
 
@@ -16,7 +16,39 @@ The agent decomposes the milestone into 2–5 file-disjoint domains, writes `.gs
16
16
 
17
17
  Read `.gsd-t/progress.md` to determine the active milestone and its defined scope. If a scan exists and is stale (>10 commits or >14 days), the agent refreshes the relevant dimensions before partitioning.
18
18
 
19
- ## Step 2: Invoke the phase Workflow
19
+ ## Step 2: Resolve the active model profile (M86 — invoke-time injection)
20
+
21
+ Before calling the Workflow, resolve the active model profile to build the `overrides` map:
22
+
23
+ ```bash
24
+ # Run via Bash at invoke time:
25
+ gsd-t model-profile resolve --json
26
+ # Bare form (NO --profile flag): reads .gsd-t/model-profile.json — profile AND stageOverrides
27
+ # (set-stage overrides MUST win — contract precedence; --profile is a config-blind diagnostic
28
+ # form that ZEROES stageOverrides and must never be used for invocation — Red Team M86 r3)
29
+ # Output: { "ok": true, "profile": "...", "overrides": { "stage-key": "concrete-model-id", ... } }
30
+ ```
31
+
32
+ **Resolver-failure handling (M86 — SC(f), pre-mortem c2 #2):** if the resolve call fails
33
+ (`{ok:false}`, spawn error, or the `model-profile` subcommand is not present in the installed
34
+ binary), do NOT silently proceed on the premium fallback. Either:
35
+ - HALT with `blocked-needs-human` and explain the resolver is unavailable; OR
36
+ - Proceed ONLY with a **loud, surfaced warning** that names the effective posture:
37
+
38
+ ```
39
+ ⚠ model-profile resolver unavailable — running on PREMIUM fallback literals
40
+ (configured profile unknown; stale global binary may lack model-profile subcommand)
41
+ ```
42
+
43
+ A configured-standard project silently billing premium fable post-promo is the exact inverse of
44
+ the spend-switch goal. Never silently fall through.
45
+
46
+ Also surface a SUCCESSFUL resolve that carries a `configError` field (the resolver returns a
47
+ named default + `configError` for malformed/hand-edited configs — Red Team M86): print the
48
+ `configError` as a visible warning naming the effective profile before proceeding. A clean-looking
49
+ run on a posture the user did not configure is the same silent-spend failure class.
50
+
51
+ ## Step 3: Invoke the phase Workflow
20
52
 
21
53
  Call the `Workflow` tool with:
22
54
 
@@ -30,7 +62,11 @@ Call the `Workflow` tool with:
30
62
  phase: "partition",
31
63
  milestone: "M{NN}",
32
64
  projectDir: ".",
33
- userInput: "$ARGUMENTS"
65
+ userInput: "$ARGUMENTS",
66
+ // M86: inject the resolved overrides map so the workflow's ?? forms pick up the
67
+ // profile-tier assignments instead of falling back to the premium literals.
68
+ // Pass {} when the resolver failed AND you chose the loud-warning path (not halt).
69
+ overrides: { /* ...from resolver result.overrides, or {} on failure */ }
34
70
  // M84 Competition Mode is AUTOMATIC — do NOT pass `competition` by default.
35
71
  // The workflow runs a solution-space probe and self-decides whether to fan out
36
72
  // N candidate partitions (judged by the file-disjointness oracle). Only pass an
@@ -42,7 +78,7 @@ Call the `Workflow` tool with:
42
78
 
43
79
  **Competition Mode (automatic).** Partition auto-competes when the workflow's probe finds ≥2 genuinely different ways to carve the domains; the objective file-disjointness oracle judges the candidates and picks the most-parallelizable valid one. No flag needed. Override only on explicit request: `/gsd-t-partition --no-competition` (force single draft) or `--competition N` (force N). See `.gsd-t/contracts/competition-mode-contract.md`.
44
80
 
45
- ## Step 3: Interpret the result
81
+ ## Step 4: Interpret the result
46
82
 
47
83
  The Workflow returns `{ status, artifacts, summary, decisions }` (plus `competition: { n, winner, ranked }` when Competition Mode ran).
48
84
 
@@ -14,7 +14,32 @@ For each domain, the agent writes atomic `tasks.md` entries in parallel-executio
14
14
 
15
15
  Read `.gsd-t/progress.md` and each domain's `scope.md`/`constraints.md`. The partition output is the input to planning.
16
16
 
17
- ## Step 2: Invoke the phase Workflow
17
+ ## Step 2: Resolve the active model profile (M86 — invoke-time injection)
18
+
19
+ Before calling the Workflow, resolve the active model profile to build the `overrides` map:
20
+
21
+ ```bash
22
+ # Run via Bash at invoke time:
23
+ gsd-t model-profile resolve --json
24
+ # Bare form (NO --profile flag): reads .gsd-t/model-profile.json — profile AND stageOverrides
25
+ # (set-stage overrides MUST win — contract precedence; --profile is a config-blind diagnostic
26
+ # form that ZEROES stageOverrides and must never be used for invocation — Red Team M86 r3)
27
+ ```
28
+
29
+ **Resolver-failure handling (M86 — pre-mortem c2 #2):** if the resolve call fails, do NOT
30
+ silently proceed on the premium fallback. Either HALT with `blocked-needs-human`, or proceed
31
+ ONLY with a loud, surfaced warning:
32
+ ```
33
+ ⚠ model-profile resolver unavailable — running on PREMIUM fallback literals
34
+ (configured profile unknown; stale global binary may lack model-profile subcommand)
35
+ ```
36
+
37
+ Also surface a SUCCESSFUL resolve that carries a `configError` field (the resolver returns a
38
+ named default + `configError` for malformed/hand-edited configs — Red Team M86): print the
39
+ `configError` as a visible warning naming the effective profile before proceeding. A clean-looking
40
+ run on a posture the user did not configure is the same silent-spend failure class.
41
+
42
+ ## Step 3: Invoke the phase Workflow
18
43
 
19
44
  ```js
20
45
  {
@@ -26,12 +51,15 @@ Read `.gsd-t/progress.md` and each domain's `scope.md`/`constraints.md`. The par
26
51
  phase: "plan",
27
52
  milestone: "M{NN}",
28
53
  projectDir: ".",
29
- userInput: "$ARGUMENTS"
54
+ userInput: "$ARGUMENTS",
55
+ // M86: inject the resolved overrides map (pre-mortem uses this).
56
+ // Pass {} when the resolver failed AND you chose the loud-warning path (not halt).
57
+ overrides: { /* ...from resolver result.overrides, or {} on failure */ }
30
58
  }
31
59
  }
32
60
  ```
33
61
 
34
- ## Step 3: Interpret the result
62
+ ## Step 4: Interpret the result
35
63
 
36
64
  The Workflow returns `{ status, artifacts, summary, decisions, traceability?, preMortem? }`.
37
65
 
@@ -14,7 +14,32 @@ The agent takes a user's idea — however rough — reads available GSD-T projec
14
14
 
15
15
  Read any existing `docs/requirements.md`, `docs/architecture.md`, and `.gsd-t/progress.md`. Capture the user's idea from `$ARGUMENTS`.
16
16
 
17
- ## Step 2: Invoke the phase Workflow
17
+ ## Step 2: Resolve the active model profile (M86 — invoke-time injection)
18
+
19
+ Before calling the Workflow, resolve the active model profile to build the `overrides` map:
20
+
21
+ ```bash
22
+ # Run via Bash at invoke time:
23
+ gsd-t model-profile resolve --json
24
+ # Bare form (NO --profile flag): reads .gsd-t/model-profile.json — profile AND stageOverrides
25
+ # (set-stage overrides MUST win — contract precedence; --profile is a config-blind diagnostic
26
+ # form that ZEROES stageOverrides and must never be used for invocation — Red Team M86 r3)
27
+ ```
28
+
29
+ **Resolver-failure handling (M86 — pre-mortem c2 #2):** if the resolve call fails, do NOT
30
+ silently proceed on the premium fallback. Either HALT with `blocked-needs-human`, or proceed
31
+ ONLY with a loud, surfaced warning:
32
+ ```
33
+ ⚠ model-profile resolver unavailable — running on PREMIUM fallback literals
34
+ (configured profile unknown; stale global binary may lack model-profile subcommand)
35
+ ```
36
+
37
+ Also surface a SUCCESSFUL resolve that carries a `configError` field (the resolver returns a
38
+ named default + `configError` for malformed/hand-edited configs — Red Team M86): print the
39
+ `configError` as a visible warning naming the effective profile before proceeding. A clean-looking
40
+ run on a posture the user did not configure is the same silent-spend failure class.
41
+
42
+ ## Step 3: Invoke the phase Workflow
18
43
 
19
44
  ```js
20
45
  {
@@ -25,12 +50,15 @@ Read any existing `docs/requirements.md`, `docs/architecture.md`, and `.gsd-t/pr
25
50
  args: {
26
51
  phase: "prd",
27
52
  projectDir: ".",
28
- userInput: "$ARGUMENTS"
53
+ userInput: "$ARGUMENTS",
54
+ // M86: inject the resolved overrides map.
55
+ // Pass {} when the resolver failed AND you chose the loud-warning path (not halt).
56
+ overrides: { /* ...from resolver result.overrides, or {} on failure */ }
29
57
  }
30
58
  }
31
59
  ```
32
60
 
33
- ## Step 3: Interpret the result
61
+ ## Step 4: Interpret the result
34
62
 
35
63
  The Workflow returns `{ status, artifacts, summary, decisions }`.
36
64
 
@@ -54,6 +54,7 @@ Present a concise status to the user:
54
54
  ```
55
55
  📊 GSD-T Status: {milestone name}
56
56
  Phase: {PARTITIONED | DISCUSSED | PLANNED | EXECUTING | INTEGRATED | VERIFIED}
57
+ Model Profile: {profile-name} [{(default)} if no per-project config set]
57
58
 
58
59
  Domains:
59
60
  {domain-1}: {completed}/{total} tasks {✅ done | 🔄 in progress | ⏳ blocked}
@@ -72,6 +73,19 @@ Recent decisions:
72
73
  - {latest decision from Decision Log}
73
74
  ```
74
75
 
76
+ ### Active Model Profile Line (M86 — SC(f): no silent degradation)
77
+
78
+ The **Model Profile** line MUST always name the active profile — never blank, never an implicit fallback.
79
+
80
+ - Read `.gsd-t/model-profile.json` in the project root. If the file exists and contains a valid `profile` field (`standard` | `pro` | `premium`), display it by name: `Model Profile: pro`.
81
+ - If the file is absent, display the global default by name with the `(default)` marker: `Model Profile: premium (default)`.
82
+ - If the file is present but malformed or contains an unknown profile, display: `Model Profile: premium (default, config-error)` — never silently promote to the most expensive posture.
83
+
84
+ Profiles control which workflow stages run on Fable vs. Opus/Sonnet:
85
+ - `standard` — zero Fable stages (pre-M85 posture)
86
+ - `pro` — Fable on red-team + pre-mortem + debug-cycle-2
87
+ - `premium` — all 6 M85 designated Fable stages (full posture, global default)
88
+
75
89
  ### Backlog Section
76
90
 
77
91
  If `.gsd-t/backlog.md` exists, read and parse it. Show total count and top 3 items (position, title, type). If no backlog file exists, skip the Backlog section entirely. If the backlog file exists but is empty (no entries), show `Backlog: No items`.
@@ -28,7 +28,36 @@ Per `.gsd-t/contracts/orthogonal-validation-contract.md` v1.0.0 STABLE, the thre
28
28
 
29
29
  Read `.gsd-t/progress.md` to determine the active milestone.
30
30
 
31
- ## Step 2: Invoke the verify Workflow
31
+ ## Step 2: Resolve the active model profile (M86 — invoke-time injection)
32
+
33
+ Before calling the Workflow, resolve the active model profile to build the `overrides` map:
34
+
35
+ ```bash
36
+ # Run via Bash at invoke time:
37
+ gsd-t model-profile resolve --json
38
+ # Bare form (NO --profile flag): reads .gsd-t/model-profile.json — profile AND stageOverrides
39
+ # (set-stage overrides MUST win — contract precedence; --profile is a config-blind diagnostic
40
+ # form that ZEROES stageOverrides and must never be used for invocation — Red Team M86 r3)
41
+ # Output: { "ok": true, "profile": "...", "overrides": { "stage-key": "concrete-model-id", ... } }
42
+ ```
43
+
44
+ **Resolver-failure handling (M86 — SC(f), pre-mortem c2 #2):** if the resolve call fails
45
+ (`{ok:false}`, spawn error, or the `model-profile` subcommand is not present in the installed
46
+ binary), do NOT silently proceed on the premium fallback. Either:
47
+ - HALT with `blocked-needs-human` and explain the resolver is unavailable; OR
48
+ - Proceed ONLY with a **loud, surfaced warning** that names the effective posture:
49
+
50
+ ```
51
+ ⚠ model-profile resolver unavailable — running on PREMIUM fallback literals
52
+ (configured profile unknown; stale global binary may lack model-profile subcommand)
53
+ ```
54
+
55
+ Also surface a SUCCESSFUL resolve that carries a `configError` field (the resolver returns a
56
+ named default + `configError` for malformed/hand-edited configs — Red Team M86): print the
57
+ `configError` as a visible warning naming the effective profile before proceeding. A clean-looking
58
+ run on a posture the user did not configure is the same silent-spend failure class.
59
+
60
+ ## Step 3: Invoke the verify Workflow
32
61
 
33
62
  Call the `Workflow` tool with:
34
63
 
@@ -41,6 +70,10 @@ Call the `Workflow` tool with:
41
70
  args: {
42
71
  milestone: "M{NN}",
43
72
  projectDir: ".",
73
+ // M86: inject the resolved overrides map so the workflow's ?? forms (including
74
+ // red-team) pick up the profile-tier assignments instead of premium literals.
75
+ // Pass {} when the resolver failed AND you chose the loud-warning path (not halt).
76
+ overrides: { /* ...from resolver result.overrides, or {} on failure */ },
44
77
  // Optional: skip /code-review ultra when rate-limited.
45
78
  // skipUltra: false,
46
79
  // skipUltraReason: "ultra rate-limited per error E429",
@@ -50,7 +83,7 @@ Call the `Workflow` tool with:
50
83
 
51
84
  `skipUltra: true` requires `skipUltraReason: string` per contract Rule #2. A run with `skipUltra: true` is INELIGIBLE for `VERIFIED` — best attainable verdict is `VERIFIED-WITH-WARNINGS`. Red Team and QA are non-skippable.
52
85
 
53
- ## Step 3: Interpret the result
86
+ ## Step 4: Interpret the result
54
87
 
55
88
  The Workflow returns:
56
89
 
@@ -23,7 +23,35 @@ The native Workflow `workflow()` global runs each sub-workflow inline and return
23
23
 
24
24
  Read `.gsd-t/progress.md` to determine the active milestone and the wave domain list from `.gsd-t/contracts/m{NN}-integration-points.md`.
25
25
 
26
- ## Step 2: Invoke the wave Workflow
26
+ ## Step 2: Resolve the active model profile (M86 — invoke-time injection)
27
+
28
+ Before calling the Workflow, resolve the active model profile to build the `overrides` map:
29
+
30
+ ```bash
31
+ # Run via Bash at invoke time:
32
+ gsd-t model-profile resolve --json
33
+ # Bare form (NO --profile flag): reads .gsd-t/model-profile.json — profile AND stageOverrides
34
+ # (set-stage overrides MUST win — contract precedence; --profile is a config-blind diagnostic
35
+ # form that ZEROES stageOverrides and must never be used for invocation — Red Team M86 r3)
36
+ ```
37
+
38
+ **Resolver-failure handling (M86 — pre-mortem c2 #2):** if the resolve call fails, do NOT
39
+ silently proceed on the premium fallback. Either HALT with `blocked-needs-human`, or proceed
40
+ ONLY with a loud, surfaced warning:
41
+ ```
42
+ ⚠ model-profile resolver unavailable — running on PREMIUM fallback literals
43
+ (configured profile unknown; stale global binary may lack model-profile subcommand)
44
+ ```
45
+
46
+ Also surface a SUCCESSFUL resolve that carries a `configError` field (the resolver returns a
47
+ named default + `configError` for malformed/hand-edited configs — Red Team M86): print the
48
+ `configError` as a visible warning naming the effective profile before proceeding. A clean-looking
49
+ run on a posture the user did not configure is the same silent-spend failure class.
50
+
51
+ The wave workflow forwards `overrides` to BOTH its `gsd-t-execute` and `gsd-t-verify`
52
+ sub-workflow calls, so the spend switch is active across the full cycle (execute + verify).
53
+
54
+ ## Step 3: Invoke the wave Workflow
27
55
 
28
56
  Call the `Workflow` tool with:
29
57
 
@@ -36,12 +64,15 @@ Call the `Workflow` tool with:
36
64
  args: {
37
65
  milestone: "M{NN}",
38
66
  domains: ["m{NN}-d1-...", "m{NN}-d2-...", ...], // domains for this wave only, per integration-points
39
- projectDir: "."
67
+ projectDir: ".",
68
+ // M86: inject the resolved overrides map — forwarded to execute + verify sub-workflows.
69
+ // Pass {} when the resolver failed AND you chose the loud-warning path (not halt).
70
+ overrides: { /* ...from resolver result.overrides, or {} on failure */ }
40
71
  }
41
72
  }
42
73
  ```
43
74
 
44
- ## Step 3: Interpret the result
75
+ ## Step 4: Interpret the result
45
76
 
46
77
  ```js
47
78
  {
@@ -977,6 +977,12 @@ Supporting contracts (to be written during D1):
977
977
  | REQ-M79-02 | `genSystemArchitecture` draws up to 12 real domains from scan data with rounded classDefs. All 5 diagrams rendered with shared `MERMAID_CONFIG` (dark base theme + rounded corners + node padding/spacing) applied via `mmdc -c`, plus `-b transparent`. The Database Schema diagram is suppressed by default (`SUPPRESSED_TYPES`, opt-in via `includeSchemaDiagram` option) because Drizzle schema parsing produces inaccurate results on large repos. | m79-d1 | T3 | complete |
978
978
  | REQ-M79-03 | `genSequence` uses `"validate and sanitize"` (unquoted `&` broke the Mermaid sequence parser). `scan.test.js` and `verify-gates.js` reflect the 5-diagrams-by-default contract (schema diagram excluded unless opted-in). | m79-d1 | T4 | complete |
979
979
  | REQ-M79-VERIFY | +1 regression test (m79-diagram-quality). Suite 1325/1325 pass. Hilo report regenerated: 5/5 diagrams render, 35 real services, rounded corners present, schema section gone, no placeholders. Patch bump 4.0.26 → 4.0.27. | m79-d1 | T5 | complete |
980
+ | REQ-M86-01 | Three model profiles (standard/pro/premium) as a second dimension over the M85 `STAGE_TIERS` — `PROFILE_STAGE_TIERS` + `resolveProfile` on `bin/gsd-t-model-tier-policy.cjs` (standard = zero fable; pro = red-team + pre-mortem + debug-cycle-2 fable; premium = all 6; producers HELD opus in every profile — M82). Precedence `stageOverrides[stage] ?? profile-tier ?? named global default`, blindness clamps at write AND resolve level, own-property validation (prototype-key bypass killed). | m86-d1 | T1–T5 | complete (M86, v4.5.10) |
981
+ | REQ-M86-02 | Per-project config `.gsd-t/model-profile.json` + `gsd-t model-profile` CLI (show/set/set-stage/resolve/--json). Malformed/hand-edited configs produce defined envelopes with explicit configError — never a silent clean-premium fall-through. set-stage refuses to rewrite erroring configs. | m86-d1 | T2/T5 | complete (M86) |
982
+ | REQ-M86-03 | Invoke-time injection (M69): ALL 10 workflow-invoking commands resolve the active config via the bare `resolve --json` form (stageOverrides WIN) and inject `overrides` into workflow args; designated stages read `model: overrides["<stage>"] ?? "<premium-literal>"`; wave forwards overrides to both sub-workflow calls; resolver-failure = halt or loud named-posture warning, never silent premium. NO tracked-file mutation on profile switch. | m86-d2 | T1–T10 | complete (M86) |
983
+ | REQ-M86-04 | Drift lint unwraps the `??` form: validates bracket key against the 6 INJECTABLE stages (producers excluded), fallback literal against tier set + stage policy, flat AND combined-debug forms, fail-closed; 8 mandatory negatives incl. typo'd key + wrapped-producers + drifted combined-form. Invoker fleet lint pins the injection block + failure clause + configError surfacing + bare resolve form. | m86-d3 (+d2 T10) | lint suites | complete (M86) |
984
+ | REQ-M86-05 | Active profile surfaced NAMED at every surface (SC f): `[GSD-T PROFILE]` banner token (GSD-T projects only), statusline segment, `gsd-t status` line, CLI envelopes — global default named when config absent; hook/statusline resilient to resolver absence/errors (exit 0, `[GSD-T NOW]` intact). | m86-d4 | m86-surfacing | complete (M86) |
985
+ | REQ-M86-06 | Installer self-protection: `copyBinToolsToProject` skips the GSD-T source repo (copy loop AND stray sweep) — propagation must never revert in-flight source work; CLI carries a version-skew guard (structured error on pre-M86 sibling module). | m86-d1 (verify fix) | r2 killing tests | complete (M86) |
980
986
 
981
987
  ## Updated Functional Requirements (scan findings - v4.0.27)
982
988
 
@@ -1037,5 +1043,6 @@ The deep scan identified functional deficiencies not captured in previous requir
1037
1043
  | REQ-M77 | test/m77-renderer-table-summary.test.js | 4 | passing |
1038
1044
  | REQ-M78 | test/m78-plain-english-grouping.test.js | 3 | passing |
1039
1045
  | REQ-M79 | test/m79-diagram-quality.test.js | 1 | passing |
1046
+ | REQ-M86-* | test/m86-policy-profiles.test.js, test/m86-invoker-injection.test.js, test/m86-lint-unwrap-fallback.test.js, test/m86-surfacing.test.js, test/m85-workflow-tier-policy-lint.test.js (extended) | 146 | passing |
1040
1047
 
1041
- **Total automated tests (v4.0.27)**: 1325 pass / 0 fail / 4 skip. Runner: `node --test` (zero dependencies). E2E: `playwright.config.ts` at project root, `e2e/` directory with journey, viewer, and live-journey specs.
1048
+ **Total automated tests (v4.5.10)**: 1598 pass / 0 fail / 4 skip. Runner: `node --test` (zero dependencies). E2E: `playwright.config.ts` at project root, `e2e/` directory with journey, viewer, and live-journey specs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "4.4.11",
3
+ "version": "4.6.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",
@@ -10,13 +10,90 @@
10
10
  * dated banner at the top of Claude's response. Fresh per turn so multi-day
11
11
  * sessions and date-rollovers are reflected accurately.
12
12
  *
13
- * Conditionally emits (GSD-T projects only, plain text prompts only):
14
- * - [GSD-T AUTO-ROUTE] signal so Claude routes via /gsd
13
+ * Conditionally emits (GSD-T projects only dirs with .gsd-t/):
14
+ * - [GSD-T PROFILE] {profile} active model profile (M86). Gated like the
15
+ * statusline segment: a model profile is a GSD-T concept; announcing
16
+ * "premium (default)" in unrelated directories is noise (Red Team M86 LOW).
17
+ * - [GSD-T AUTO-ROUTE] signal so Claude routes via /gsd (plain text prompts
18
+ * in projects with .gsd-t/progress.md only)
19
+ *
20
+ * NOTE: resolveActiveProfile duplicates the read/validate logic of
21
+ * readConfig() in bin/gsd-t-model-profile.cjs (the canonical copy) — kept
22
+ * inline because this hook must be zero-dep and runs from ~/.claude/scripts
23
+ * where the package module may be absent. Keep the two in sync.
15
24
  */
16
25
 
17
26
  const fs = require("fs");
18
27
  const path = require("path");
19
28
 
29
+ /**
30
+ * Resolve the active model profile from .gsd-t/model-profile.json.
31
+ *
32
+ * Resilience contract (pre-mortem r1 #7 MEDIUM — hook resilience):
33
+ * - Resolver module/binary absent → named global default with (default) marker.
34
+ * - Resolver error / malformed config → named global default or "unknown" marker.
35
+ * - NEVER throws, NEVER suppresses the [GSD-T NOW] line, NEVER kills auto-routing.
36
+ *
37
+ * @param {string} cwd — project root (may be any dir; absent .gsd-t is fine)
38
+ * @returns {{ profile: string, isDefault: boolean, configError?: string }}
39
+ */
40
+ function resolveActiveProfile(cwd) {
41
+ const GLOBAL_DEFAULT = 'premium';
42
+ const VALID_PROFILES = ['standard', 'pro', 'premium'];
43
+
44
+ try {
45
+ const configPath = path.join(cwd, '.gsd-t', 'model-profile.json');
46
+ if (!fs.existsSync(configPath)) {
47
+ return { profile: GLOBAL_DEFAULT, isDefault: true };
48
+ }
49
+
50
+ let raw;
51
+ try {
52
+ raw = fs.readFileSync(configPath, 'utf8');
53
+ } catch (_) {
54
+ return { profile: GLOBAL_DEFAULT, isDefault: true, configError: 'unreadable' };
55
+ }
56
+
57
+ let config;
58
+ try {
59
+ config = JSON.parse(raw);
60
+ } catch (_) {
61
+ return { profile: GLOBAL_DEFAULT, isDefault: true, configError: 'invalid-json' };
62
+ }
63
+
64
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
65
+ return { profile: GLOBAL_DEFAULT, isDefault: true, configError: 'wrong-type' };
66
+ }
67
+
68
+ const p = config.profile;
69
+ if (typeof p !== 'string' || !VALID_PROFILES.includes(p)) {
70
+ return { profile: GLOBAL_DEFAULT, isDefault: true, configError: 'unknown-profile' };
71
+ }
72
+
73
+ return { profile: p, isDefault: false };
74
+ } catch (_) {
75
+ // Catch-all: never crash the hook
76
+ return { profile: GLOBAL_DEFAULT, isDefault: true };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Format the profile token for the banner.
82
+ * SC(f): always named — never blank, never an unsurfaced fallback.
83
+ */
84
+ function profileToken(profileResult) {
85
+ if (!profileResult || typeof profileResult.profile !== 'string') {
86
+ return 'profile: unknown';
87
+ }
88
+ if (profileResult.configError) {
89
+ return `profile: ${profileResult.profile} (default, config-error: ${profileResult.configError})`;
90
+ }
91
+ if (profileResult.isDefault) {
92
+ return `profile: ${profileResult.profile} (default)`;
93
+ }
94
+ return `profile: ${profileResult.profile}`;
95
+ }
96
+
20
97
  function liveTimestamp(now = new Date()) {
21
98
  const day = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][now.getDay()];
22
99
  const mon = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
@@ -32,17 +109,40 @@ function liveTimestamp(now = new Date()) {
32
109
  return `${date} ${time}${tzShort ? " " + tzShort : ""}`;
33
110
  }
34
111
 
112
+ // Hook runtime — guarded so require()-ing this module for unit tests
113
+ // (test/m86-surfacing.test.js) does NOT attach stdin listeners. An unguarded
114
+ // stdin listener holds the requiring test process's event loop open forever
115
+ // (npm test runner children get a never-EOF stdin pipe → suite hang), and the
116
+ // "end" handler's process.exit(0) would kill the test process on stdin EOF.
117
+ if (require.main === module) {
35
118
  let input = "";
36
119
  process.stdin.setEncoding("utf8");
37
120
  process.stdin.on("data", (chunk) => { input += chunk; });
38
121
  process.stdin.on("end", () => {
39
122
  // Always emit live timestamp first — every turn, every project.
123
+ // [GSD-T NOW] format is date-guard-invariant: NEVER alter it.
40
124
  process.stdout.write(`[GSD-T NOW] ${liveTimestamp()}\n`);
41
125
 
126
+ // Parse stdin ONCE; both the profile token and auto-route reuse it.
127
+ let data = null;
128
+ try { data = JSON.parse(input); } catch (_) { /* tolerated — gates below skip */ }
129
+ const cwd = (data && typeof data.cwd === "string" && data.cwd) ? data.cwd : process.cwd();
130
+
131
+ // Emit the active model profile — GSD-T projects only (dirs with .gsd-t/),
132
+ // matching the statusline gate. SC(f): named, never blank, never a crash.
133
+ try {
134
+ if (fs.existsSync(path.join(cwd, ".gsd-t"))) {
135
+ const profileResult = resolveActiveProfile(cwd);
136
+ process.stdout.write(`[GSD-T PROFILE] ${profileToken(profileResult)}\n`);
137
+ }
138
+ } catch (_) {
139
+ // Belt-and-suspenders: if the gate or profileToken throws, emit the unknown marker.
140
+ process.stdout.write(`[GSD-T PROFILE] profile: unknown\n`);
141
+ }
142
+
42
143
  try {
43
- const data = JSON.parse(input);
44
144
  // Auto-route is GSD-T-project-only.
45
- const cwd = typeof data.cwd === "string" ? data.cwd : process.cwd();
145
+ if (!data) process.exit(0);
46
146
  if (!fs.existsSync(path.join(cwd, ".gsd-t", "progress.md"))) process.exit(0);
47
147
  const prompt = (typeof data.prompt === "string" ? data.prompt : "").trimStart();
48
148
  if (prompt.startsWith("/")) process.exit(0); // slash command — pass through
@@ -58,5 +158,6 @@ process.stdin.on("end", () => {
58
158
  }
59
159
  process.exit(0);
60
160
  });
161
+ }
61
162
 
62
- module.exports = { liveTimestamp };
163
+ module.exports = { liveTimestamp, resolveActiveProfile, profileToken };
@@ -12,6 +12,11 @@
12
12
  // never populated those env vars. When the state file is absent or stale
13
13
  // (>5min), the context segment is omitted.
14
14
  //
15
+ // Active model profile is read from .gsd-t/model-profile.json (M86 — SC(f):
16
+ // always named, never blank). When the file is absent, the named global default
17
+ // (premium) is shown with a (default) marker. Resilient: never crashes on missing
18
+ // or malformed config (propagation-gap class — pre-mortem c2 #5).
19
+ //
15
20
  // Zero external dependencies.
16
21
 
17
22
  const fs = require('fs');
@@ -26,6 +31,53 @@ const DIM = '\x1b[2m';
26
31
  const BOLD = '\x1b[1m';
27
32
  const ORANGE = '\x1b[38;5;208m'; // 256-color orange
28
33
 
34
+ // ─── Profile resolution (M86 — SC(f): always named, never blank) ─────────────
35
+
36
+ /**
37
+ * Resolve the active model profile from .gsd-t/model-profile.json.
38
+ *
39
+ * Resilience contract (pre-mortem c2 #5 — statusline resilience):
40
+ * - Config absent → named global default with (default) marker.
41
+ * - Malformed config / parse error → named global default or configError marker.
42
+ * - NEVER throws, NEVER crashes the statusline.
43
+ *
44
+ * @param {string|null} projectRoot — resolved project root (may be null)
45
+ * @returns {{ profile: string, isDefault: boolean, configError?: string }|null}
46
+ * null when projectRoot is null (non-GSD-T directory — omit segment).
47
+ */
48
+ // NOTE: duplicates the read/validate logic of readConfig() in bin/gsd-t-model-profile.cjs
49
+ // (the canonical copy) — kept inline because the statusline must be zero-dep and runs from
50
+ // ~/.claude/scripts where the package module may be absent. Keep the copies in sync.
51
+ function readActiveProfile(projectRoot) {
52
+ if (!projectRoot) return null;
53
+ const GLOBAL_DEFAULT = 'premium';
54
+ const VALID_PROFILES = ['standard', 'pro', 'premium'];
55
+ try {
56
+ const configPath = path.join(projectRoot, '.gsd-t', 'model-profile.json');
57
+ if (!fs.existsSync(configPath)) {
58
+ return { profile: GLOBAL_DEFAULT, isDefault: true };
59
+ }
60
+ let raw;
61
+ try { raw = fs.readFileSync(configPath, 'utf8'); } catch (_) {
62
+ return { profile: GLOBAL_DEFAULT, isDefault: true, configError: 'unreadable' };
63
+ }
64
+ let config;
65
+ try { config = JSON.parse(raw); } catch (_) {
66
+ return { profile: GLOBAL_DEFAULT, isDefault: true, configError: 'invalid-json' };
67
+ }
68
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
69
+ return { profile: GLOBAL_DEFAULT, isDefault: true, configError: 'wrong-type' };
70
+ }
71
+ const p = config.profile;
72
+ if (typeof p !== 'string' || !VALID_PROFILES.includes(p)) {
73
+ return { profile: GLOBAL_DEFAULT, isDefault: true, configError: 'unknown-profile' };
74
+ }
75
+ return { profile: p, isDefault: false };
76
+ } catch (_) {
77
+ return { profile: GLOBAL_DEFAULT, isDefault: true };
78
+ }
79
+ }
80
+
29
81
  // ─── Helpers ─────────────────────────────────────────────────────────────────
30
82
 
31
83
  function findProjectRoot() {
@@ -105,5 +157,24 @@ if (ctxPct !== null) {
105
157
  contextPart = ` │ ctx ${contextBar(ctxPct)}`;
106
158
  }
107
159
 
108
- const line = `gsd-t │ ${projectPart}${contextPart}`;
160
+ // Active model profile (M86 — SC(f): always named when in a GSD-T project)
161
+ let profilePart = '';
162
+ try {
163
+ const profileResult = readActiveProfile(root);
164
+ if (profileResult) {
165
+ let label;
166
+ if (profileResult.configError) {
167
+ label = `${profileResult.profile}* (default)`; // * signals config error
168
+ } else if (profileResult.isDefault) {
169
+ label = `${profileResult.profile} (default)`;
170
+ } else {
171
+ label = profileResult.profile;
172
+ }
173
+ profilePart = ` │ ${DIM}profile:${RESET} ${label}`;
174
+ }
175
+ } catch (_) {
176
+ // Resilience: never crash the statusline
177
+ }
178
+
179
+ const line = `gsd-t │ ${projectPart}${profilePart}${contextPart}`;
109
180
  process.stdout.write(line + '\n');