@tekyzinc/gsd-t 4.0.27 → 4.0.29
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 +31 -0
- package/README.md +91 -142
- package/bin/scan-data-collector.js +28 -0
- 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/CLAUDE-global.md +1 -1
- package/templates/workflows/gsd-t-debug.workflow.js +34 -5
- package/templates/workflows/gsd-t-execute.workflow.js +54 -29
- package/templates/workflows/gsd-t-integrate.workflow.js +37 -7
- package/templates/workflows/gsd-t-phase.workflow.js +36 -7
- package/templates/workflows/gsd-t-quick.workflow.js +59 -7
- package/templates/workflows/gsd-t-scan.workflow.js +49 -14
- package/templates/workflows/gsd-t-verify.workflow.js +67 -47
- package/templates/workflows/gsd-t-wave.workflow.js +7 -4
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.29",
|
|
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",
|
|
@@ -331,7 +331,7 @@ Canonical scripts:
|
|
|
331
331
|
- `gsd-t-phase.workflow.js` — generic upper-stage runner (partition / plan / discuss / impact / milestone / prd / design-decompose / doc-ripple)
|
|
332
332
|
- `gsd-t-scan.workflow.js` — preflight → volume-probe → pipeline(per-slice deep finder → single verify) → synthesis → document → render (M66: fans out by codebase VOLUME, not a fixed 5-teammate dimension count; M67: deep document phase deterministically produces the full living-doc set + dimension files, per-doc fan-out)
|
|
333
333
|
|
|
334
|
-
|
|
334
|
+
**Runtime-native invariant (M81 — v4.0.29+):** the Workflow sandbox provides ONLY `agent/parallel/pipeline/log/phase/budget/args` — NO `require`/`fs`/`path`/`child_process`/`process`, and `args` arrives as a JSON STRING. Each workflow is self-contained: it `JSON.parse`s `args` and delegates every CLI call (preflight, verify-gate, brief, build-coverage, ci-parity, test-data, disjointness) to inline `async` helpers that run the command via an `agent()`'s Bash (preferring project-local `bin/<tool>.cjs`, else the global `gsd-t` PATH binary) and parse the JSON envelope — preserving the M55-D5 project-local-bin invariant. The old `require("./_lib.js")` pattern threw `ReferenceError` on first eval and silently broke every workflow except scan (TD-113, fixed M81); `_lib.js` is retired as a workflow dependency.
|
|
335
335
|
|
|
336
336
|
## Preflight Gate (KEPT from M55)
|
|
337
337
|
|
|
@@ -16,10 +16,39 @@ export const meta = {
|
|
|
16
16
|
],
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// M81: runtime-native helpers (sandbox bans require/fs/child_process/process — the old
|
|
20
|
+
// require("./_lib.js") crashed this workflow on first eval, TD-113). Delegate CLI calls
|
|
21
|
+
// to an agent's Bash; args arrives as a JSON STRING in this runtime. See gsd-t-scan.workflow.js.
|
|
22
|
+
const _args = (typeof args === "string") ? (() => { try { return JSON.parse(args); } catch { return {}; } })() : (args || {});
|
|
23
|
+
const _CLI_ENVELOPE_SCHEMA = {
|
|
24
|
+
type: "object", required: ["ok", "exitCode"], additionalProperties: true,
|
|
25
|
+
properties: { ok: { type: "boolean" }, exitCode: { type: "integer" }, envelope: {}, stdout: { type: "string" }, stderr: { type: "string" }, via: { type: "string" } },
|
|
26
|
+
};
|
|
27
|
+
async function runCli(projectDir, subcmd, argv, localBin, label, parseJson = true, phaseName) {
|
|
28
|
+
const argStr = (argv || []).map((a) => `'${String(a).replace(/'/g, "'\\''")}'`).join(" ");
|
|
29
|
+
const prompt = [
|
|
30
|
+
`Run a GSD-T CLI command for the project at \`${projectDir}\` and report the result. Steps:`,
|
|
31
|
+
`1. If \`${projectDir}/bin/${localBin}\` exists, run: \`node ${projectDir}/bin/${localBin} ${argStr}\` (set via="local"). Otherwise run: \`gsd-t ${subcmd} ${argStr}\` (set via="global"). Use cwd \`${projectDir}\`.`,
|
|
32
|
+
`2. Capture exit code (ok = exitCode 0) and stdout/stderr.`,
|
|
33
|
+
parseJson ? `3. Parse stdout as JSON into \`envelope\` (null if not JSON). Return JSON per the schema.` : `3. Put stdout (trimmed, ≤4000 chars) in \`stdout\`. Return JSON per the schema.`,
|
|
34
|
+
`Do NOT do any other work. ONLY run this one command and report.`,
|
|
35
|
+
].join("\n");
|
|
36
|
+
const opts = { label, schema: _CLI_ENVELOPE_SCHEMA, model: "haiku" };
|
|
37
|
+
if (phaseName) opts.phase = phaseName;
|
|
38
|
+
const r = await agent(prompt, opts).catch((e) => ({ ok: false, exitCode: -1, envelope: null, stderr: String(e && e.message), via: "error" }));
|
|
39
|
+
return r || { ok: false, exitCode: -1, envelope: null, via: "error" };
|
|
40
|
+
}
|
|
41
|
+
async function runPreflight(projectDir, label = "preflight", phaseName) { return runCli(projectDir, "preflight", ["--json"], "cli-preflight.cjs", label, true, phaseName); }
|
|
42
|
+
async function generateBrief(projectDir, { kind = "execute", milestone, domain, id, label = "brief", phaseName } = {}) {
|
|
43
|
+
const argv = ["--kind", kind, "--spawn-id", id, "--out", `${projectDir}/.gsd-t/briefs/${id}.json`];
|
|
44
|
+
if (milestone) argv.push("--milestone", milestone);
|
|
45
|
+
if (domain) argv.push("--domain", domain);
|
|
46
|
+
const r = await runCli(projectDir, "brief", argv, "gsd-t-context-brief.cjs", label, false, phaseName);
|
|
47
|
+
return { ok: r.ok, briefPath: `${projectDir}/.gsd-t/briefs/${id}.json`, via: r.via };
|
|
48
|
+
}
|
|
20
49
|
|
|
21
|
-
const projectDir =
|
|
22
|
-
const symptom =
|
|
50
|
+
const projectDir = _args.projectDir || ".";
|
|
51
|
+
const symptom = _args.symptom || null;
|
|
23
52
|
|
|
24
53
|
const DEBUG_CYCLE_SCHEMA = {
|
|
25
54
|
type: "object",
|
|
@@ -42,9 +71,9 @@ if (!symptom) {
|
|
|
42
71
|
}
|
|
43
72
|
|
|
44
73
|
phase("Preflight");
|
|
45
|
-
const pre =
|
|
74
|
+
const pre = await runPreflight(projectDir);
|
|
46
75
|
if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
|
|
47
|
-
const brief =
|
|
76
|
+
const brief = await generateBrief(projectDir, { kind: "execute", id: "debug-brief" });
|
|
48
77
|
|
|
49
78
|
let lastResult = null;
|
|
50
79
|
for (let cycle = 1; cycle <= 2; cycle++) {
|
|
@@ -65,12 +65,47 @@ const INTEGRATE_RESULT_SCHEMA = {
|
|
|
65
65
|
|
|
66
66
|
// ───── Script body ──────────────────────────────────────────────────────────
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// M81: runtime-native helpers (sandbox bans require/fs/path/child_process/process — the
|
|
69
|
+
// old require("./_lib.js")+require("path") crashed this on first eval, TD-113). CLI calls
|
|
70
|
+
// delegate to an agent's Bash; file reads (scope.md/tasks.md) move INTO the worker agent
|
|
71
|
+
// (it has Read). args arrives as a JSON STRING in this runtime. See gsd-t-scan.workflow.js.
|
|
72
|
+
const _args = (typeof args === "string") ? (() => { try { return JSON.parse(args); } catch { return {}; } })() : (args || {});
|
|
73
|
+
const _CLI_ENVELOPE_SCHEMA = {
|
|
74
|
+
type: "object", required: ["ok", "exitCode"], additionalProperties: true,
|
|
75
|
+
properties: { ok: { type: "boolean" }, exitCode: { type: "integer" }, envelope: {}, stdout: { type: "string" }, stderr: { type: "string" }, via: { type: "string" } },
|
|
76
|
+
};
|
|
77
|
+
async function runCli(projectDir, subcmd, argv, localBin, label, parseJson = true, phaseName) {
|
|
78
|
+
const argStr = (argv || []).map((a) => `'${String(a).replace(/'/g, "'\\''")}'`).join(" ");
|
|
79
|
+
const prompt = [
|
|
80
|
+
`Run a GSD-T CLI command for the project at \`${projectDir}\` and report the result. Steps:`,
|
|
81
|
+
`1. If \`${projectDir}/bin/${localBin}\` exists, run: \`node ${projectDir}/bin/${localBin} ${argStr}\` (set via="local"). Otherwise run: \`gsd-t ${subcmd} ${argStr}\` (set via="global"). Use cwd \`${projectDir}\`.`,
|
|
82
|
+
`2. Capture exit code (ok = exitCode 0) and stdout/stderr.`,
|
|
83
|
+
parseJson ? `3. Parse stdout as JSON into \`envelope\` (null if not JSON). Return JSON per the schema.` : `3. Put stdout (trimmed, ≤4000 chars) in \`stdout\`. Return JSON per the schema.`,
|
|
84
|
+
`Do NOT do any other work. ONLY run this one command and report.`,
|
|
85
|
+
].join("\n");
|
|
86
|
+
const opts = { label, schema: _CLI_ENVELOPE_SCHEMA, model: "haiku" };
|
|
87
|
+
if (phaseName) opts.phase = phaseName;
|
|
88
|
+
const r = await agent(prompt, opts).catch((e) => ({ ok: false, exitCode: -1, envelope: null, stderr: String(e && e.message), via: "error" }));
|
|
89
|
+
return r || { ok: false, exitCode: -1, envelope: null, via: "error" };
|
|
90
|
+
}
|
|
91
|
+
async function runPreflight(projectDir, label = "preflight", phaseName) { return runCli(projectDir, "preflight", ["--json"], "cli-preflight.cjs", label, true, phaseName); }
|
|
92
|
+
async function runVerifyGate(projectDir, label = "verify-gate", phaseName) { return runCli(projectDir, "verify-gate", ["--json"], "gsd-t-verify-gate.cjs", label, true, phaseName); }
|
|
93
|
+
async function proveFileDisjointness(projectDir, domains, label = "disjointness", phaseName) {
|
|
94
|
+
const argv = ["--dry-run"];
|
|
95
|
+
for (const d of (domains || [])) { argv.push("--domain", d); }
|
|
96
|
+
return runCli(projectDir, "parallel", argv, "gsd-t-parallel.cjs", label, false, phaseName);
|
|
97
|
+
}
|
|
98
|
+
async function generateBrief(projectDir, { kind = "execute", milestone, domain, id, label = "brief", phaseName } = {}) {
|
|
99
|
+
const argv = ["--kind", kind, "--spawn-id", id, "--out", `${projectDir}/.gsd-t/briefs/${id}.json`];
|
|
100
|
+
if (milestone) argv.push("--milestone", milestone);
|
|
101
|
+
if (domain) argv.push("--domain", domain);
|
|
102
|
+
const r = await runCli(projectDir, "brief", argv, "gsd-t-context-brief.cjs", label, false, phaseName);
|
|
103
|
+
return { ok: r.ok, briefPath: `${projectDir}/.gsd-t/briefs/${id}.json`, via: r.via };
|
|
104
|
+
}
|
|
70
105
|
|
|
71
|
-
const projectDir =
|
|
72
|
-
const milestone =
|
|
73
|
-
const domains = (
|
|
106
|
+
const projectDir = _args.projectDir || ".";
|
|
107
|
+
const milestone = _args.milestone || null;
|
|
108
|
+
const domains = (Array.isArray(_args.domains) && _args.domains) || [];
|
|
74
109
|
|
|
75
110
|
if (!milestone) {
|
|
76
111
|
log("execute: no milestone provided — args.milestone is required");
|
|
@@ -83,7 +118,7 @@ if (!domains.length) {
|
|
|
83
118
|
|
|
84
119
|
phase("Preflight");
|
|
85
120
|
log(`execute: milestone=${milestone}, domains=${domains.length}`);
|
|
86
|
-
const pre =
|
|
121
|
+
const pre = await runPreflight(projectDir);
|
|
87
122
|
if (!pre.ok) {
|
|
88
123
|
log(`preflight FAIL — exitCode=${pre.exitCode}: ${pre.stderr || "(no stderr)"}`);
|
|
89
124
|
return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
|
|
@@ -93,7 +128,7 @@ log(`preflight OK`);
|
|
|
93
128
|
phase("Disjointness");
|
|
94
129
|
// 4.8-audit fix: scope disjointness to the requested domain set, not the whole project.
|
|
95
130
|
// Without this, an unrelated DRAFT domain elsewhere in the project could flip the result.
|
|
96
|
-
const disj =
|
|
131
|
+
const disj = await proveFileDisjointness(projectDir, domains);
|
|
97
132
|
if (!disj.ok) {
|
|
98
133
|
log(`disjointness FAIL — exitCode=${disj.exitCode}: ${disj.stderr || disj.stdout}`);
|
|
99
134
|
return { status: "failed", reason: "non-disjoint" };
|
|
@@ -105,32 +140,22 @@ const domainResults = await parallel(
|
|
|
105
140
|
domains.map((domain) => async () => {
|
|
106
141
|
// 4.8-audit fix: per-domain brief (M55-D2 brief-per-spawn semantic) — each worker
|
|
107
142
|
// gets a brief scoped to its own domain so grep-the-brief is most effective.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// every task" — silently dropping tail content is a correctness regression. Briefs
|
|
115
|
-
// are the compression layer; raw scope/tasks must pass whole.
|
|
116
|
-
const scope = lib.readScope({ projectDir, domain }) || "(scope.md missing)";
|
|
117
|
-
const tasks = lib.readDomainTasks({ projectDir, domain }) || "(tasks.md missing)";
|
|
143
|
+
// M81: generated via an awaited agent (sandbox-safe); the worker reads its own
|
|
144
|
+
// scope.md/tasks.md (it has Read) instead of the orchestrator pre-reading via fs.
|
|
145
|
+
const domBrief = await generateBrief(projectDir, { kind: "execute", milestone, domain, id: `execute-${(milestone || "m").toLowerCase()}-${domain}`, phaseName: "Domains", label: `brief:${domain}` });
|
|
146
|
+
const briefRef = domBrief.ok ? domBrief.briefPath : "(brief generation failed — re-walk repo)";
|
|
147
|
+
const scopePath = `${projectDir}/.gsd-t/domains/${domain}/scope.md`;
|
|
148
|
+
const tasksPath = `${projectDir}/.gsd-t/domains/${domain}/tasks.md`;
|
|
118
149
|
const prompt = [
|
|
119
150
|
`You are the worker agent for the GSD-T domain \`${domain}\` in milestone \`${milestone}\`.`,
|
|
120
151
|
``,
|
|
121
|
-
`
|
|
152
|
+
`FIRST, read these two files in full (do NOT skip or truncate them):`,
|
|
153
|
+
`- Scope (your owned files): \`${scopePath}\``,
|
|
154
|
+
`- Tasks: \`${tasksPath}\``,
|
|
122
155
|
``,
|
|
123
|
-
|
|
124
|
-
``,
|
|
125
|
-
`**Scope (your owned files):**`,
|
|
126
|
-
"```",
|
|
127
|
-
scope,
|
|
128
|
-
"```",
|
|
156
|
+
`Your job: execute every task listed under "## Tasks" in tasks.md, respecting the file ownership in scope.md.`,
|
|
129
157
|
``,
|
|
130
|
-
`**
|
|
131
|
-
"```",
|
|
132
|
-
tasks,
|
|
133
|
-
"```",
|
|
158
|
+
`**Brief (REQUIRED READ):** ${briefRef} — if present, grep this JSON first instead of re-reading CLAUDE.md and contracts.`,
|
|
134
159
|
``,
|
|
135
160
|
`Constraints:`,
|
|
136
161
|
`- Touch only files in your scope's "Owned Files" list.`,
|
|
@@ -194,7 +219,7 @@ if (integrate.status === "failed") {
|
|
|
194
219
|
}
|
|
195
220
|
|
|
196
221
|
phase("Verify-Gate");
|
|
197
|
-
const vg =
|
|
222
|
+
const vg = await runVerifyGate(projectDir);
|
|
198
223
|
log(`verify-gate exitCode=${vg.exitCode} ok=${vg.ok}`);
|
|
199
224
|
|
|
200
225
|
return {
|