@tekyzinc/gsd-t 4.0.26 → 4.0.28
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 +29 -0
- package/README.md +91 -142
- package/bin/scan-data-collector.js +88 -1
- package/bin/scan-diagrams-generators.js +15 -7
- package/bin/scan-diagrams.js +9 -0
- package/bin/scan-renderer.js +32 -1
- package/docs/architecture.md +231 -21
- package/docs/infrastructure.md +461 -245
- package/docs/requirements.md +226 -0
- package/docs/workflows.md +353 -4
- package/package.json +1 -1
- package/templates/workflows/gsd-t-scan.workflow.js +49 -14
package/docs/workflows.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Workflows — GSD-T Framework (@tekyzinc/gsd-t)
|
|
2
2
|
|
|
3
|
-
## Last Updated: 2026-
|
|
3
|
+
## Last Updated: 2026-06-04 (Scan #10, Post-M61/M66/M71/M75)
|
|
4
4
|
|
|
5
5
|
## User Workflows
|
|
6
6
|
|
|
@@ -33,15 +33,17 @@
|
|
|
33
33
|
### Full Wave Cycle
|
|
34
34
|
1. User defines milestone via `/gsd-t-milestone`
|
|
35
35
|
2. **Partition**: Decompose into domains + contracts (file ownership, interfaces)
|
|
36
|
-
3. **Discuss**: Explore design decisions (always pauses, even Level 3)
|
|
36
|
+
3. **Discuss**: Explore design decisions (always pauses, even Level 3) - SKIPPABLE via structured 3-condition check: single domain, no open questions in Decision Log, all cross-domain contracts exist (M7)
|
|
37
37
|
4. **Plan**: Create atomic task lists per domain with dependencies
|
|
38
38
|
5. **Impact**: Analyze downstream effects (PROCEED / CAUTION / BLOCK verdicts)
|
|
39
|
-
6. **Execute**: Implement tasks
|
|
39
|
+
6. **Execute**: Implement tasks via Workflow parallel domain workers + integrate barrier
|
|
40
40
|
7. **Test-Sync**: Align tests with code changes, verify coverage
|
|
41
41
|
8. **Integrate**: Wire domains at boundaries, verify contracts honored
|
|
42
|
-
9. **Verify**:
|
|
42
|
+
9. **Verify**: Orthogonal triad (code-review ultra + Red Team + QA) + CI-parity + test-data purge
|
|
43
43
|
10. **Complete**: Archive to `.gsd-t/milestones/`, bump version, git tag
|
|
44
44
|
|
|
45
|
+
Note: `/gsd-t-wave` runs ONLY execute + verify as sub-workflows. Partition, plan, discuss, and impact must be invoked separately before running wave.
|
|
46
|
+
|
|
45
47
|
Additional wave behaviors (M10-M12):
|
|
46
48
|
- **M10**: QA removed from partition/plan; execute/integrate spawn QA as Task subagent; test-sync/verify/complete run QA inline
|
|
47
49
|
- **M11**: Per-task commits (`feat({domain}/task-{N})`) enforced; between-phase spot-check (status + git + filesystem); Deviation Rules in execute (4-rule protocol, 3-attempt limit)
|
|
@@ -224,3 +226,350 @@ Every commit must pass applicable checks:
|
|
|
224
226
|
- **Trigger**: `npm publish` (via `prepublishOnly`)
|
|
225
227
|
- **Gate**: `npm test` runs 205 tests (8 files including verify-gates.js)
|
|
226
228
|
- **Verify-gates checks**: file size compliance (all bin/*.js ≤ 200 lines), no CDN references in scan-report.html, has DOCTYPE, has 6 diagram sections, export format validation
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## M61+ Native Workflow Journeys
|
|
233
|
+
|
|
234
|
+
### Execute Workflow (gsd-t-execute.workflow.js)
|
|
235
|
+
|
|
236
|
+
The execute phase is the primary code-change delivery mechanism. Entry is via `/gsd-t-execute` or as a sub-workflow of `/gsd-t-wave`.
|
|
237
|
+
|
|
238
|
+
**Args**: `{ milestone, domains: [...], projectDir? }`
|
|
239
|
+
|
|
240
|
+
**Phases and flow**:
|
|
241
|
+
1. **Preflight** - `lib.runPreflight({ projectDir })` runs `gsd-t preflight --json`. Hard-fail on any severity:"error" check (wrong branch, occupied required port). Non-error checks record but do not block.
|
|
242
|
+
2. **Disjointness** - `lib.proveFileDisjointness({ projectDir, domains })` calls `gsd-t parallel --dry-run --domain D1 --domain D2...` scoped to the requested domain set. If any two domains claim the same file, returns `ok=false` and the workflow halts before spawning any workers.
|
|
243
|
+
3. **Domains (parallel)** - For each domain in `args.domains`, an `agent("sonnet")` worker is spawned concurrently. Each worker:
|
|
244
|
+
- Generates a per-domain brief via `lib.generateBrief({ kind:"execute", milestone, domain })`
|
|
245
|
+
- Reads its `scope.md` (owned files) and `tasks.md` (work to do) via `lib.readScope()` / `lib.readDomainTasks()`
|
|
246
|
+
- Executes every task listed under `## Tasks`, touching only files in its owned scope
|
|
247
|
+
- Makes git commits per completed task or task group
|
|
248
|
+
- Updates affected docs (progress.md Decision Log, architecture.md, contracts/, requirements.md) in the same commits
|
|
249
|
+
- Returns a schema-validated `DOMAIN_RESULT_SCHEMA` object: `{ domain, status, filesTouched, tasksDone, tasksBlocked, notes }`
|
|
250
|
+
- If any domain returns `status:"failed"`, the workflow halts before integrate
|
|
251
|
+
4. **Integrate** - A single `agent("sonnet")` receives all domain results and performs cross-domain wire-up: resolves shared-file edits sequenced at integrate, updates cross-domain contracts, runs interleaved-touch resolution. Returns `{ status:"green"|"warnings"|"failed", crossDomainEdits, notes }`.
|
|
252
|
+
5. **Verify-Gate** - `lib.runVerifyGate({ projectDir })` runs `gsd-t verify-gate --json`. Runs Track 1 (preflight envelope) + Track 2 (parallel-CLI: tsc, biome/ruff, npm test, knip, gitleaks, scc/lizard) deterministically.
|
|
253
|
+
|
|
254
|
+
**Exit**: Returns `{ status:"complete"|"verify-failed"|"failed", milestone, domainResults, integrate, verifyGate }`.
|
|
255
|
+
|
|
256
|
+
**Failure paths**:
|
|
257
|
+
- Missing `milestone` arg: returns `{ status:"failed", reason:"missing-milestone" }`
|
|
258
|
+
- Empty `domains` arg: returns `{ status:"failed", reason:"no-domains" }`
|
|
259
|
+
- Preflight error: returns `{ status:"failed", reason:"preflight-failed", preflight: envelope }`
|
|
260
|
+
- Non-disjoint domains: returns `{ status:"failed", reason:"non-disjoint" }`
|
|
261
|
+
- Any domain worker throws/fails: integrate is skipped, returns `{ status:"failed", reason:"domain-failed" }`
|
|
262
|
+
- Integrate agent fails: verify-gate is skipped, returns `{ status:"failed", reason:"integrate-failed" }`
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### Verify Workflow (gsd-t-verify.workflow.js)
|
|
267
|
+
|
|
268
|
+
The verify phase enforces quality gates before milestone promotion. Entry is via `/gsd-t-verify` or as a sub-workflow of `/gsd-t-wave`.
|
|
269
|
+
|
|
270
|
+
**Args**: `{ milestone, projectDir?, skipUltra?: false, skipUltraReason?: string }`
|
|
271
|
+
|
|
272
|
+
Note: `skipUltra=true` requires `skipUltraReason` (per orthogonal-validation-contract.md Rule #2) and makes the workflow INELIGIBLE for a `VERIFIED` verdict (best case is `VERIFIED-WITH-WARNINGS`).
|
|
273
|
+
|
|
274
|
+
**Phases and flow**:
|
|
275
|
+
1. **Preflight** - `lib.runPreflight()` + `lib.generateBrief({ kind:"verify", milestone })`. Hard-fails on preflight errors.
|
|
276
|
+
2. **Verify-Gate** - `lib.runVerifyGate({ projectDir })` runs Track 1 + Track 2. If not ok, halts immediately with `{ status:"verify-gate-failed", overallVerdict:"VERIFY-FAILED" }`. Track 2 currently runs via `_runJsonCli` (a `spawnSync` helper that invokes the project-local or global `gsd-t` CLI). Known migration debt: this uses `require()` / `child_process` which are sandbox-banned in native Workflows (see findings).
|
|
277
|
+
3. **CI-Parity (M57 - FAIL-blocking)** - Runs `gsd-t build-coverage --json` then `gsd-t ci-parity --json`. Both must exit 0 or the workflow halts with `{ status:"ci-parity-failed", overallVerdict:"VERIFY-FAILED" }`. Origin: TimeTracking v1.10.12 shipped VERIFIED with a new dir absent from the Dockerfile COPY.
|
|
278
|
+
4. **Test-Data Purge (M58 - FAIL-blocking)** - Runs `gsd-t test-data --purge --run {verifyRunId} --json`. Purges all ledger rows for this run via registered adapters. If any adapter throws, halts with `{ status:"test-data-purge-failed", overallVerdict:"VERIFY-FAILED" }`. Origin: GSD-T-Board v0.1.10 shipped with 2442 E2E_TEST_* orphans in production data.
|
|
279
|
+
5. **Orthogonal Triad (parallel)** - Three independent validators run concurrently (or two if `skipUltra=true`):
|
|
280
|
+
- **code-review ultra** (opus): cooperative correctness + cleanup pass. Severity: `important`, `nit`, `pre-existing`. Skippable via `skipUltra=true` + `skipUltraReason`.
|
|
281
|
+
- **Red Team** (opus): adversarial / security / boundaries. Non-skippable. Verdict: `FAIL` (any CRITICAL or HIGH bug) or `GRUDGING-PASS` (nothing found after exhaustive search). Uses `templates/prompts/red-team-subagent.md` protocol.
|
|
282
|
+
- **QA** (sonnet): test execution + shallow-test detection + contract compliance. Non-skippable. Uses `templates/prompts/qa-subagent.md` protocol.
|
|
283
|
+
6. **Synthesis** (opus): Merges triad results WITHOUT collapsing categories. Computes `overallVerdict`:
|
|
284
|
+
- `VERIFIED`: Red Team GRUDGING-PASS + QA suite all-pass + no shallow tests + contracts compliant + code-review ultra ran with no "important" findings
|
|
285
|
+
- `VERIFIED-WITH-WARNINGS`: Red Team GRUDGING-PASS, QA green, contracts compliant, but code-review ultra has "important" findings OR skipUltra=true OR 1 non-core shallow test
|
|
286
|
+
- `VERIFY-FAILED`: Red Team FAIL, QA fail > 0, contract violations, or >= 2 shallow tests in core paths
|
|
287
|
+
- Note: there is no programmatic invariant enforcing Red Team FAIL -> VERIFY-FAILED; the synthesis agent is the sole arbiter (see findings).
|
|
288
|
+
|
|
289
|
+
**Exit**: Returns `{ status:"complete"|"failed", overallVerdict, verifyGate, buildCoverage, ciParity, testDataPurge, triad, verdict }`.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
### Wave Workflow (gsd-t-wave.workflow.js)
|
|
294
|
+
|
|
295
|
+
Wave composes execute and verify as two sequential sub-workflows. It does NOT run partition, plan, discuss, or impact - those must be run before invoking wave.
|
|
296
|
+
|
|
297
|
+
**Args**: `{ milestone, domains: [...], projectDir?, autoIntegrate? }`
|
|
298
|
+
|
|
299
|
+
**Flow**:
|
|
300
|
+
1. **Execute phase** - Calls `workflow("gsd-t-execute", { milestone, domains, projectDir })`. If execute does not return `status:"complete"`, wave halts and returns `{ status: execResult.status, stage:"execute", execResult }`.
|
|
301
|
+
2. **Verify phase** - Calls `workflow("gsd-t-verify", { milestone, projectDir })`. Returns the combined result.
|
|
302
|
+
|
|
303
|
+
**Exit**: Returns `{ status:"complete"|"verify-failed", milestone, execResult, verifyResult }`.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
### Quick Workflow (gsd-t-quick.workflow.js)
|
|
308
|
+
|
|
309
|
+
Fast single-task execution with contract awareness. Entry via `/gsd-t-quick`.
|
|
310
|
+
|
|
311
|
+
**Args**: `{ task, projectDir?, model? }`
|
|
312
|
+
|
|
313
|
+
**Flow**:
|
|
314
|
+
1. **Preflight** - `lib.runPreflight()` + `lib.generateBrief({ kind:"execute" })`.
|
|
315
|
+
2. **Execute** - Single `agent()` (default sonnet) receives the task description, brief path, and CLAUDE.md constraints. Returns `{ status, filesEdited, summary }`.
|
|
316
|
+
3. **Verify** - `lib.runVerifyGate()` runs Track 1 + Track 2.
|
|
317
|
+
|
|
318
|
+
If the task agent returns `status:"failed"` or `status:"blocked"`, verify is skipped.
|
|
319
|
+
|
|
320
|
+
Known issue: commit prefix is hardcoded as `"m61(quick)"` regardless of the active milestone (see findings).
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
### Debug Workflow (gsd-t-debug.workflow.js)
|
|
325
|
+
|
|
326
|
+
Diagnose and fix a failing test or runtime error. Up to 2 fix cycles per CLAUDE.md Prime Rule. Entry via `/gsd-t-debug`.
|
|
327
|
+
|
|
328
|
+
**Args**: `{ symptom, projectDir? }`
|
|
329
|
+
|
|
330
|
+
**Flow**:
|
|
331
|
+
1. **Preflight** - `lib.runPreflight()` + `lib.generateBrief({ kind:"execute" })`.
|
|
332
|
+
2. **Cycle 1** (opus) - Agent reads relevant code, forms a hypothesis, applies a fix, runs affected tests, reports. Returns `{ resolved, rootCause, filesEdited, testRunResult, nextStepsIfNotResolved }`.
|
|
333
|
+
- If `resolved=true`, exits immediately with `{ status:"complete", cyclesUsed:1 }`.
|
|
334
|
+
3. **Cycle 2** (opus) - If cycle 1 did not resolve, cycle 2 agent receives the prior cycle's root cause hypothesis (to avoid repeating the same wrong fix). Returns same schema.
|
|
335
|
+
- If `resolved=true`, exits with `{ status:"complete", cyclesUsed:2 }`.
|
|
336
|
+
4. If both cycles fail: returns `{ status:"needs-human", cyclesUsed:2, nextSteps }`. This signals the caller to escalate.
|
|
337
|
+
|
|
338
|
+
Known issue: commit prefix is hardcoded as `"m61(debug-cycle${cycle})"` regardless of the active milestone.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
### Integrate Workflow (gsd-t-integrate.workflow.js)
|
|
343
|
+
|
|
344
|
+
Cross-domain integration after parallel workers complete. Can be invoked standalone between execute and verify when execute's built-in integrate barrier is insufficient. Entry via `/gsd-t-integrate`.
|
|
345
|
+
|
|
346
|
+
**Args**: `{ milestone, domains: [...], projectDir? }`
|
|
347
|
+
|
|
348
|
+
**Flow**:
|
|
349
|
+
1. **Preflight** - `lib.runPreflight()` + `lib.generateBrief({ kind:"execute", milestone })`.
|
|
350
|
+
2. **Integrate** (sonnet) - Agent reads `.gsd-t/contracts/{milestone}-integration-points.md` (if present), resolves cross-domain shared-file edits per the "Cross-Domain File Contention Matrix", updates cross-domain contracts, makes commits. Returns `{ status:"green"|"warnings"|"failed", crossDomainEdits, notes }`.
|
|
351
|
+
3. **Verify-Gate** - `lib.runVerifyGate()` runs quick sanity check.
|
|
352
|
+
|
|
353
|
+
Known issue: commit prefix is hardcoded as `"m61(integrate)"` regardless of the active milestone.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### Phase Workflow (gsd-t-phase.workflow.js)
|
|
358
|
+
|
|
359
|
+
Generic upper-stage phase runner covering partition, plan, discuss, impact, milestone, prd, design-decompose, doc-ripple. Entry via the corresponding slash command.
|
|
360
|
+
|
|
361
|
+
**Args**: `{ phase, milestone?, projectDir?, userInput? }`
|
|
362
|
+
|
|
363
|
+
Valid phases: `partition | plan | discuss | impact | milestone | prd | design-decompose | doc-ripple`
|
|
364
|
+
|
|
365
|
+
**Flow**:
|
|
366
|
+
1. **Preflight** - `lib.runPreflight()` + `lib.generateBrief({ kind: phaseName, milestone })`.
|
|
367
|
+
2. **Phase** (opus) - Single agent receives a phase-specific objective prompt:
|
|
368
|
+
- `partition`: Decompose milestone into 2-5 independent domains; write `.gsd-t/domains/{domain}/{scope,constraints,tasks}.md`; cross-domain contracts in `.gsd-t/contracts/`.
|
|
369
|
+
- `plan`: Write atomic `tasks.md` entries per domain with files, contract refs, dependencies, acceptance criteria; update `.gsd-t/contracts/integration-points.md`.
|
|
370
|
+
- `discuss`: Multi-perspective design exploration; settle locked decisions into `.gsd-t/CONTEXT.md`; do NOT implement.
|
|
371
|
+
- `impact`: Analyze downstream effects; identify breaking changes, affected consumers, migration paths.
|
|
372
|
+
- `milestone`: Define a new milestone - origin, goal, success criteria, falsifiable acceptance; append to `.gsd-t/progress.md`.
|
|
373
|
+
- `prd`: Generate `docs/prd.md` with functional + non-functional requirements traceable to acceptance criteria.
|
|
374
|
+
- `design-decompose`: Decompose a design reference (Figma URL / images) into hierarchical contracts: elements -> widgets -> pages at `.gsd-t/contracts/design/`.
|
|
375
|
+
- `doc-ripple`: Identify and update all docs affected by recent code changes per the Document Ripple Completion Gate. No code edits.
|
|
376
|
+
|
|
377
|
+
Known issue: commit prefix is hardcoded as `"m61({phaseName})"` regardless of the active milestone.
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
### Deep Scan Workflow (gsd-t-scan.workflow.js)
|
|
382
|
+
|
|
383
|
+
Volume-driven codebase analysis that fans out per-slice finder + verifier agents, then synthesizes findings into a tech-debt register + living documents. This is the ONLY workflow fully migrated to the M71 runtime-native sandbox (no `require()`). Entry via `/gsd-t-scan`.
|
|
384
|
+
|
|
385
|
+
**Args**: `{ projectDir?, scanNumber?, verify?: "single"|"none", maxSlicesHint? }`
|
|
386
|
+
|
|
387
|
+
Note: `args` is a JSON STRING in the native Workflow sandbox - the workflow parses it via `JSON.parse(args)` before use.
|
|
388
|
+
|
|
389
|
+
**Phases and flow**:
|
|
390
|
+
|
|
391
|
+
1. **Preflight** (haiku) - Bash agent checks current git branch, whether `.gsd-t/techdebt.md` exists (`priorRegisterExists`), and if so, the highest `TD-NNN` number in it (`priorMaxTd`). TD numbering for this scan starts at `priorMaxTd + 1`. Returns `{ ok, branch, priorRegisterExists, priorMaxTd }`.
|
|
392
|
+
|
|
393
|
+
2. **Probe** (sonnet) - Volume probe agent measures the codebase (file counts, LOC, routes, tables, components, feature domains via Bash/Grep/Read), then decomposes it into COHESIVE SLICES. A slice is one logical sub-domain - smaller than a domain, larger than a single file. The probe should produce as many slices as the codebase has cohesive agent-sized responsibilities.
|
|
394
|
+
- After the probe returns, a volume-derived backstop cap is computed: `computeSliceCap(totals)` = `f(sqrt(files), domains, routes, tables, components, sqrt(loc))`, capped at 3..50.
|
|
395
|
+
- If the probe over-slices (returns more slices than the cap), excess slices are dropped and a warning is logged.
|
|
396
|
+
|
|
397
|
+
3. **Deep Scan** (pipeline per slice) - All slices run concurrently via a shared 10-permit adaptive semaphore (`MAX_CONCURRENT=10`, floor `MIN_CONCURRENT=4`):
|
|
398
|
+
- Per slice, a **finder** (sonnet) reads every file under the slice's owned paths and reports CRITICAL/HIGH/MEDIUM/LOW findings. Up to 2 retry attempts per slice; a null result after both attempts flags `failed:true`.
|
|
399
|
+
- For each finding returned, a **verifier** (sonnet) opens the referenced files and confirms whether the defect genuinely exists. False positives are discarded; corrected severities are applied.
|
|
400
|
+
- A slice that fails both finder attempts is flagged as a coverage gap (PARTIAL coverage) and included in `failedSlices[]`.
|
|
401
|
+
- Rate limits: if an agent call throws a rate-limit error, the semaphore ceiling lowers by 1 (floor MIN_CONCURRENT) and the call is retried up to 4 times with 2-4-6s backoff. After 8 consecutive clean completions, the ceiling rises by 1.
|
|
402
|
+
|
|
403
|
+
4. **Synthesis**:
|
|
404
|
+
- **Dedup** (opus, bounded): A compact title+location list of all findings (not the full objects) is sent to a dedup agent which returns merge groups (arrays of indices that represent the same underlying issue). On agent failure, synthesis proceeds with no dedup.
|
|
405
|
+
- **Deterministic merge + sort**: The orchestrator body (no agent) merges duplicate groups, sorts findings by severity (CRITICAL first), and assigns sequential TD-NNN numbers starting at `tdStart`.
|
|
406
|
+
- **Archive** (haiku): If a prior register exists, it is renamed from `techdebt.md` to `techdebt_{today}.md` via `git mv` (or `mv` if not a git repo).
|
|
407
|
+
- **Write register** (haiku): The formatted register markdown is split into chunks of <= 30KB each. Chunk 0 creates the file (Write tool); subsequent chunks append via bash heredoc. The chunked write approach prevents the M75 stall that occurred when a single agent was asked to type a 466KB file.
|
|
408
|
+
|
|
409
|
+
5. **Document** (parallel, sonnet) - Fan-out to 10 document agents, each writing one file:
|
|
410
|
+
- `.gsd-t/scan/architecture.md` - stack, structure, components, data flow
|
|
411
|
+
- `.gsd-t/scan/security.md` - security findings as `### SEC-H<n>:` / `### SEC-M<n>:` sections
|
|
412
|
+
- `.gsd-t/scan/quality.md` - quality/dead-code/test-gap findings as `### DC-<n>:` / `### TCG-<n>:` sections
|
|
413
|
+
- `.gsd-t/scan/business-rules.md` - embedded business logic (validation, auth, state machines, pricing)
|
|
414
|
+
- `.gsd-t/scan/contract-drift.md` - compare `.gsd-t/contracts/` to implementation
|
|
415
|
+
- `docs/architecture.md` - MERGE (Edit tool): system overview, components, data flow, design decisions
|
|
416
|
+
- `docs/workflows.md` - MERGE (Edit tool): user journeys per slice, technical/integration workflows
|
|
417
|
+
- `docs/infrastructure.md` - MERGE (Edit tool): commands, dev setup, DB commands, deployment
|
|
418
|
+
- `docs/requirements.md` - MERGE (Edit tool): functional + technical + non-functional requirements
|
|
419
|
+
- `README.md` - MERGE (Edit tool): project overview, stack, getting started
|
|
420
|
+
- If a file has real content, agents MUST use Edit (targeted section edits), not Write (which destroys unrecreated content).
|
|
421
|
+
|
|
422
|
+
6. **Plain-English** (batched sonnet + haiku writes) - Generates a non-technical companion file `.gsd-t/techdebt_in_plain_english.md` with one entry per TD item. To avoid the M75 stall, findings are batched into groups of 36 and sent to parallel generator agents (each returns `{ entries: [{td, markdown}] }`). Entries are then assembled deterministically by severity group and chunk-written (haiku, <= 30KB per chunk).
|
|
423
|
+
|
|
424
|
+
7. **Commit** (haiku) - After document phase, a single git agent stages `.gsd-t/scan`, `.gsd-t/techdebt_in_plain_english.md`, `docs`, and `README.md` and commits them.
|
|
425
|
+
|
|
426
|
+
Note: The HTML render stage was removed in M71. The renderer resolved paths relative to the package dir (not projectDir), causing it to overwrite the package's own `scan-report.html`. The authoritative deliverables are the register, dimension files, plain-english file, and living docs.
|
|
427
|
+
|
|
428
|
+
**Exit**: Returns `{ status:"complete"|"complete-partial-coverage", coverageComplete, slicesTotal, slicesSucceeded, slicesFailed, findings, counts, tdRange, docsWritten }`.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
### Verify-Gate Technical Workflow
|
|
433
|
+
|
|
434
|
+
The verify-gate runs as a deterministic subroutine inside every workflow that calls `lib.runVerifyGate()`. It uses `bin/gsd-t-verify-gate.cjs`.
|
|
435
|
+
|
|
436
|
+
**Two-track structure**:
|
|
437
|
+
- **Track 1** (D1 preflight envelope): runs `bin/cli-preflight.cjs::runPreflight()`. Hard-fails on any `severity:"error"` check.
|
|
438
|
+
- **Track 2** (D2 parallel-CLI substrate): fans out CLI checks via `bin/parallel-cli.cjs::runParallel()`. Default workers: `tsc`, `biome`/`ruff` (lint), `npm test` (unit suite), `knip` (dead code), `gitleaks` (secrets), `scc`/`lizard` (complexity). Concurrency capped by `.gsd-t/ratelimit-map.json` (default fallback: 2).
|
|
439
|
+
|
|
440
|
+
**Result envelope**: `{ ok, schemaVersion, runId, track1, track2, summary }`. Summary is capped at 500 tokens (2000 chars) and is the input consumed by `bin/gsd-t-verify-gate-judge.cjs`.
|
|
441
|
+
|
|
442
|
+
**Known correctness issue**: When `runParallel` throws an exception, the catch block constructs `{ ok:false, results:[] }`. Inside `_shapeTrack2`, `[].every(...)` is vacuously true in JavaScript, so `track2Ok` becomes `true` even though the entire track failed. This causes a false PASS verdict when all CLI tools failed to run.
|
|
443
|
+
|
|
444
|
+
**Known test gap**: `playwright.config.mjs` is not detected by `_detectDefaultTrack2()`, so ESM playwright configs silently skip E2E in Track 2.
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
### Test-Data Ledger Workflow (M58)
|
|
449
|
+
|
|
450
|
+
When E2E tests insert data into project stores, they must register those inserts via the test-data ledger so Verify can purge them.
|
|
451
|
+
|
|
452
|
+
**Test-side flow (withTestData Playwright fixture)**:
|
|
453
|
+
1. Test extends base `@playwright/test` with `withTestData()` fixture
|
|
454
|
+
2. `testData.tag('E2E_DRAG')` generates a unique id: `"E2E_DRAG_{runId}_{counter}"`
|
|
455
|
+
3. `testData.register({ kind, store, id, taggedPrefix })` calls `appendInsert()` in `bin/gsd-t-test-data-ledger.cjs`, appending one JSONL row to `.gsd-t/test-data-ledger.jsonl`
|
|
456
|
+
4. The adapter's `id` must start with `taggedPrefix` - a mismatch throws (defense in depth)
|
|
457
|
+
|
|
458
|
+
**Purge flow (gsd-t-verify Step M58 gate)**:
|
|
459
|
+
1. `gsd-t test-data --purge --run {verifyRunId} --json` is called
|
|
460
|
+
2. Ledger reads all rows matching `runId`, groups by adapter `kind`
|
|
461
|
+
3. Three built-in adapters: `localStorage-key-prefix` (browser localStorage), `file-json-array` (JSON array files), `sqlite-table-where` (SQLite table rows)
|
|
462
|
+
4. Each adapter re-validates that `id` starts with `taggedPrefix` before deleting (defense in depth - never trust upstream alone)
|
|
463
|
+
5. If any adapter throws or refuses, verify gate FAILs (block-promotion semantics)
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
### Parallel Task Planning Workflow
|
|
468
|
+
|
|
469
|
+
The `gsd-t parallel` CLI and `bin/gsd-t-parallel.cjs` are used by `lib.proveFileDisjointness()` inside execute to validate file ownership before spawning domain workers.
|
|
470
|
+
|
|
471
|
+
**Gates applied in order**:
|
|
472
|
+
1. **D4 depgraph** - `validateDepGraph()` vets each ready task's dependency IDs against the task graph. Tasks with unmet dependencies are vetoed and appended as `dep_gate_veto` events.
|
|
473
|
+
2. **D5 disjointness** - `proveDisjointness()` checks that no two ready tasks claim the same output file. Overlap -> sequential fallback, appended as `disjointness_fallback` events.
|
|
474
|
+
3. **D6 economics** - `estimateTaskFootprint()` estimates per-task CW% (currently a M61 stub returning zeros; unattended split gate is permanently non-functional).
|
|
475
|
+
|
|
476
|
+
**Mode-aware gating**:
|
|
477
|
+
- `in-session`: `ctxPct + workerCount * summarySize <= 85%`. If exceeded, reduces N by 1 until it fits; floor is N=1 (never refuses).
|
|
478
|
+
- `unattended`: `estimatedCwPct > 60%` emits a `task_split` signal. Currently non-functional because the M61 stub always returns `estimatedCwPct=undefined`.
|
|
479
|
+
|
|
480
|
+
**Known critical issue**: `parseArgv` has only one `domain` slot - multiple `--domain` flags overwrite each other. The disjointness check therefore only evaluates the last domain's tasks, silently passing when earlier domains share files. This means the primary safety invariant (no two concurrent workers write the same file) is bypassed for multi-domain executions.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
### Heartbeat Event Logging (Updated)
|
|
485
|
+
|
|
486
|
+
The heartbeat system now also writes to the event stream for dashboard consumption.
|
|
487
|
+
|
|
488
|
+
**Flow**:
|
|
489
|
+
1. Claude Code hook fires (9 events: SessionStart, PostToolUse, SubagentStart, SubagentStop, TaskCompleted, TeammateIdle, Notification, Stop, SessionEnd)
|
|
490
|
+
2. `settings.json` hook calls `node ~/.claude/scripts/gsd-t-heartbeat.js`
|
|
491
|
+
3. Script reads JSON from stdin (capped at 1MB)
|
|
492
|
+
4. Validates `session_id` (alphanumeric regex `SAFE_SID=/^[a-zA-Z0-9_-]+$/`) - blocks path traversal
|
|
493
|
+
5. Validates `cwd` is absolute and `.gsd-t/` directory exists
|
|
494
|
+
6. Verifies resolved heartbeat file path is still within `.gsd-t/` (symlink-safe containment check)
|
|
495
|
+
7. Appends structured event to `.gsd-t/heartbeat-{session_id}.jsonl`
|
|
496
|
+
8. Also calls `buildEventStreamEntry(hook)` and appends to `.gsd-t/events/YYYY-MM-DD.jsonl` (enriched event stream for dashboard + stream-feed-server)
|
|
497
|
+
9. On SessionStart: runs `cleanupOldHeartbeats()` removing files older than 7 days
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
### Stream Feed Server Workflow
|
|
502
|
+
|
|
503
|
+
The stream-feed server (`scripts/gsd-t-stream-feed-server.js`) is a loopback-only HTTP + WebSocket server that ingests stream-json frames from worker processes and broadcasts to connected dashboard clients.
|
|
504
|
+
|
|
505
|
+
**Ingest flow (POST /ingest)**:
|
|
506
|
+
1. Worker (claude -p subprocess) sends a chunked POST to `http://127.0.0.1:{port}/ingest`
|
|
507
|
+
2. Server accumulates body in `buf` string, splitting on newlines as frames arrive
|
|
508
|
+
3. Each newline-delimited frame is validated as JSON, then `persistFrame()` appends it to `.gsd-t/stream-feed/YYYY-MM-DD.jsonl`
|
|
509
|
+
4. At UTC midnight, `rotateIfNeeded()` opens a new daily file and resets the frame count
|
|
510
|
+
5. Known issue: no size limit on `buf` - a slow sender that never emits newline can grow the buffer to OOM
|
|
511
|
+
|
|
512
|
+
**Broadcast flow (GET /feed WebSocket)**:
|
|
513
|
+
1. Client sends a WebSocket upgrade request to `/feed?from=N` (replay from frame N)
|
|
514
|
+
2. Server performs RFC 6455 handshake (Sec-WebSocket-Accept header) and upgrades the connection
|
|
515
|
+
3. On upgrade, server replays frames from `recentFrames` buffer (in-memory mirror of today's JSONL, up to 10,000 frames) from index N
|
|
516
|
+
4. Each new ingested frame is broadcast as a WebSocket text frame to all connected clients
|
|
517
|
+
5. Clients that exceed the `BACKPRESSURE_LIMIT` (1000 buffered frames) are kicked via a close frame
|
|
518
|
+
6. Known issue: `encodeWsCloseFrame` writes `body.length` directly into the header byte, violating RFC 6455 (control frames max 125 bytes). Strict clients may reject malformed close frames.
|
|
519
|
+
|
|
520
|
+
**Port**: Default `7842` (overridden via `GSD_T_STREAM_FEED_PORT` env var).
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
### Auto-Update Workflow (Updated)
|
|
525
|
+
|
|
526
|
+
**Trigger**: Claude Code `SessionStart` hook via `scripts/gsd-t-update-check.js`.
|
|
527
|
+
|
|
528
|
+
**Flow**:
|
|
529
|
+
1. Reads installed version from `~/.claude/.gsd-t-version`
|
|
530
|
+
2. Reads cached npm latest from `~/.claude/.gsd-t-update-check` (JSON: `{ latest, timestamp }`)
|
|
531
|
+
3. If cache is fresh (< 1 hour): compares cached `latest` vs `installed`
|
|
532
|
+
4. If cache is stale (> 1 hour): calls `fetchLatestVersion()` which runs a Node inline script that GETs `https://registry.npmjs.org/@tekyzinc/gsd-t/latest` (5s timeout, 8s execFileSync timeout)
|
|
533
|
+
5. Known bug: the inline script has a syntax error at `r.on('data',(c)=>d+=c;` (missing `)`) - `fetchLatestVersion()` always returns `null`; auto-update from SessionStart is completely broken
|
|
534
|
+
6. If an update is available and fetchLatestVersion worked: runs `npm install -g @tekyzinc/gsd-t@{latest}` + `gsd-t update-all`; outputs `[GSD-T AUTO-UPDATE] v{old} -> v{new}` to stdout
|
|
535
|
+
7. Claude agent reads the hook output from context and shows the update status in the first response banner
|
|
536
|
+
|
|
537
|
+
**Output signals** (read from Claude Code hook context):
|
|
538
|
+
- `[GSD-T AUTO-UPDATE]`: auto-update ran successfully
|
|
539
|
+
- `[GSD-T UPDATE]`: update available but auto-update failed
|
|
540
|
+
- `[GSD-T] v{ver}`: current, no update needed
|
|
541
|
+
|
|
542
|
+
**Cache TTL**: 1 hour.
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
### Context Brief Generation Workflow
|
|
547
|
+
|
|
548
|
+
Context briefs are generated per Workflow spawn to give worker agents a compact snapshot of the project state without requiring them to re-walk the entire repo.
|
|
549
|
+
|
|
550
|
+
**Entry**: `lib.generateBrief({ kind, milestone, domain, projectDir })` in `_lib.js`, which calls `bin/gsd-t-context-brief.cjs::generateBrief()`.
|
|
551
|
+
|
|
552
|
+
**Flow**:
|
|
553
|
+
1. Loads the kind-specific collector from `bin/gsd-t-context-brief-kinds/{kind}.cjs`
|
|
554
|
+
2. Known kinds (11): `design-verify`, `discuss`, `execute`, `impact`, `milestone`, `partition`, `plan`, `qa`, `red-team`, `scan`, `verify`
|
|
555
|
+
3. Collector runs synchronously (no LLM spawn) using read-only filesystem + git commands
|
|
556
|
+
4. Output is a JSON brief envelope (max 10240 bytes), written to `.gsd-t/briefs/{kind}-{spawnId}.json` (gitignored - ephemera, not committed)
|
|
557
|
+
5. `FAIL_CLOSED_KINDS = ['qa', 'red-team', 'design-verify']` - errors in these collectors throw rather than returning partial data
|
|
558
|
+
6. Other kinds fail-open: if the collector throws, the brief is empty and the worker falls back to re-walking the repo
|
|
559
|
+
|
|
560
|
+
**Workers use briefs by**: reading `$BRIEF_PATH` before grepping/reading the codebase. Per Brief-First Worker Rule: "if you're about to grep, check the brief first."
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
### Decision Log Archive Workflow
|
|
565
|
+
|
|
566
|
+
To keep `.gsd-t/progress.md` lean, old Decision Log entries are rolled into archive files via `bin/archive-progress.cjs`.
|
|
567
|
+
|
|
568
|
+
**Entry**: `node bin/archive-progress.cjs [--project DIR] [--keep N] [--per-archive N] [--dry-run]`
|
|
569
|
+
|
|
570
|
+
**Flow**:
|
|
571
|
+
1. Reads `progress.md` and finds the Decision Log section (heading containing "Decision Log" at any ATX level)
|
|
572
|
+
2. Parses entries by start pattern `/^- (\d{4}-\d{2}-\d{2})(?:[ T]\d{2}:\d{2})?/`
|
|
573
|
+
3. Keeps the most recent `keepLive` entries (default 5) in the live `progress.md`
|
|
574
|
+
4. Remaining older entries are appended to numbered archive files at `.gsd-t/progress-archive/NNN-YYYY-MM-DD.md` (up to `perArchive` entries per file, default 20)
|
|
575
|
+
5. Idempotent: running with no new entries is a no-op; running repeatedly does not re-archive already-archived entries
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.28",
|
|
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",
|
|
@@ -607,6 +607,19 @@ log(`register written: ${JSON.stringify(synthesis.counts)} (${synthesis.tdRange}
|
|
|
607
607
|
// READ the just-written register itself, since the orchestrator can't read it.
|
|
608
608
|
phase("Document");
|
|
609
609
|
const sliceSummary = slices.map((s) => `- ${s.key} (${s.dimension}): ${JSON.stringify(s.paths)}`).join("\n");
|
|
610
|
+
// Compact serialization of the verified findings handed to each document agent. Project
|
|
611
|
+
// only the fields the docs need (full objects can carry verbose verify metadata); the
|
|
612
|
+
// baseCtx caps it at 120KB below. (Bugfix: findingsJson was referenced but never defined,
|
|
613
|
+
// which crashed the entire workflow at the Document phase AFTER all finders/verify/synthesis
|
|
614
|
+
// ran — ReferenceError: findingsJson is not defined.)
|
|
615
|
+
const findingsJson = JSON.stringify(
|
|
616
|
+
finalFindings.map((f) => ({
|
|
617
|
+
title: f.title, severity: f.severity, area: f.area,
|
|
618
|
+
files: f.files, detail: f.detail, impact: f.impact,
|
|
619
|
+
recommendation: f.recommendation, slice: f.slice,
|
|
620
|
+
})),
|
|
621
|
+
null, 1
|
|
622
|
+
);
|
|
610
623
|
const baseCtx = [
|
|
611
624
|
`Project: \`${projectDir}\`. Probe totals: ${JSON.stringify(probe.totals)}.`,
|
|
612
625
|
`Slices the scan covered:`,
|
|
@@ -740,20 +753,37 @@ for (const sev of ["CRITICAL", "HIGH", "MEDIUM", "LOW"]) {
|
|
|
740
753
|
}
|
|
741
754
|
if (buf.trim()) peChunks.push(buf);
|
|
742
755
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
|
|
756
|
+
// M80 fix: a SINGLE owning agent writes ALL chunks sequentially and SELF-VERIFIES the
|
|
757
|
+
// final `### TD-` entry count, retrying short writes. The prior design fanned each chunk
|
|
758
|
+
// to a separate haiku agent via heredoc-append — agents replied "OK" without faithfully
|
|
759
|
+
// appending 30KB blobs of markdown (special chars `$` ` # collide with the heredoc /
|
|
760
|
+
// get paraphrased), so the middle chunks silently dropped: run wf_b2a6a9e0-9de wrote
|
|
761
|
+
// only 65/181 entries (Critical+High + a 2-item tail). With one owner + a count check,
|
|
762
|
+
// an incomplete write is detected and fixed in-agent instead of shipping truncated.
|
|
763
|
+
const peExpectedEntries = Object.values(peGroups).reduce((a, b) => a + b.length, 0);
|
|
764
|
+
const peWriteRes = await gatedAgent(
|
|
765
|
+
[
|
|
766
|
+
`You write ONE file: \`${peTarget}\`. It has ${peChunks.length} ordered chunks (below, each between <<<C n>>> and <<<END n>>>).`,
|
|
767
|
+
`Procedure — follow EXACTLY:`,
|
|
768
|
+
`1. Write chunk 1 to \`${peTarget}\` VERBATIM using the Write tool (creates/overwrites).`,
|
|
769
|
+
`2. For chunks 2..${peChunks.length}, APPEND each VERBATIM to the END of the file. Use the Write tool with the FULL accumulated content (read the file, concatenate the next chunk, Write the whole thing) — do NOT use a heredoc (special chars corrupt it).`,
|
|
770
|
+
`3. After all chunks: run \`grep -c '^### TD-' ${peTarget}\`. It MUST equal ${peExpectedEntries}.`,
|
|
771
|
+
`4. If the count is LESS than ${peExpectedEntries}, you dropped content — redo the append for the missing chunks until the count is exactly ${peExpectedEntries}.`,
|
|
772
|
+
`Reply with ONLY the final integer count from step 3 (e.g. "${peExpectedEntries}"). Nothing else. Reproduce every chunk verbatim — do not summarize, reword, or skip entries.`,
|
|
773
|
+
``,
|
|
774
|
+
...peChunks.map((c, i) => `<<<C ${i + 1}>>>\n${c}\n<<<END ${i + 1}>>>`),
|
|
775
|
+
].join("\n"),
|
|
776
|
+
{ label: `plain-english write (${peChunks.length} chunks, ${peExpectedEntries} entries)`, phase: "Plain-English", model: "sonnet" }
|
|
777
|
+
).catch((e) => ({ _e: String(e && e.message) }));
|
|
778
|
+
// Independent verification by a second cheap agent (the writer self-reports; trust but verify).
|
|
779
|
+
const peVerify = await gatedAgent(
|
|
780
|
+
`Run \`grep -c '^### TD-' ${peTarget}\` and reply with ONLY the integer it prints.`,
|
|
781
|
+
{ label: `plain-english verify-count`, phase: "Plain-English", model: "haiku" }
|
|
782
|
+
).catch(() => null);
|
|
783
|
+
const peActual = (typeof peVerify === "string" && /\d+/.test(peVerify)) ? parseInt(peVerify.match(/\d+/)[0], 10) : null;
|
|
784
|
+
const peComplete = peActual === peExpectedEntries;
|
|
785
|
+
if (!peComplete) log(`⚠ plain-english INCOMPLETE: wrote ${peActual}/${peExpectedEntries} entries (writer said ${typeof peWriteRes === "string" ? peWriteRes.trim().slice(0, 20) : JSON.stringify(peWriteRes).slice(0, 40)})`);
|
|
786
|
+
log(`plain-english: ${peExpectedEntries}/${peItems.length} entries, grouped by severity, ${peChunks.length} chunks, on-disk count ${peActual ?? "?"} (${peComplete ? "COMPLETE" : "INCOMPLETE"})${peFailed ? `; ${peFailed} gen batch(es) failed` : ""}`);
|
|
757
787
|
|
|
758
788
|
// Commit the docs + dimension files + plain-english via a small agent (Bash git).
|
|
759
789
|
const commitAgent = await agent(
|
|
@@ -787,6 +817,11 @@ return {
|
|
|
787
817
|
docsWritten: docsOk.length,
|
|
788
818
|
docsFailed: docsFailed.map((d) => d.doc),
|
|
789
819
|
docsCommitted: commitAgent && commitAgent.status,
|
|
820
|
+
// M80: plain-english completeness is surfaced, not silent. plainEnglishComplete=false
|
|
821
|
+
// means the companion doc is truncated (writer dropped entries) — the caller should flag it.
|
|
822
|
+
plainEnglishEntries: peActual,
|
|
823
|
+
plainEnglishExpected: peExpectedEntries,
|
|
824
|
+
plainEnglishComplete: peComplete,
|
|
790
825
|
htmlReport: null, // render stage removed (M71)
|
|
791
826
|
probeTotals: probe.totals,
|
|
792
827
|
};
|