@tekyzinc/gsd-t 2.72.10 → 2.73.11
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 +14 -0
- package/bin/design-orchestrator.js +56 -22
- package/bin/gsd-t.js +1 -1
- package/bin/orchestrator.js +129 -27
- package/commands/gsd-t-design-build.md +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [2.73.11] - 2026-04-08
|
|
6
|
+
|
|
7
|
+
### Changed (reviewer — Playwright-first visual inspection)
|
|
8
|
+
- **Playwright is now the PRIMARY reviewer method** — every contract-specified visual property is verified via `getComputedStyle()` in a real browser. Code review demoted to supplement for non-visual concerns (props, events, accessibility). CSS box math (cascade, specificity, flex/grid computation, relative units) can only be verified at render time, not from source code.
|
|
9
|
+
|
|
10
|
+
## [2.73.10] - 2026-04-08
|
|
11
|
+
|
|
12
|
+
### Added (orchestrator — parallel execution)
|
|
13
|
+
- **`--parallel N` flag** — runs N build+review items concurrently via async `spawnClaudeAsync()` and `_runWithConcurrency()`. Default: 1 (sequential). Recommended: 3. Reduces 15-element pipeline from ~30min to ~10min at 3x parallelism.
|
|
14
|
+
- **`--clean` artifact cleanup expanded** — now clears `auto-review/`, `build-logs/`, `queue/`, `feedback/`, `review-complete.json`, `orchestrator-state.json` on fresh start (not just build output).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **Orchestrator `run()` is now async** — callers (`bin/gsd-t.js`, `bin/design-orchestrator.js`) updated with `.catch()` for proper error handling.
|
|
18
|
+
|
|
5
19
|
## [2.72.10] - 2026-04-08
|
|
6
20
|
|
|
7
21
|
### Added (orchestrator — per-item pipeline, stream-json, verbose, clean)
|
|
@@ -365,25 +365,39 @@ ${componentList}
|
|
|
365
365
|
${measurementContext}
|
|
366
366
|
## Review Process
|
|
367
367
|
|
|
368
|
-
**Step 1 —
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
-
|
|
376
|
-
|
|
377
|
-
|
|
368
|
+
**Step 1 — Playwright visual inspection (PRIMARY — do this FIRST):**
|
|
369
|
+
Every UI component is visual and MUST be visually inspected. For each component:
|
|
370
|
+
1. Open http://localhost:${ports.reviewPort}/ in Playwright
|
|
371
|
+
2. Navigate to / render the component using its selector
|
|
372
|
+
3. For EVERY contract-specified visual property, use \`page.locator(selector).evaluate(el => getComputedStyle(el))\` to measure the actual computed value:
|
|
373
|
+
- **Box model**: width, height, padding, margin, gap, border-width, border-radius
|
|
374
|
+
- **Colors**: background-color, color, border-color (compare computed rgb/rgba values)
|
|
375
|
+
- **Typography**: font-family, font-size, font-weight, line-height, letter-spacing
|
|
376
|
+
- **Layout**: display, flex-direction, justify-content, align-items, grid-template-columns
|
|
377
|
+
- **Spacing**: gap, row-gap, column-gap, padding (all sides), margin (all sides)
|
|
378
|
+
- **Visual**: opacity, box-shadow, overflow, text-overflow
|
|
379
|
+
4. Compare each measured value against the contract specification
|
|
380
|
+
5. Check that the component renders without console errors
|
|
381
|
+
6. For charts/SVGs: verify chart type, orientation, data structure, and proportions visually
|
|
382
|
+
|
|
383
|
+
Code review alone CANNOT verify CSS box math — cascade, specificity, flex/grid computation, relative units, and parent constraints all affect the final rendered result. Only computed styles from a running browser are authoritative.
|
|
384
|
+
|
|
385
|
+
**Step 2 — Code review (supplement for non-visual concerns):**
|
|
386
|
+
Read the source file to check:
|
|
387
|
+
- Prop interfaces match contract definitions
|
|
388
|
+
- Event handlers and interactivity are wired correctly
|
|
389
|
+
- Data binding and state management are correct
|
|
390
|
+
- Accessibility attributes (aria-*, role) are present
|
|
391
|
+
- Component structure matches contract hierarchy
|
|
378
392
|
|
|
379
393
|
## Output Format
|
|
380
394
|
|
|
381
|
-
Output your findings between these markers. Each issue must have component, severity (critical/high/medium/low), and description with SPECIFIC contract vs. actual
|
|
395
|
+
Output your findings between these markers. Each issue must have component, severity (critical/high/medium/low), and description with SPECIFIC contract value vs. actual computed value:
|
|
382
396
|
|
|
383
397
|
[REVIEW_ISSUES]
|
|
384
398
|
[
|
|
385
399
|
{"component": "ComponentName", "severity": "critical", "description": "Contract specifies donut chart but rendered as pie chart (no inner radius)"},
|
|
386
|
-
{"component": "ComponentName", "severity": "high", "description": "Grid gap: contract 16px,
|
|
400
|
+
{"component": "ComponentName", "severity": "high", "description": "Grid gap: contract 16px, computed 24px"}
|
|
387
401
|
]
|
|
388
402
|
[/REVIEW_ISSUES]
|
|
389
403
|
|
|
@@ -395,9 +409,10 @@ If ALL components match their contracts, output:
|
|
|
395
409
|
## CRITICAL — Output Rules
|
|
396
410
|
- Output MUST contain the [REVIEW_ISSUES] markers — the orchestrator parses your result from these markers. Without them, your review is lost.
|
|
397
411
|
- You write ZERO code. You ONLY review.
|
|
412
|
+
- You MUST run Playwright for every component. Skipping visual inspection is a review failure.
|
|
398
413
|
- Be HARSH. Your value is in catching what the builder missed.
|
|
399
|
-
- NEVER say "looks close" or "appears to match" — give SPECIFIC values.
|
|
400
|
-
- Every contract property must be verified. Missing verification = missed issue.
|
|
414
|
+
- NEVER say "looks close" or "appears to match" — give SPECIFIC computed values.
|
|
415
|
+
- Every contract property must be verified via computed style. Missing verification = missed issue.
|
|
401
416
|
- Severity guide: critical = wrong component type, missing element, broken render. high = wrong dimensions, colors, layout. medium = spacing/padding off. low = minor visual difference.`;
|
|
402
417
|
}
|
|
403
418
|
|
|
@@ -421,15 +436,30 @@ function buildSingleItemReviewPrompt(phase, item, measurements, projectDir, port
|
|
|
421
436
|
${measurementContext}
|
|
422
437
|
## Review Process
|
|
423
438
|
|
|
424
|
-
1
|
|
425
|
-
|
|
426
|
-
|
|
439
|
+
**Step 1 — Playwright visual inspection (PRIMARY):**
|
|
440
|
+
This is a UI component — visual inspection is mandatory, not optional.
|
|
441
|
+
1. Read the design contract file — note every specified visual property value
|
|
442
|
+
2. Open http://localhost:${ports.reviewPort}/ in Playwright
|
|
443
|
+
3. Render the component using its selector
|
|
444
|
+
4. For EVERY contract-specified visual property, measure the actual computed value:
|
|
445
|
+
\`page.locator(selector).evaluate(el => getComputedStyle(el))\`
|
|
446
|
+
- Box model: width, height, padding, margin, gap, border-width, border-radius
|
|
447
|
+
- Colors: background-color, color, border-color (compare computed rgb/rgba)
|
|
448
|
+
- Typography: font-family, font-size, font-weight, line-height
|
|
449
|
+
- Layout: display, flex-direction, justify-content, align-items
|
|
450
|
+
5. Compare each computed value against the contract specification
|
|
451
|
+
6. For charts/SVGs: verify type, orientation, proportions visually
|
|
452
|
+
|
|
453
|
+
Code review alone cannot verify CSS — cascade, specificity, flex/grid, and relative units all affect computed output.
|
|
454
|
+
|
|
455
|
+
**Step 2 — Code review (non-visual concerns):**
|
|
456
|
+
Read the source file to check prop interfaces, event handlers, data binding, and accessibility attributes.
|
|
427
457
|
|
|
428
458
|
## Output Format
|
|
429
459
|
|
|
430
460
|
[REVIEW_ISSUES]
|
|
431
461
|
[
|
|
432
|
-
{"component": "${item.componentName}", "severity": "high", "description": "Contract specifies X,
|
|
462
|
+
{"component": "${item.componentName}", "severity": "high", "description": "Contract specifies X, computed value is Y"}
|
|
433
463
|
]
|
|
434
464
|
[/REVIEW_ISSUES]
|
|
435
465
|
|
|
@@ -439,8 +469,9 @@ If the component matches its contract, output:
|
|
|
439
469
|
[/REVIEW_ISSUES]
|
|
440
470
|
|
|
441
471
|
## Rules
|
|
472
|
+
- You MUST run Playwright. Skipping visual inspection is a review failure.
|
|
442
473
|
- You write ZERO code. You ONLY review.
|
|
443
|
-
- Be HARSH — specific values only, no "looks close."
|
|
474
|
+
- Be HARSH — specific computed values only, no "looks close."
|
|
444
475
|
- Output MUST contain [REVIEW_ISSUES] markers.`;
|
|
445
476
|
}
|
|
446
477
|
|
|
@@ -485,6 +516,9 @@ ${BOLD}Options:${RESET}
|
|
|
485
516
|
--review-port <N> Review server port (default: 3456)
|
|
486
517
|
--timeout <sec> Claude timeout per tier in seconds (default: 600)
|
|
487
518
|
--skip-measure Skip Playwright measurement (human-review only)
|
|
519
|
+
--clean Clear all stale artifacts before starting
|
|
520
|
+
--verbose, -v Show Claude's tool calls and prompts in terminal
|
|
521
|
+
--parallel <N> Run N items concurrently (default: 1 = sequential)
|
|
488
522
|
--help Show this help
|
|
489
523
|
|
|
490
524
|
${BOLD}Pipeline:${RESET}
|
|
@@ -542,12 +576,12 @@ const designBuildWorkflow = {
|
|
|
542
576
|
|
|
543
577
|
// ─── Entry Point ────────────────────────────────────────────────────────────
|
|
544
578
|
|
|
545
|
-
function run(args) {
|
|
546
|
-
new Orchestrator(designBuildWorkflow).run(args || []);
|
|
579
|
+
async function run(args) {
|
|
580
|
+
await new Orchestrator(designBuildWorkflow).run(args || []);
|
|
547
581
|
}
|
|
548
582
|
|
|
549
583
|
if (require.main === module) {
|
|
550
|
-
run(process.argv.slice(2));
|
|
584
|
+
run(process.argv.slice(2)).catch(e => { console.error(e); process.exit(1); });
|
|
551
585
|
}
|
|
552
586
|
|
|
553
587
|
module.exports = { run, workflow: designBuildWorkflow };
|
package/bin/gsd-t.js
CHANGED
|
@@ -2699,7 +2699,7 @@ if (require.main === module) {
|
|
|
2699
2699
|
break;
|
|
2700
2700
|
case "design-build": {
|
|
2701
2701
|
const orchestrator = require("./design-orchestrator.js");
|
|
2702
|
-
orchestrator.run(args.slice(1));
|
|
2702
|
+
orchestrator.run(args.slice(1)).catch(e => { console.error(e); process.exit(1); });
|
|
2703
2703
|
break;
|
|
2704
2704
|
}
|
|
2705
2705
|
case "scan": {
|
package/bin/orchestrator.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
const fs = require("fs");
|
|
27
27
|
const path = require("path");
|
|
28
|
-
const { execFileSync, spawn: cpSpawn } = require("child_process");
|
|
28
|
+
const { execFileSync, execFile, spawn: cpSpawn } = require("child_process");
|
|
29
29
|
|
|
30
30
|
// ─── ANSI Colors ────────────────────────────────────────────────────────────
|
|
31
31
|
|
|
@@ -136,6 +136,7 @@ class Orchestrator {
|
|
|
136
136
|
case "--skip-measure": opts.skipMeasure = true; break;
|
|
137
137
|
case "--clean": opts.clean = true; break;
|
|
138
138
|
case "--verbose": case "-v": opts.verbose = true; break;
|
|
139
|
+
case "--parallel": opts.parallel = parseInt(argv[++i], 10) || 3; break;
|
|
139
140
|
case "--help":
|
|
140
141
|
case "-h":
|
|
141
142
|
if (this.wf.showUsage) this.wf.showUsage();
|
|
@@ -162,7 +163,8 @@ ${BOLD}Options:${RESET}
|
|
|
162
163
|
--review-port <N> Review server port (default: ${this.wf.defaults?.reviewPort || 3456})
|
|
163
164
|
--timeout <sec> Claude timeout per phase in seconds (default: 600)
|
|
164
165
|
--skip-measure Skip automated measurement (human-review only)
|
|
165
|
-
--clean
|
|
166
|
+
--clean Clear all artifacts from previous runs + delete build output
|
|
167
|
+
--parallel <N> Run N items concurrently (default: 1, recommended: 3)
|
|
166
168
|
--verbose, -v Show Claude's tool calls and prompts in terminal
|
|
167
169
|
--help Show this help
|
|
168
170
|
|
|
@@ -237,6 +239,71 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
|
|
|
237
239
|
|
|
238
240
|
// ─── Server Management ───────────────────────────────────────────────
|
|
239
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Async Claude spawn — returns a Promise. Used for parallel execution.
|
|
244
|
+
*/
|
|
245
|
+
spawnClaudeAsync(projectDir, prompt, timeout, opts = {}) {
|
|
246
|
+
const start = Date.now();
|
|
247
|
+
const verbose = this._verbose;
|
|
248
|
+
|
|
249
|
+
const args = ["-p", "--dangerously-skip-permissions", "--output-format", "stream-json"];
|
|
250
|
+
if (verbose) args.push("--verbose");
|
|
251
|
+
args.push(prompt);
|
|
252
|
+
|
|
253
|
+
if (verbose && opts.label) {
|
|
254
|
+
const logDir = path.join(this.getReviewDir(projectDir), "build-logs");
|
|
255
|
+
ensureDir(logDir);
|
|
256
|
+
fs.writeFileSync(
|
|
257
|
+
path.join(logDir, `${opts.label}-prompt.txt`),
|
|
258
|
+
`--- Prompt (${new Date().toISOString()}) ---\nTimeout: ${(timeout || 120_000) / 1000}s\nCWD: ${projectDir}\n\n${prompt}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return new Promise((resolve) => {
|
|
263
|
+
const child = execFile("claude", args, {
|
|
264
|
+
encoding: "utf8",
|
|
265
|
+
timeout: timeout || 120_000,
|
|
266
|
+
cwd: projectDir,
|
|
267
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
268
|
+
}, (err, stdout, stderr) => {
|
|
269
|
+
const raw = err ? ((err.stdout || "") + (err.stderr || "")) : (stdout || "");
|
|
270
|
+
const output = this._parseStreamJson(raw, false);
|
|
271
|
+
const exitCode = err ? (err.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" ? 1 : (err.killed ? 143 : (err.code || 1))) : 0;
|
|
272
|
+
const duration = Math.round((Date.now() - start) / 1000);
|
|
273
|
+
|
|
274
|
+
if (verbose) {
|
|
275
|
+
dim(` ${opts.label || "claude"}: exit=${exitCode}, ${duration}s, ${output.length} chars`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
resolve({ output, exitCode, duration });
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Run tasks with concurrency limit. Returns results in same order as tasks.
|
|
285
|
+
* @param {Array<Function>} taskFns — array of () => Promise<result>
|
|
286
|
+
* @param {number} concurrency — max concurrent tasks
|
|
287
|
+
*/
|
|
288
|
+
async _runWithConcurrency(taskFns, concurrency) {
|
|
289
|
+
const results = new Array(taskFns.length).fill(null);
|
|
290
|
+
let nextIdx = 0;
|
|
291
|
+
|
|
292
|
+
async function runNext() {
|
|
293
|
+
while (nextIdx < taskFns.length) {
|
|
294
|
+
const idx = nextIdx++;
|
|
295
|
+
results[idx] = await taskFns[idx]();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const workers = [];
|
|
300
|
+
for (let i = 0; i < Math.min(concurrency, taskFns.length); i++) {
|
|
301
|
+
workers.push(runNext());
|
|
302
|
+
}
|
|
303
|
+
await Promise.all(workers);
|
|
304
|
+
return results;
|
|
305
|
+
}
|
|
306
|
+
|
|
240
307
|
_parseStreamJson(raw, verbose) {
|
|
241
308
|
// stream-json format: one JSON object per line
|
|
242
309
|
// We want assistant text content and tool use visibility
|
|
@@ -554,7 +621,7 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
|
|
|
554
621
|
|
|
555
622
|
// ─── Main Pipeline ──────────────────────────────────────────────────
|
|
556
623
|
|
|
557
|
-
run(argv) {
|
|
624
|
+
async run(argv) {
|
|
558
625
|
const opts = this.wf.parseArgs
|
|
559
626
|
? this.wf.parseArgs(argv, this.parseBaseArgs.bind(this))
|
|
560
627
|
: this.parseBaseArgs(argv || []);
|
|
@@ -597,6 +664,29 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
|
|
|
597
664
|
}
|
|
598
665
|
} else {
|
|
599
666
|
state = this._createState();
|
|
667
|
+
|
|
668
|
+
// Clean all orchestrator artifacts on fresh start (not --resume)
|
|
669
|
+
if (opts.clean) {
|
|
670
|
+
const reviewDir = this.getReviewDir(projectDir);
|
|
671
|
+
const dirsToClean = ["auto-review", "build-logs", "queue", "feedback"];
|
|
672
|
+
let cleaned = 0;
|
|
673
|
+
for (const dir of dirsToClean) {
|
|
674
|
+
const full = path.join(reviewDir, dir);
|
|
675
|
+
if (fs.existsSync(full)) {
|
|
676
|
+
for (const f of fs.readdirSync(full)) {
|
|
677
|
+
try { fs.unlinkSync(path.join(full, f)); cleaned++; } catch { /* ignore */ }
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
// Remove signal and state files
|
|
682
|
+
for (const f of ["review-complete.json", "orchestrator-state.json", "shutdown.json"]) {
|
|
683
|
+
const full = path.join(reviewDir, f);
|
|
684
|
+
if (fs.existsSync(full)) {
|
|
685
|
+
try { fs.unlinkSync(full); cleaned++; } catch { /* ignore */ }
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (cleaned > 0) info(`--clean: removed ${cleaned} stale artifact(s)`);
|
|
689
|
+
}
|
|
600
690
|
}
|
|
601
691
|
|
|
602
692
|
// 4. Start servers
|
|
@@ -691,24 +781,30 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
|
|
|
691
781
|
let measurements = {};
|
|
692
782
|
|
|
693
783
|
// ── Per-item pipeline: build ONE → review ONE → fix if needed ──
|
|
694
|
-
//
|
|
695
|
-
//
|
|
784
|
+
// Each item is independent: 1 contract + 1 source = tiny context.
|
|
785
|
+
// With --parallel N, runs N items concurrently.
|
|
696
786
|
if (this.wf.buildSingleItemPrompt && this.wf.buildSingleItemReviewPrompt) {
|
|
697
|
-
|
|
787
|
+
const concurrency = opts.parallel || 1;
|
|
788
|
+
if (concurrency > 1) {
|
|
789
|
+
log(`\n${CYAN} ⚙${RESET} Building and reviewing ${items.length} ${phase} (${concurrency} parallel)...`);
|
|
790
|
+
} else {
|
|
791
|
+
log(`\n${CYAN} ⚙${RESET} Building and reviewing ${items.length} ${phase} one at a time...`);
|
|
792
|
+
}
|
|
698
793
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
794
|
+
// Each task: build → review → fix loop for one item
|
|
795
|
+
const processItem = async (item, idx) => {
|
|
796
|
+
const label = `[${idx + 1}/${items.length}] ${item.componentName}`;
|
|
797
|
+
log(`\n ${BOLD}${label}${RESET}`);
|
|
702
798
|
|
|
703
|
-
// Build
|
|
799
|
+
// Build
|
|
704
800
|
const buildPrompt = this.wf.buildSingleItemPrompt(phase, item, prevResults, projectDir);
|
|
705
801
|
dim(` Building...`);
|
|
706
|
-
const buildResult = this.
|
|
802
|
+
const buildResult = await this.spawnClaudeAsync(projectDir, buildPrompt, perItemTimeout, { label: `${phase}-build-${item.id}` });
|
|
707
803
|
|
|
708
804
|
if (buildResult.exitCode === 0) {
|
|
709
|
-
success(`
|
|
805
|
+
success(` ${item.componentName}: built (${buildResult.duration}s)`);
|
|
710
806
|
} else {
|
|
711
|
-
warn(`
|
|
807
|
+
warn(` ${item.componentName}: build exit ${buildResult.exitCode} (${buildResult.duration}s)`);
|
|
712
808
|
}
|
|
713
809
|
|
|
714
810
|
fs.writeFileSync(
|
|
@@ -716,12 +812,12 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
|
|
|
716
812
|
`Exit code: ${buildResult.exitCode}\nDuration: ${buildResult.duration}s\n\n--- OUTPUT ---\n${buildResult.output.slice(0, 5000)}`
|
|
717
813
|
);
|
|
718
814
|
|
|
719
|
-
// Review
|
|
815
|
+
// Review cycles
|
|
720
816
|
let itemClean = false;
|
|
721
817
|
for (let cycle = 1; cycle <= maxAutoReviewCycles && !itemClean; cycle++) {
|
|
722
|
-
dim(`
|
|
818
|
+
dim(` ${item.componentName}: review c${cycle}...`);
|
|
723
819
|
const reviewPrompt = this.wf.buildSingleItemReviewPrompt(phase, item, {}, projectDir, { devPort, reviewPort });
|
|
724
|
-
const reviewResult = this.
|
|
820
|
+
const reviewResult = await this.spawnClaudeAsync(projectDir, reviewPrompt, perItemTimeout, { label: `${phase}-review-${item.id}-c${cycle}` });
|
|
725
821
|
|
|
726
822
|
const isCrash = reviewResult.exitCode !== 0 && reviewResult.duration < 10;
|
|
727
823
|
const isKilled = [143, 137].includes(reviewResult.exitCode);
|
|
@@ -730,7 +826,7 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
|
|
|
730
826
|
let itemIssues = [];
|
|
731
827
|
if (isCrash || isKilled || isEmptyFail) {
|
|
732
828
|
const reason = isCrash ? "crashed" : isKilled ? "killed/timed out" : "failed with no output";
|
|
733
|
-
warn(`
|
|
829
|
+
warn(` ${item.componentName}: reviewer ${reason} (${reviewResult.duration}s)`);
|
|
734
830
|
itemIssues = [{ component: item.componentName, severity: "critical", description: `Reviewer ${reason}` }];
|
|
735
831
|
} else {
|
|
736
832
|
itemIssues = this.wf.parseReviewResult
|
|
@@ -740,28 +836,34 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
|
|
|
740
836
|
|
|
741
837
|
if (itemIssues.length === 0) {
|
|
742
838
|
itemClean = true;
|
|
743
|
-
success(`
|
|
839
|
+
success(` ${item.componentName}: clean (${reviewResult.duration}s)`);
|
|
744
840
|
} else {
|
|
745
|
-
warn(` ${itemIssues.length} issue(s)
|
|
841
|
+
warn(` ${item.componentName}: ${itemIssues.length} issue(s)`);
|
|
746
842
|
for (const issue of itemIssues) {
|
|
747
|
-
dim(` ${issue.description ||
|
|
843
|
+
dim(` ${issue.description || "issue"} [${issue.severity || "medium"}]`);
|
|
748
844
|
}
|
|
749
845
|
|
|
750
846
|
if (cycle < maxAutoReviewCycles) {
|
|
751
|
-
// Fix this one item
|
|
752
847
|
const fixPrompt = this.wf.buildAutoFixPrompt
|
|
753
848
|
? this.wf.buildAutoFixPrompt(phase, itemIssues, [item], projectDir)
|
|
754
849
|
: this._defaultAutoFixPrompt(phase, itemIssues);
|
|
755
|
-
dim(`
|
|
756
|
-
const fixResult = this.
|
|
757
|
-
if (fixResult.exitCode === 0) success(`
|
|
758
|
-
else warn(`
|
|
850
|
+
dim(` ${item.componentName}: fixing...`);
|
|
851
|
+
const fixResult = await this.spawnClaudeAsync(projectDir, fixPrompt, perItemTimeout, { label: `${phase}-fix-${item.id}-c${cycle}` });
|
|
852
|
+
if (fixResult.exitCode === 0) success(` ${item.componentName}: fixed (${fixResult.duration}s)`);
|
|
853
|
+
else warn(` ${item.componentName}: fix exit ${fixResult.exitCode}`);
|
|
759
854
|
}
|
|
760
855
|
}
|
|
761
856
|
}
|
|
762
857
|
|
|
763
|
-
|
|
764
|
-
}
|
|
858
|
+
return item.sourcePath || (this.wf.guessPaths ? this.wf.guessPaths(phase, item) : "");
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
// Run with concurrency
|
|
862
|
+
const taskFns = items.map((item, idx) => () => processItem(item, idx));
|
|
863
|
+
const startTime = Date.now();
|
|
864
|
+
builtPaths = await this._runWithConcurrency(taskFns, concurrency);
|
|
865
|
+
const totalSec = Math.round((Date.now() - startTime) / 1000);
|
|
866
|
+
success(`All ${items.length} ${phase} processed in ${totalSec}s (${concurrency}x parallel)`);
|
|
765
867
|
|
|
766
868
|
// Measure ALL at once (one Playwright run after all items built)
|
|
767
869
|
if (!skipMeasure && this.wf.measure) {
|
|
@@ -33,7 +33,8 @@ Pass any of these as `$ARGUMENTS`:
|
|
|
33
33
|
| `--review-port <N>` | Review server port (default: 3456) |
|
|
34
34
|
| `--timeout <sec>` | Claude timeout per tier in seconds (default: 600) |
|
|
35
35
|
| `--skip-measure` | Skip Playwright measurement (human-review only) |
|
|
36
|
-
| `--clean` |
|
|
36
|
+
| `--clean` | Clear all stale artifacts + delete build output before each phase |
|
|
37
|
+
| `--parallel <N>` | Run N items concurrently (default: 1, recommended: 3) |
|
|
37
38
|
| `--verbose`, `-v` | Show Claude's tool calls and prompts in terminal |
|
|
38
39
|
|
|
39
40
|
## Prerequisites
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.73.11",
|
|
4
4
|
"description": "GSD-T: Contract-Driven Development for Claude Code — 56 slash commands with headless CI/CD mode, graph-powered code analysis, real-time agent dashboard, execution intelligence, 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",
|