@tekyzinc/gsd-t 5.11.17 → 5.11.19
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 +49 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/templates/workflows/gsd-t-scan.workflow.js +78 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [5.11.19] - 2026-08-10
|
|
6
|
+
|
|
7
|
+
### Changed — a scan that lost areas now STOPS instead of writing the report
|
|
8
|
+
|
|
9
|
+
It already warned and stamped the register PARTIAL. It kept going anyway, so the
|
|
10
|
+
register got written, read, and acted on. On HiloAviation the two lost areas
|
|
11
|
+
were repositories and data-access — the areas with the most to find. A report
|
|
12
|
+
that under-counts the debt while looking finished is worse than no report.
|
|
13
|
+
|
|
14
|
+
The stop happens **before synthesis and before any document is written**, names
|
|
15
|
+
every area that was missed, and returns a failure rather than a quiet success.
|
|
16
|
+
It also says plainly that a failed area is not a clean one.
|
|
17
|
+
|
|
18
|
+
`allowPartial: true` continues deliberately, says so out loud, and the register
|
|
19
|
+
still carries its PARTIAL banner.
|
|
20
|
+
|
|
21
|
+
- `templates/workflows/gsd-t-scan.workflow.js`: the halt + the `allowPartial` option
|
|
22
|
+
- `test/m112-scan-schema-tolerance.test.js`: 4 more tests — the halt precedes writing, names the areas, fails rather than passes, and cannot be bypassed by a truthy-ish value
|
|
23
|
+
|
|
24
|
+
## [5.11.18] - 2026-08-10
|
|
25
|
+
|
|
26
|
+
### Fixed — a scan finding was thrown away over its FORM, not its truth
|
|
27
|
+
|
|
28
|
+
HiloAviation, a 228-slice deep scan: 2 slices failed after 179.6k tokens and 61
|
|
29
|
+
tool calls of real work. The agent's own log shows one call carrying findings
|
|
30
|
+
and the next carrying an empty array — an agent giving up and submitting nothing
|
|
31
|
+
to satisfy the schema. Both refused. The report then read 226 of 228 with no
|
|
32
|
+
sign that two of the densest areas had vanished.
|
|
33
|
+
|
|
34
|
+
Two rules did it, neither of which checks whether a finding is *true*:
|
|
35
|
+
|
|
36
|
+
- **No extra fields.** A finder adding `evidence` or a line number lost the
|
|
37
|
+
entire call, not the extra field.
|
|
38
|
+
- **Case-exact word lists.** "High" refused where "HIGH" was demanded, and
|
|
39
|
+
"High" refused where "high" was demanded — in the same finding.
|
|
40
|
+
|
|
41
|
+
Large projects hit this hardest: the failing slices were data-access and
|
|
42
|
+
repositories, the areas producing the most findings and the most extra context.
|
|
43
|
+
|
|
44
|
+
**Nothing about what a finding must contain is loosened.** Title, severity,
|
|
45
|
+
area, files, detail and recommendation are still required.
|
|
46
|
+
|
|
47
|
+
Widening a word list breaks exact-match comparisons, so three were fixed in the
|
|
48
|
+
same pass — most importantly the false-positive check, which would otherwise
|
|
49
|
+
have KEPT a finding the verifier had just rejected.
|
|
50
|
+
|
|
51
|
+
- `templates/workflows/gsd-t-scan.workflow.js`: agent-authored schemas accept extra keys and any casing; severity normalised once at the merge point; verdict and document-status comparisons made case-insensitive
|
|
52
|
+
- `test/m112-scan-schema-tolerance.test.js`: 8 tests, including that the required-field list was not quietly shortened
|
|
53
|
+
|
|
5
54
|
## [5.11.17] - 2026-08-10
|
|
6
55
|
|
|
7
56
|
### Added — `/concise`, rewrite the last reply short on request
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GSD-T: Contract-Driven Development for Claude Code
|
|
2
2
|
|
|
3
|
-
**v5.11.
|
|
3
|
+
**v5.11.19** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
|
|
4
4
|
|
|
5
5
|
**Eliminates context rot** — task-level fresh dispatch (one subagent per task, ~10-20% context each) means compaction never triggers.
|
|
6
6
|
**Compaction-proof debug loops** — `gsd-t headless --debug-loop` runs test-fix-retest cycles as separate `claude -p` sessions. A JSONL debug ledger persists all hypothesis/fix/learning history across fresh sessions. Anti-repetition preamble injection prevents retrying failed hypotheses. Escalation tiers (sonnet → opus → human) and a hard iteration ceiling enforced externally.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "5.11.
|
|
3
|
+
"version": "5.11.19",
|
|
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",
|
|
@@ -56,6 +56,9 @@ const maxSlicesOverride = _args.maxSlicesHint || null; // optional power-user ce
|
|
|
56
56
|
// M94-D6: graph wiring — "wired" (default) or "disabled" (no-graph baseline for AC-4).
|
|
57
57
|
// [RULE] scan-injects-structural-slice / [RULE] no-graph-baseline-proven-graph-free
|
|
58
58
|
const graphMode = (_args.graphMode === "disabled") ? "disabled" : "wired";
|
|
59
|
+
// A scan that lost areas STOPS rather than writing a register that under-counts
|
|
60
|
+
// the debt while looking finished. Set true to accept an incomplete scan.
|
|
61
|
+
const allowPartial = _args.allowPartial === true;
|
|
59
62
|
|
|
60
63
|
// VOLUME-DERIVED CAP — a RUNAWAY BACKSTOP, not the target count. The probe decides
|
|
61
64
|
// the actual slice count by cohesive sub-domain WITHIN this cap; the cap only fires
|
|
@@ -112,7 +115,7 @@ const PROBE_SCHEMA = {
|
|
|
112
115
|
properties: {
|
|
113
116
|
key: { type: "string" },
|
|
114
117
|
paths: { type: "array", items: { type: "string" } },
|
|
115
|
-
dimension: { type: "string", enum: ["architecture", "business-rules", "security", "quality", "contracts", "feature-domain", "data-layer", "api-surface", "testing"] },
|
|
118
|
+
dimension: { type: "string", enum: ["architecture", "ARCHITECTURE", "Architecture", "business-rules", "BUSINESS-RULES", "Business-rules", "security", "SECURITY", "Security", "quality", "QUALITY", "Quality", "contracts", "CONTRACTS", "Contracts", "feature-domain", "FEATURE-DOMAIN", "Feature-domain", "data-layer", "DATA-LAYER", "Data-layer", "api-surface", "API-SURFACE", "Api-surface", "testing", "TESTING", "Testing"] },
|
|
116
119
|
why: { type: "string" },
|
|
117
120
|
},
|
|
118
121
|
},
|
|
@@ -124,7 +127,8 @@ const PROBE_SCHEMA = {
|
|
|
124
127
|
const FINDER_SCHEMA = {
|
|
125
128
|
type: "object",
|
|
126
129
|
required: ["slice", "findings"],
|
|
127
|
-
|
|
130
|
+
// Agent-authored, so extra keys are allowed here too — see the finding object.
|
|
131
|
+
additionalProperties: true,
|
|
128
132
|
properties: {
|
|
129
133
|
slice: { type: "string" },
|
|
130
134
|
findings: {
|
|
@@ -132,16 +136,21 @@ const FINDER_SCHEMA = {
|
|
|
132
136
|
items: {
|
|
133
137
|
type: "object",
|
|
134
138
|
required: ["title", "severity", "area", "files", "detail", "recommendation"],
|
|
135
|
-
|
|
139
|
+
// Extra keys are ALLOWED. A finder that adds `evidence` or a line number
|
|
140
|
+
// is giving more, not less — and rejecting the whole call for it threw
|
|
141
|
+
// away 179.6k tokens of real findings on two of 228 slices in a large
|
|
142
|
+
// scan (HiloAviation, 2026-08-10). The required keys are still
|
|
143
|
+
// required, so nothing about what a finding must contain is loosened.
|
|
144
|
+
additionalProperties: true,
|
|
136
145
|
properties: {
|
|
137
146
|
title: { type: "string" },
|
|
138
|
-
severity: { type: "string", enum: ["CRITICAL", "HIGH", "MEDIUM", "LOW"] },
|
|
147
|
+
severity: { type: "string", enum: ["CRITICAL", "critical", "Critical", "HIGH", "high", "High", "MEDIUM", "medium", "Medium", "LOW", "low", "Low"] },
|
|
139
148
|
area: { type: "string" },
|
|
140
149
|
files: { type: "array", items: { type: "string" } },
|
|
141
150
|
detail: { type: "string" },
|
|
142
151
|
impact: { type: "string" },
|
|
143
152
|
recommendation: { type: "string" },
|
|
144
|
-
confidence: { type: "string", enum: ["high", "medium", "low"] },
|
|
153
|
+
confidence: { type: "string", enum: ["high", "HIGH", "High", "medium", "MEDIUM", "Medium", "low", "LOW", "Low"] },
|
|
145
154
|
},
|
|
146
155
|
},
|
|
147
156
|
},
|
|
@@ -152,12 +161,13 @@ const FINDER_SCHEMA = {
|
|
|
152
161
|
const VERIFY_SCHEMA = {
|
|
153
162
|
type: "object",
|
|
154
163
|
required: ["confirmed", "verdict"],
|
|
155
|
-
|
|
164
|
+
// Agent-authored — extra context must not cost the whole verdict.
|
|
165
|
+
additionalProperties: true,
|
|
156
166
|
properties: {
|
|
157
167
|
confirmed: { type: "boolean" },
|
|
158
|
-
verdict: { type: "string", enum: ["confirmed", "false-positive", "needs-detail"] },
|
|
168
|
+
verdict: { type: "string", enum: ["confirmed", "CONFIRMED", "Confirmed", "false-positive", "FALSE-POSITIVE", "False-positive", "needs-detail", "NEEDS-DETAIL", "Needs-detail"] },
|
|
159
169
|
note: { type: "string" },
|
|
160
|
-
correctedSeverity: { type: "string", enum: ["CRITICAL", "HIGH", "MEDIUM", "LOW"] },
|
|
170
|
+
correctedSeverity: { type: "string", enum: ["CRITICAL", "critical", "Critical", "HIGH", "high", "High", "MEDIUM", "medium", "Medium", "LOW", "low", "Low"] },
|
|
161
171
|
},
|
|
162
172
|
};
|
|
163
173
|
|
|
@@ -173,7 +183,7 @@ const DOC_RESULT_SCHEMA = {
|
|
|
173
183
|
additionalProperties: false,
|
|
174
184
|
properties: {
|
|
175
185
|
doc: { type: "string" },
|
|
176
|
-
status: { type: "string", enum: ["written", "merged", "skipped", "failed"] },
|
|
186
|
+
status: { type: "string", enum: ["written", "WRITTEN", "Written", "merged", "MERGED", "Merged", "skipped", "SKIPPED", "Skipped", "failed", "FAILED", "Failed"] },
|
|
177
187
|
path: { type: "string" },
|
|
178
188
|
notes: { type: "string" },
|
|
179
189
|
},
|
|
@@ -184,7 +194,7 @@ const RENDER_SCHEMA = {
|
|
|
184
194
|
required: ["status"],
|
|
185
195
|
additionalProperties: false,
|
|
186
196
|
properties: {
|
|
187
|
-
status: { type: "string", enum: ["rendered", "skipped", "failed"] },
|
|
197
|
+
status: { type: "string", enum: ["rendered", "RENDERED", "Rendered", "skipped", "SKIPPED", "Skipped", "failed", "FAILED", "Failed"] },
|
|
188
198
|
outputPath: { type: "string" },
|
|
189
199
|
notes: { type: "string" },
|
|
190
200
|
},
|
|
@@ -699,8 +709,15 @@ async function scanSlice(slice) {
|
|
|
699
709
|
].join("\n"),
|
|
700
710
|
{ label: `verify:${sliceKey}`, phase: "Deep Scan", schema: VERIFY_SCHEMA, model: "sonnet" }
|
|
701
711
|
);
|
|
702
|
-
|
|
703
|
-
|
|
712
|
+
// Compared case-INSENSITIVELY: the schema now accepts "false-positive"
|
|
713
|
+
// in any casing, so an exact match would silently KEEP a finding the
|
|
714
|
+
// verifier had rejected.
|
|
715
|
+
const verdict = String(v && v.verdict || "").toLowerCase();
|
|
716
|
+
if (!v || verdict === "false-positive" || v.confirmed === false) return null;
|
|
717
|
+
// Severity is normalised to the shouted form here, once, so the report
|
|
718
|
+
// reads consistently no matter how a finder typed it.
|
|
719
|
+
const sev = String(v.correctedSeverity || f.severity || "").toUpperCase();
|
|
720
|
+
return { ...f, severity: sev, _verify: verdict };
|
|
704
721
|
} catch (e) {
|
|
705
722
|
return { ...f, _verify: "verify-errored" };
|
|
706
723
|
}
|
|
@@ -726,7 +743,50 @@ const succeededCount = slices.length - failedSlices.length;
|
|
|
726
743
|
const coverageComplete = failedSlices.length === 0;
|
|
727
744
|
const allFindings = sliceResults.filter(Boolean).filter((r) => !r.failed).flatMap((r) => (r.findings || []).map((f) => ({ ...f, slice: r.slice })));
|
|
728
745
|
if (!coverageComplete) {
|
|
729
|
-
log(`⚠ PARTIAL COVERAGE — ${failedSlices.length}/${slices.length} slices failed after retry and produced NO findings: ${failedSlices.join(", ")}
|
|
746
|
+
log(`⚠ PARTIAL COVERAGE — ${failedSlices.length}/${slices.length} slices failed after retry and produced NO findings: ${failedSlices.join(", ")}.`);
|
|
747
|
+
|
|
748
|
+
// HALT. A scan whose densest areas dropped out is not a scan with a caveat —
|
|
749
|
+
// it is a report that under-counts the debt while looking finished. On
|
|
750
|
+
// HiloAviation two of 228 slices failed, and the two were repositories and
|
|
751
|
+
// data-access: the areas with the most to find. Warning and continuing meant
|
|
752
|
+
// the register got written, read, and acted on as if complete.
|
|
753
|
+
//
|
|
754
|
+
// Writing the documents is the point of no return, so the stop goes HERE,
|
|
755
|
+
// before them — not after, where the flawed register already exists.
|
|
756
|
+
//
|
|
757
|
+
// Not a fallback and not a gate on findings: the run stops and says exactly
|
|
758
|
+
// which areas are missing. `allowPartial: true` continues deliberately, and
|
|
759
|
+
// the register still carries its PARTIAL banner in that case.
|
|
760
|
+
if (!allowPartial) {
|
|
761
|
+
const lines = [
|
|
762
|
+
"",
|
|
763
|
+
"════════════════════════════════════════════════════════════════",
|
|
764
|
+
` SCAN HALTED — ${failedSlices.length} of ${slices.length} areas were never scanned`,
|
|
765
|
+
"════════════════════════════════════════════════════════════════",
|
|
766
|
+
"",
|
|
767
|
+
" Not scanned:",
|
|
768
|
+
...failedSlices.map((k) => ` · ${k}`),
|
|
769
|
+
"",
|
|
770
|
+
" These areas found nothing because they FAILED, not because they",
|
|
771
|
+
" are clean. Their problems are missing from the register.",
|
|
772
|
+
"",
|
|
773
|
+
" Nothing has been written. Re-run to scan only the failed areas,",
|
|
774
|
+
" or re-run with allowPartial: true to accept an incomplete scan",
|
|
775
|
+
" (the register will say PARTIAL on its first line).",
|
|
776
|
+
"════════════════════════════════════════════════════════════════",
|
|
777
|
+
"",
|
|
778
|
+
].join("\n");
|
|
779
|
+
log(lines);
|
|
780
|
+
return {
|
|
781
|
+
ok: false,
|
|
782
|
+
halted: "partial-coverage",
|
|
783
|
+
slicesTotal: slices.length,
|
|
784
|
+
slicesFailed: failedSlices,
|
|
785
|
+
findingsDiscarded: allFindings.length,
|
|
786
|
+
message: `${failedSlices.length} of ${slices.length} areas were never scanned. Nothing written.`,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
log("→ allowPartial: true — continuing with an INCOMPLETE scan, as asked.");
|
|
730
790
|
}
|
|
731
791
|
log(`deep scan complete: ${allFindings.length} verified findings across ${succeededCount}/${slices.length} slices${coverageComplete ? " (full coverage)" : " (PARTIAL)"}`);
|
|
732
792
|
|
|
@@ -1131,8 +1191,11 @@ const docResults = await parallel(
|
|
|
1131
1191
|
}
|
|
1132
1192
|
})
|
|
1133
1193
|
);
|
|
1134
|
-
|
|
1135
|
-
|
|
1194
|
+
// Case-insensitive: the status word-list accepts any casing, so an exact match
|
|
1195
|
+
// would count a written document as neither written nor failed.
|
|
1196
|
+
const _status = (r) => String(r && r.status || "").toLowerCase();
|
|
1197
|
+
const docsOk = docResults.filter(Boolean).filter((r) => _status(r) === "written" || _status(r) === "merged");
|
|
1198
|
+
const docsFailed = docResults.filter(Boolean).filter((r) => _status(r) === "failed");
|
|
1136
1199
|
log(`document phase: ${docsOk.length}/${docTargets.length} written/merged${docsFailed.length ? `; ${docsFailed.length} failed (non-fatal): ${docsFailed.map((d) => d.doc).join(", ")}` : ""}`);
|
|
1137
1200
|
|
|
1138
1201
|
// ─── Plain-English phase (M78) ───────────────────────────────────────────────
|