@tekyzinc/gsd-t 5.1.13 → 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.
- package/CHANGELOG.md +22 -0
- package/README.md +1 -1
- package/bin/gsd-t-doc-marker.cjs +65 -0
- package/bin/gsd-t-env-registry-check.cjs +449 -0
- package/bin/gsd-t-env-registry.cjs +1384 -0
- package/bin/gsd-t-logging-scaffolder.cjs +6 -18
- package/bin/gsd-t-verify-gate.cjs +2 -0
- package/bin/gsd-t.js +10 -0
- package/commands/gsd-t-init.md +6 -0
- package/commands/gsd-t-populate.md +7 -0
- package/package.json +1 -1
- package/templates/CLAUDE-global.md +13 -0
- package/templates/infrastructure.md +40 -0
|
@@ -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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
package/commands/gsd-t-init.md
CHANGED
|
@@ -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.
|
|
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",
|
|
@@ -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
|
|
@@ -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)
|