@tekyzinc/gsd-t 5.1.12 → 5.2.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.
@@ -21,6 +21,9 @@
21
21
  const fs = require("fs");
22
22
  const path = require("path");
23
23
  const crypto = require("crypto");
24
+ // M102 — the marker-block idempotent doc-writer was extracted here into the ONE
25
+ // shared module both this scaffolder and bin/gsd-t-env-registry.cjs call.
26
+ const { upsertMarkedDocBlock } = require("./gsd-t-doc-marker.cjs");
24
27
 
25
28
  const VALID_BACKENDS = ["db-table", "local-sqlite", "local-jsonl"];
26
29
 
@@ -182,24 +185,9 @@ function writeChoiceToProjectDocs(projectDir, envelope) {
182
185
  const targetPath = candidates.find((p) => fs.existsSync(p)) || candidates[0];
183
186
 
184
187
  const block = docBlock(envelope);
185
- let content = "";
186
- if (fs.existsSync(targetPath)) {
187
- content = fs.readFileSync(targetPath, "utf8");
188
- } else {
189
- const dir = path.dirname(targetPath);
190
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
191
- }
192
-
193
- if (content.includes(DOC_MARKER_START) && content.includes(DOC_MARKER_END)) {
194
- const startIdx = content.indexOf(DOC_MARKER_START);
195
- const endIdx = content.indexOf(DOC_MARKER_END) + DOC_MARKER_END.length;
196
- content = content.slice(0, startIdx) + block + content.slice(endIdx);
197
- } else {
198
- content = content.replace(/\s*$/, "") + "\n\n" + block + "\n";
199
- }
200
-
201
- fs.writeFileSync(targetPath, content, "utf8");
202
- return targetPath;
188
+ // M102 delegate to the ONE shared marker-block writer (byte-identical logic
189
+ // to the former inline replace/append; extracted so env-registry reuses it).
190
+ return upsertMarkedDocBlock(targetPath, DOC_MARKER_START, DOC_MARKER_END, block);
203
191
  }
204
192
 
205
193
  // ─── Seam envelope builder ──────────────────────────────────────────────────
@@ -307,6 +307,8 @@ function _detectDefaultTrack2(projectDir, notes) {
307
307
 
308
308
  plan.push({ id: 'logging-envelope', cmd: 'node', args: [path.join(__dirname, 'gsd-t-logging-envelope-check.cjs'), '--project', projectDir], timeoutMs: 30000 }); // M100 D3: structural trace+audit envelope gate, FAIL-CLOSED
309
309
 
310
+ plan.push({ id: 'env-registry', cmd: 'node', args: [path.join(__dirname, 'gsd-t-env-registry-check.cjs'), '--project', projectDir], timeoutMs: 30000 }); // M102 D3: no-secret-in-registry + rule-without-table gate, FAIL-CLOSED
311
+
310
312
  // secrets — gitleaks (PATH detection deferred to runtime)
311
313
  if (_hasOnPath('gitleaks')) {
312
314
  plan.push({
package/bin/gsd-t.js CHANGED
@@ -2999,6 +2999,16 @@ const PROJECT_BIN_TOOLS = [
2999
2999
  // ship to every project or the consumers can't classify. Same propagation class as
3000
3000
  // gsd-t-graph-store-resolver.cjs above. [[project_global_bin_propagation_gap]]
3001
3001
  "gsd-t-graph-availability.cjs",
3002
+ // M102 — Environment Registry. The shared marker-block doc-writer + the
3003
+ // registry helper (record/lookup/detect/add-permission/ensure-gitignore).
3004
+ // Sandboxed workflows (init/populate + provisioning execute/quick tasks) call
3005
+ // the registry via project-local Bash, and the registry `require`s the shared
3006
+ // writer at load — BOTH must ship or the registry throws / the writer is a
3007
+ // second copy. Same propagation class as the graph tools above.
3008
+ // [[project_global_bin_propagation_gap]]
3009
+ // Plus the verify-gate lint (gsd-t-verify-gate.cjs invokes it via __dirname,
3010
+ // so it must sit alongside the gate in the project's bin/).
3011
+ "gsd-t-doc-marker.cjs", "gsd-t-env-registry.cjs", "gsd-t-env-registry-check.cjs",
3002
3012
  ];
3003
3013
 
3004
3014
  // Files that older versions of this installer copied into project bin/ but
@@ -296,6 +296,12 @@ docs/
296
296
 
297
297
  These are the living documents that persist across milestones and keep institutional knowledge alive. The `infrastructure.md` is especially important — it captures the exact commands for provisioning cloud resources, setting up databases, managing secrets, and deploying, so this knowledge doesn't get lost between sessions.
298
298
 
299
+ The `infrastructure.md` template ships with a marker-delimited `## Environments` table (`<!-- gsd-t-env-registry:start -->` / `:end`) — the committed, **secret-free** connection registry (M102). It is created empty by init and populated by the two triggers:
300
+ - **Greenfield (record-at-create):** whenever this project later BUILDS/PROVISIONS/CONFIGURES an environment (a local test DB, a remote server, credentials, or hands the user setup instructions), record the map row in the SAME pass:
301
+ `node bin/gsd-t-env-registry.cjs record '{"projectDir":".","scope":"local","kind":"postgres","host":"localhost","port":"5432","name":"appdb","authMethod":"password","secretVault":"local (.env)","secretEnvVarName":"DATABASE_URL","fetchCommand":"read $DATABASE_URL from .env","connectCommand":"psql \"$DATABASE_URL\""}'`
302
+ Records only the MAP + vault pointer + env-var NAME + fetch/connect commands — **never a secret VALUE** (a literal password/token in a command is REJECTED). `scope=prod` defaults `read-only=YES`.
303
+ - Also run `node bin/gsd-t-env-registry.cjs ensure-gitignore .` so `.env` is gitignored (auto-add + report; secret-leak precheck).
304
+
299
305
  ## Step 8: Ensure README.md Exists
300
306
 
301
307
  If no `README.md` exists, create one with:
@@ -29,6 +29,13 @@ Scan this codebase and populate the GSD-T documentation. Analyze the actual code
29
29
  - List Credentials and Secrets from .env.example (local) and any secret manager configs
30
30
  - Document Deployment from CI/CD configs, Dockerfiles, cloud configs
31
31
  - Document Logging and Monitoring from any logging setup or dashboard configs
32
+ - **Back-fill the `## Environments` registry (M102 — brownfield capture).** For an existing project whose non-local environments are undocumented, back-fill the map-only Environments table WITHOUT ever reading or writing a secret VALUE:
33
+ 1. `node bin/gsd-t-env-registry.cjs detect .` → proposes the vault + fetch command from `.env.example` var NAMES, connection-string SHAPES, and `vercel.json`/`.vercel` (Vercel) · `cloudbuild.yaml`/`.gcloudignore` (Google Secret Manager) · neon dep (Neon) · plain `.env` (local). Map only — never a value.
34
+ 2. Confirm/fill gaps with the human, then `node bin/gsd-t-env-registry.cjs record '<json>'` per environment (upsert by `(scope, kind)` — a re-provision replaces the stale row).
35
+ 3. `node bin/gsd-t-env-registry.cjs ensure-gitignore .` — auto-add `.env` to `.gitignore` if missing + report.
36
+ - **No-Fallback-Ever:** a genuinely undocumented environment reached for the first time HALTs → detect → ask → record → `add-permission` → proceed. NEVER guess a connection string, NEVER grep transcripts to rediscover.
37
+ - **Leak remediation (secret found in a URL/command):** before recording, run `node bin/gsd-t-env-registry.cjs propose-remediation . '<command>'`. If it reports `needed:true`, STOP and tell the user *what* leaked (`detectedSecret`), then ASK **rotate or move** (`choices.rotate` / `choices.move`) — each time, do not assume. On their answer: store the (rotated or existing) secret into the detected `vault` (directly where possible — e.g. `vercel env add`, write to `.env`; else instruct the user to create/rotate it), then record the row with the `rewrittenCommand` ($VAR form). NEVER commit the literal secret.
38
+ - **Local-literal switch (per project):** by default every row uses `$VAR` (strict). A project may opt in via `.gsd-t/env-registry-config.json` → `{"allowLocalLiteral": true}` to allow a LITERAL secret in `scope=local` rows only (testing convenience). `staging`/`prod` are ALWAYS strict. A relaxed local write returns a one-time "committed file → git history" warning — surface it.
32
39
 
33
40
  ## Graph Structural Slice — who-imports + cluster (M94-D10)
34
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "5.1.12",
3
+ "version": "5.2.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",
@@ -32,6 +32,7 @@ const path = require("path");
32
32
  const READER_CONTRACT = [
33
33
  "[GSD-T READER CONTRACT] Before sending ANY reply, assume your first draft is too wordy and rewrite it tight. Rules:",
34
34
  "• Answer FIRST. No preamble, no restating the question, no narrating what you're about to do (\"let me check…\"). Do the work silently, then give the result.",
35
+ "• NO PREAMBLES — start with the answer, never a framing phrase. BANNED openers (and anything like them): \"One thing I owe you honestly:\", \"To be honest\", \"Here's the thing\", \"The honest truth is\", \"I'll be straight with you\", \"Let me level with you\", \"Full transparency:\", \"Real talk\", \"What's worth noting here\", \"The key insight is\", \"Here's what's happening\". Delete the opener and lead with the actual point — if a sentence only announces that a point is coming, cut it.",
35
36
  "• Exception — when you're about to CHANGE code/files: state intent in one line first, so the user can stop a wrong direction.",
36
37
  "• Gloss every code/jargon term in plain words on first use. No bare IDs or acronyms the reader must decode.",
37
38
  "• Bullets/tables over paragraphs. Cut hedging and meta-commentary. Expand only if asked.",
@@ -96,9 +96,22 @@ NEED TO UNDERSTAND SOMETHING?
96
96
  ├── Is it about what to build? → Read docs/requirements.md
97
97
  ├── Is it about how to deploy/operate? → Read docs/infrastructure.md
98
98
  ├── Is it about domain interfaces? → Read .gsd-t/contracts/
99
+ ├── Is it about reaching a non-local environment (DB/server URL, creds, perms)? → Read the `## Environments` registry in docs/infrastructure.md
99
100
  └── Not documented? → Research, then DOCUMENT IT
100
101
  ```
101
102
 
103
+ ## Environment Access — read-first, HALT-and-document (M102)
104
+
105
+ **Before reaching ANY non-local environment (a staging/prod DB, a remote server, an external service), READ the `## Environments` registry in `docs/infrastructure.md` FIRST.** It is the committed, **secret-free** map of every environment — host, port, db, auth method, which vault holds the secret, the env-var NAME, and the connect command (secrets referenced as `$VAR`, never as a literal value).
106
+
107
+ - **On a HIT** — use the recorded connect command; the secret is pulled from its vault at runtime via the env-var NAME.
108
+ - **On a MISS (no row) — HALT and document. NEVER guess a connection string, NEVER grep transcripts to rediscover it** (No-Fallback-Ever). Then: `detect` the env → ask the human to confirm/fill → `record` the map row → `add-permission` (broad-glob) → proceed. The registry self-heals staleness because a re-provision upserts by `(scope, kind)`.
109
+ - **Record-at-create (greenfield):** whenever GSD-T BUILDS/PROVISIONS an environment, record its map row in the SAME pass (it has the URL + creds right then).
110
+ - **Never write a secret VALUE into the registry** — only the vault name + env-var NAME + a `$VAR`-referencing command. A literal secret is rejected by `recordEnvironment` and by the `gsd-t-verify` env-registry gate. If a secret is found in a URL/command, the capture flow OFFERS to move it into the vault (rotate-or-move, user's choice) and replace it with a `$VAR`.
111
+ - **Local-literal switch:** a project may opt in via `.gsd-t/env-registry-config.json` `{"allowLocalLiteral": true}` to allow a literal secret in `scope=local` rows only; `staging`/`prod` stay strict.
112
+
113
+ Details: `.gsd-t/contracts/env-registry-contract.md`.
114
+
102
115
 
103
116
 
104
117
  # Versioning
@@ -484,7 +497,7 @@ See memory pointer: `feedback_auto_research_external_gaps`.
484
497
 
485
498
  **The HALT (the teeth):** a deliverable whose simply-stated lead cannot be written cleanly is NOT done — stop, surface it, re-think the muddled part. Do not ship the verbose version with an apology; the verbose version is evidence the design has a gap.
486
499
 
487
- **§Applies to conversational narration too, not just formal deliverables.** The doctrine governs REPLIES and mid-work talk-to-the-user, not only architectures/plans/findings. Two extra rules there: **NO PREAMBLE** (no throat-clearing, no "let me…", no narrating-the-explanation like "it matters that I say why…" — give the point, not a description of the point), and **load-bearing point FIRST** (never buried after justification clauses). No clever phrase that obscures where a plain one is clearer.
500
+ **§Applies to conversational narration too, not just formal deliverables.** The doctrine governs REPLIES and mid-work talk-to-the-user, not only architectures/plans/findings. Two extra rules there: **NO PREAMBLES** (no throat-clearing, no "let me…", no narrating-the-explanation like "it matters that I say why…" — give the point, not a description of the point; BANNED openers and anything like them: "One thing I owe you honestly:", "To be honest", "Here's the thing", "The honest truth is", "I'll be straight with you", "Full transparency:", "What's worth noting", "The key insight is" — delete the opener, lead with the point), and **load-bearing point FIRST** (never buried after justification clauses). No clever phrase that obscures where a plain one is clearer.
488
501
 
489
502
  **§Enforcement (four layers):** (1) this doctrine = the definition; (2) the **Architect's Oversight Write/Edit trigger** carries a Simply-Stated line; (3) the **plan/milestone Six-Stage Pass + `/gsd-t-architect` gain a Simply-Stated gate** — the pass does not complete until its finding/plan has a clean simply-stated lead, and the validation protocols flag a deliverable that lacks one (a REQUIRED ARTIFACT like the pseudocode file, not advice); (4) the **every-turn Reader Contract** (injected by the UserPromptSubmit hook `scripts/gsd-t-auto-route.js`) carries the Simply-Stated rule + a before/after example, so it governs conversational output each reply — the channel that reaches mid-work narration, which layers 2-3 do not.
490
503
 
@@ -68,6 +68,46 @@ cp .env.example .env
68
68
  {command}
69
69
  ```
70
70
 
71
+ <!-- gsd-t-env-registry:start -->
72
+ ## Environments
73
+
74
+ > **Map, not secrets.** This table records only WHERE an environment lives and
75
+ > HOW to reach it — host/port/name/auth-method/which-vault-holds-the-secret/the
76
+ > env-var NAME/the fetch + connect commands. It NEVER stores a secret VALUE.
77
+ > The value lives in the vault (`.env` / Vercel / Neon / Google Secret Manager)
78
+ > and is pulled at runtime via the env-var NAME. A missing row → HALT and
79
+ > document (detect → ask → record → proceed); never guess a connection string,
80
+ > never grep transcripts to rediscover.
81
+
82
+ | id | scope | kind | host | port | db/name | auth method | secret vault | secret env-var NAME | fetch command | connect command | access gotchas | read-only default | recorded |
83
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
84
+ <!-- gsd-t-env-registry:end -->
85
+
86
+ <!--
87
+ Column notes:
88
+ scope = local | staging | prod
89
+ kind = postgres | mysql | redis | http-api | ssh-host | ...
90
+ secret vault = local (.env) | Vercel | Neon | Google Secret Manager | ...
91
+ secret env-var NAME = e.g. DATABASE_URL_PROD — the NAME only, NEVER the value
92
+ host = POSITIVE shape: localhost | IPv4 | dotted DNS hostname |
93
+ short lowercase service name (letters+hyphen, no digits, e.g. db, postgres)
94
+ db/name = short lowercase snake identifier (<=16, e.g. binvoice_prod, analytics)
95
+ auth method = ENUMERATED — password | iam | oauth | oauth2 | service-account |
96
+ ssh-key | api-key | none | scram-sha-256 | md5 | trust | token | key | ...
97
+ fetch command = how to pull the secret from its vault (e.g. `vercel env pull`)
98
+ POSITIVE allowlist: a token is accepted ONLY if it IS a $VAR/${VAR} ref,
99
+ a flag, a hostname/IP, a .env dotfile, or a curated CLI/db word.
100
+ Any OTHER bare literal is REJECTED (move it to an env var, reference as $VAR).
101
+ connect command = references the env-var by name: `psql "$DATABASE_URL_PROD"` (same allowlist)
102
+ access gotchas = ENUMERATED — vpn | ip-allowlist | ssh-tunnel | bastion | none,
103
+ optionally `via <hostname>` (e.g. `ssh-tunnel via bastion.example.com`).
104
+ Free prose is FORBIDDEN (nowhere for a secret to hide).
105
+ read-only default = YES for scope=prod unless a human explicitly recorded write-ok
106
+ There is deliberately NO secret-value column — a row is structurally incapable
107
+ of holding a secret. Populated by `gsd-t-env-registry` (record-at-create +
108
+ capture-on-first-need).
109
+ -->
110
+
71
111
  ## Credentials & Secrets
72
112
 
73
113
  ### Local (.env)