@opengsd/gsd-core 1.5.0-rc.2 → 1.5.0-rc.4
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/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-writer.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-mempalace-curator.md +47 -0
- package/agents/gsd-nyquist-auditor.md +1 -0
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-roadmapper.md +55 -2
- package/agents/gsd-security-auditor.md +1 -0
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +13 -2
- package/bin/install.js +61 -64
- package/commands/gsd/mempalace-capture.md +71 -0
- package/commands/gsd/mempalace-recall.md +102 -0
- package/commands/gsd/ns-context.md +4 -2
- package/commands/gsd/progress.md +2 -1
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +277 -95
- package/gsd-core/bin/lib/active-workstream-store.cjs +6 -0
- package/gsd-core/bin/lib/capability-activation.cjs +86 -0
- package/gsd-core/bin/lib/capability-registry.cjs +1468 -11
- package/gsd-core/bin/lib/capability-state.cjs +128 -21
- package/gsd-core/bin/lib/capability-writer.cjs +354 -0
- package/gsd-core/bin/lib/check-command-router.cjs +328 -1
- package/gsd-core/bin/lib/clusters.cjs +2 -0
- package/gsd-core/bin/lib/command-roster.cjs +19 -0
- package/gsd-core/bin/lib/commands.cjs +33 -10
- package/gsd-core/bin/lib/config-loader.cjs +7 -8
- package/gsd-core/bin/lib/config-schema.cjs +32 -3
- package/gsd-core/bin/lib/config.cjs +81 -26
- package/gsd-core/bin/lib/core.cjs +5 -2
- package/gsd-core/bin/lib/edge-probe.cjs +25 -2
- package/gsd-core/bin/lib/frontmatter.cjs +53 -1
- package/gsd-core/bin/lib/git-base-branch.cjs +194 -0
- package/gsd-core/bin/lib/init.cjs +36 -11
- package/gsd-core/bin/lib/install-profiles.cjs +57 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +157 -16
- package/gsd-core/bin/lib/model-resolver.cjs +47 -5
- package/gsd-core/bin/lib/phase.cjs +99 -23
- package/gsd-core/bin/lib/plan-drift-guard.cjs +117 -0
- package/gsd-core/bin/lib/probe-core.cjs +117 -1
- package/gsd-core/bin/lib/profile-output.cjs +45 -4
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +138 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +13 -3
- package/gsd-core/bin/lib/roadmap.cjs +97 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +1946 -0
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +54 -30
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +27 -19
- package/gsd-core/bin/lib/runtime-homes.cjs +26 -20
- package/gsd-core/bin/lib/state-command-router.cjs +15 -3
- package/gsd-core/bin/lib/state-document.cjs +46 -1
- package/gsd-core/bin/lib/state.cjs +461 -94
- package/gsd-core/bin/lib/verify.cjs +92 -8
- package/gsd-core/bin/lib/worktree-safety.cjs +2 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -2
- package/gsd-core/bin/shared/config-schema.manifest.json +0 -18
- package/gsd-core/bin/shared/model-catalog.json +1 -0
- package/gsd-core/references/edge-probe.md +11 -0
- package/gsd-core/references/loop-hook-dispatch.md +61 -0
- package/gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json +14 -0
- package/gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json +4 -0
- package/gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json +32 -0
- package/gsd-core/references/prohibition-probe.md +248 -0
- package/gsd-core/templates/config.json +1 -1
- package/gsd-core/templates/spec.md +14 -0
- package/gsd-core/workflows/audit-milestone.md +5 -3
- package/gsd-core/workflows/autonomous.md +10 -5
- package/gsd-core/workflows/code-review-fix.md +9 -7
- package/gsd-core/workflows/code-review.md +8 -6
- package/gsd-core/workflows/complete-milestone.md +1 -5
- package/gsd-core/workflows/discuss-phase.md +14 -0
- package/gsd-core/workflows/execute-phase.md +86 -146
- package/gsd-core/workflows/execute-plan.md +21 -6
- package/gsd-core/workflows/help/modes/full.md +7 -1
- package/gsd-core/workflows/new-project.md +3 -3
- package/gsd-core/workflows/next.md +50 -2
- package/gsd-core/workflows/pause-work.md +7 -1
- package/gsd-core/workflows/plan-phase.md +91 -221
- package/gsd-core/workflows/plan-review-convergence.md +14 -4
- package/gsd-core/workflows/pr-branch.md +4 -2
- package/gsd-core/workflows/profile-user.md +3 -1
- package/gsd-core/workflows/progress.md +58 -1
- package/gsd-core/workflows/quick.md +12 -8
- package/gsd-core/workflows/resume-project.md +17 -1
- package/gsd-core/workflows/review.md +19 -2
- package/gsd-core/workflows/secure-phase.md +4 -2
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings.md +27 -1
- package/gsd-core/workflows/ship.md +58 -5
- package/gsd-core/workflows/spec-phase.md +75 -0
- package/gsd-core/workflows/validate-phase.md +4 -2
- package/gsd-core/workflows/verify-phase.md +14 -4
- package/gsd-core/workflows/verify-work.md +27 -11
- package/hooks/dist/gsd-ensure-canonical-path.js +305 -0
- package/hooks/dist/gsd-statusline.js +1 -1
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-ensure-canonical-path.js +305 -0
- package/hooks/gsd-statusline.js +1 -1
- package/hooks/hooks.json +1 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +5 -4
- package/scripts/affected-tests-lib.cjs +16 -4
- package/scripts/build-hooks.js +7 -0
- package/scripts/changeset/new.cjs +17 -3
- package/scripts/fix-slash-commands.cjs +15 -3
- package/scripts/gen-capability-registry.cjs +373 -49
- package/scripts/gen-inventory-manifest.cjs +1 -4
- package/scripts/gen-loop-host-contract.cjs +55 -0
- package/scripts/issue-version-gate.cjs +140 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +327 -0
- package/scripts/lint-allow-test-rule-refs.cjs +162 -0
- package/scripts/lint-test-file-count.allowlist.json +14 -0
- package/scripts/mutation-matrix.cjs +108 -7
- package/scripts/pr-target-policy.cjs +63 -0
- package/scripts/release-tarball-smoke.cjs +7 -1
- package/scripts/research-profiles.cjs +5 -5
- package/scripts/run-tests.cjs +178 -17
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* lint-allow-test-rule-refs.cjs — enforce that NEW `allow-test-rule:` exemption
|
|
6
|
+
* comments carry a tracking-issue reference.
|
|
7
|
+
*
|
|
8
|
+
* ## Why
|
|
9
|
+
*
|
|
10
|
+
* `allow-test-rule:` is an inline comment that disables the `no-source-grep`
|
|
11
|
+
* ESLint rule for a whole test file. Today many such comments exist with no
|
|
12
|
+
* issue reference, making it impossible to audit or revisit them. Per ADR-456
|
|
13
|
+
* (docs/adr/456-test-rigor-architecture.md) every NEW exemption must carry a
|
|
14
|
+
* `#NNN` issue reference or an https:// URL so the decision is traceable.
|
|
15
|
+
*
|
|
16
|
+
* ## What "compliant" means
|
|
17
|
+
*
|
|
18
|
+
* A compliant `allow-test-rule:` comment is one whose reason text (everything
|
|
19
|
+
* after the colon) contains either:
|
|
20
|
+
* - a `#\d+` token (e.g. `// allow-test-rule: see #1234`)
|
|
21
|
+
* - an https?:// URL
|
|
22
|
+
*
|
|
23
|
+
* Any other comment is an OFFENDER.
|
|
24
|
+
*
|
|
25
|
+
* ## Grandfathering
|
|
26
|
+
*
|
|
27
|
+
* All pre-existing untracked exemptions are recorded in
|
|
28
|
+
* scripts/lint-allow-test-rule-refs.allowlist.json (seeded at gate introduction
|
|
29
|
+
* time). The identity ratchet (scripts/lib/allowlist-ratchet.cjs) means:
|
|
30
|
+
* - A NEW non-compliant comment not in the allowlist → gate fails.
|
|
31
|
+
* - A previously-offending comment that is now compliant → allowlist entry is
|
|
32
|
+
* STALE and must be pruned (ratchet-down; the baseline only ever shrinks).
|
|
33
|
+
*
|
|
34
|
+
* ## Offender identifiers
|
|
35
|
+
*
|
|
36
|
+
* Identifiers are stable cross-rename-safe strings of the form:
|
|
37
|
+
* `<repo-relative-path> :: <trimmed-reason>`
|
|
38
|
+
*
|
|
39
|
+
* e.g. `tests/foo.test.cjs :: source-text-is-the-product`
|
|
40
|
+
*
|
|
41
|
+
* If a file has multiple non-compliant comments with the SAME reason text, only
|
|
42
|
+
* one identifier is recorded (deduped via Set).
|
|
43
|
+
*
|
|
44
|
+
* See docs/adr/456-test-rigor-architecture.md for the full policy.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
const fs = require('fs');
|
|
48
|
+
const path = require('path');
|
|
49
|
+
const { assertWithinAllowlist } = require('./lib/allowlist-ratchet.cjs');
|
|
50
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
51
|
+
|
|
52
|
+
const ROOT = path.join(__dirname, '..');
|
|
53
|
+
const TESTS_DIR = process.env.GSD_LINT_ALLOW_TEST_RULE_TESTS_DIR || path.join(ROOT, 'tests');
|
|
54
|
+
const ALLOWLIST_PATH =
|
|
55
|
+
process.env.GSD_LINT_ALLOW_TEST_RULE_ALLOWLIST ||
|
|
56
|
+
path.join(__dirname, 'lint-allow-test-rule-refs.allowlist.json');
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extracts the reason text after `allow-test-rule:` from a single line of source
|
|
60
|
+
* text in any comment form that the no-source-grep ESLint rule honours.
|
|
61
|
+
*
|
|
62
|
+
* The ESLint rule tests `c.value` (AST comment node value, delimiters stripped)
|
|
63
|
+
* with /allow-test-rule:\s*\S/, which fires on BOTH:
|
|
64
|
+
* // allow-test-rule: <reason> (line comment)
|
|
65
|
+
* /* allow-test-rule: <reason> * / (block comment, single-line)
|
|
66
|
+
*
|
|
67
|
+
* By scanning line-by-line and extracting everything after `allow-test-rule:` on
|
|
68
|
+
* each line, we cover both forms without a cross-line regex (which was previously
|
|
69
|
+
* matching arbitrary `/* ... * /` pairs spanning hundreds of lines, causing false
|
|
70
|
+
* positives).
|
|
71
|
+
*
|
|
72
|
+
* The trailing `*\/` and whitespace are stripped so block-comment closers don't
|
|
73
|
+
* bleed into the extracted reason.
|
|
74
|
+
*/
|
|
75
|
+
const ALLOW_TEST_RULE_LINE_RE = /allow-test-rule:\s*(.+)/;
|
|
76
|
+
/** Matches a compliant issue reference or URL */
|
|
77
|
+
const ISSUE_REF_RE = /#\d+|https?:\/\//;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Recursively collect offender identifiers from all *.test.cjs files under dir.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} dir absolute path to scan
|
|
83
|
+
* @returns {string[]} sorted, deduped list of `<relpath> :: <reason>` strings
|
|
84
|
+
*/
|
|
85
|
+
function collectOffenders(dir) {
|
|
86
|
+
const offenders = new Set();
|
|
87
|
+
|
|
88
|
+
function scan(current) {
|
|
89
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
90
|
+
const full = path.join(current, entry.name);
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
scan(full);
|
|
93
|
+
} else if (entry.isFile() && entry.name.endsWith('.test.cjs')) {
|
|
94
|
+
const relpath = path.relative(ROOT, full).split(path.sep).join('/');
|
|
95
|
+
let content;
|
|
96
|
+
try {
|
|
97
|
+
content = fs.readFileSync(full, 'utf8');
|
|
98
|
+
} catch {
|
|
99
|
+
// skip unreadable files (e.g. binary)
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// Scan line-by-line. By testing each line for `allow-test-rule:` we
|
|
103
|
+
// cover BOTH comment forms without a cross-line regex:
|
|
104
|
+
// // allow-test-rule: <reason> ← line comment
|
|
105
|
+
// /* allow-test-rule: <reason> */ ← single-line block comment
|
|
106
|
+
//
|
|
107
|
+
// For each matching line we extract the reason (everything after the
|
|
108
|
+
// colon), then strip any trailing block-comment closer `*/` and
|
|
109
|
+
// whitespace so the identifier stays clean.
|
|
110
|
+
for (const line of content.split('\n')) {
|
|
111
|
+
const m = ALLOW_TEST_RULE_LINE_RE.exec(line);
|
|
112
|
+
if (!m) continue;
|
|
113
|
+
// Strip trailing block-comment closer and whitespace if present
|
|
114
|
+
const reason = m[1].replace(/\s*\*\/\s*$/, '').trim();
|
|
115
|
+
if (!reason) continue;
|
|
116
|
+
if (ISSUE_REF_RE.test(reason)) continue; // compliant — skip
|
|
117
|
+
offenders.add(`${relpath} :: ${reason}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
scan(dir);
|
|
124
|
+
return [...offenders].sort();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function main() {
|
|
128
|
+
const args = process.argv.slice(2);
|
|
129
|
+
const unknown = args.filter((a) => a !== '--help');
|
|
130
|
+
if (unknown.length > 0) {
|
|
131
|
+
throw new ExitError(2, `lint-allow-test-rule-refs: unknown argument(s): ${unknown.join(', ')}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const current = collectOffenders(TESTS_DIR);
|
|
135
|
+
const known = JSON.parse(fs.readFileSync(ALLOWLIST_PATH, 'utf8'));
|
|
136
|
+
|
|
137
|
+
const failures = [];
|
|
138
|
+
const { novel } = assertWithinAllowlist({
|
|
139
|
+
label: 'allow-test-rule-refs',
|
|
140
|
+
current,
|
|
141
|
+
known,
|
|
142
|
+
fail: (msg) => failures.push(msg),
|
|
143
|
+
pruneHint: 'edit scripts/lint-allow-test-rule-refs.allowlist.json',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (failures.length > 0) {
|
|
147
|
+
for (const msg of failures) process.stderr.write(`${msg}\n`);
|
|
148
|
+
if (novel.length > 0) {
|
|
149
|
+
process.stderr.write(
|
|
150
|
+
'\nNew allow-test-rule exemption without an issue ref — add `see #NNN` per ADR-456' +
|
|
151
|
+
' (docs/adr/456-test-rigor-architecture.md).\n'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
throw new ExitError(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(
|
|
158
|
+
`ok lint-allow-test-rule-refs: ${current.length} grandfathered exemption(s) tracked, no novel untracked offenders`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
runMain(main);
|
|
@@ -160,6 +160,20 @@
|
|
|
160
160
|
"edge-probe.test.cjs"
|
|
161
161
|
],
|
|
162
162
|
"issue": "550"
|
|
163
|
+
},
|
|
164
|
+
"federated-config": {
|
|
165
|
+
"files": [
|
|
166
|
+
"federated-config.test.cjs",
|
|
167
|
+
"federated-config-loadconfig.test.cjs",
|
|
168
|
+
"federated-config-key-removal.test.cjs"
|
|
169
|
+
],
|
|
170
|
+
"issue": "TBD"
|
|
171
|
+
},
|
|
172
|
+
"external-job": {
|
|
173
|
+
"files": [
|
|
174
|
+
"external-job-waiting.test.cjs"
|
|
175
|
+
],
|
|
176
|
+
"issue": "#1165"
|
|
163
177
|
}
|
|
164
178
|
}
|
|
165
179
|
}
|
|
@@ -34,17 +34,59 @@ const { readFileSync } = require('fs');
|
|
|
34
34
|
|
|
35
35
|
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
36
36
|
|
|
37
|
+
// ── Per-module mutation score ratchet ─────────────────────────────────────────
|
|
38
|
+
// ADR-456 / issue #1187: every covered module declares a minScore floor.
|
|
39
|
+
//
|
|
40
|
+
// HOW THE RATCHET WORKS:
|
|
41
|
+
// • minScore locks in the current measured mutation score (minus a 1–2 pt
|
|
42
|
+
// margin for run-to-run timeout variance).
|
|
43
|
+
// • CI fails a shard if the module's live score drops below its minScore.
|
|
44
|
+
// • Raise minScore (never lower) as a module's tests improve.
|
|
45
|
+
// • The goal is every module reaching TARGET_MUTATION_SCORE (80).
|
|
46
|
+
//
|
|
47
|
+
// GOODHART SAFETY: scores are improved by writing genuine behavioural
|
|
48
|
+
// assertions that kill real mutants — never by adding brittle exact-string
|
|
49
|
+
// matches on incidental output. A justified `// Stryker disable` on a
|
|
50
|
+
// confirmed equivalent mutant is acceptable.
|
|
51
|
+
//
|
|
52
|
+
// HOW TO UPDATE:
|
|
53
|
+
// 1. Run the per-module Stryker shard locally.
|
|
54
|
+
// 2. Note the reported score.
|
|
55
|
+
// 3. Set minScore = floor(score) - 1 (never lower than current value).
|
|
56
|
+
// 4. Open a PR — the CI gate will enforce the new floor on every future run.
|
|
57
|
+
|
|
58
|
+
/** Long-run target for all modules (ADR-456). */
|
|
59
|
+
const TARGET_MUTATION_SCORE = 80;
|
|
60
|
+
|
|
37
61
|
// ── Single source of truth: covered modules ───────────────────────────────────
|
|
38
|
-
// Each entry: { cjs: '<built artifact>', tests: ['tests/...', ...] }
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
62
|
+
// Each entry: { cjs: '<built artifact>', tests: ['tests/...', ...], minScore: N }
|
|
63
|
+
//
|
|
64
|
+
// minScore is the CI break threshold for this module's shard.
|
|
65
|
+
// Floors are measured scores minus 1–2 pts for run-to-run variance.
|
|
66
|
+
// Measured CI scores 2026-06-14 (issue #1187, timeout-free — source of truth):
|
|
67
|
+
// context-utilization 92.31% → floor 80 (target already met)
|
|
68
|
+
// prompt-budget 68.33% → floor 66 (local was 99.6% — TIMEOUT INFLATION; CI is the truth)
|
|
69
|
+
// frontmatter 63.35% → floor 62
|
|
70
|
+
// adr-parser 69.30% → floor 68
|
|
71
|
+
// config-schema 54.55% → floor 52 (local was 69.7% — TIMEOUT INFLATION; CI is the truth)
|
|
72
|
+
// active-workstream-store 81.91% → floor 80
|
|
73
|
+
// core-utils 77.52% → floor 75
|
|
74
|
+
//
|
|
75
|
+
// LESSON: floors MUST be calibrated from CI mutation runs (CI runs with
|
|
76
|
+
// timeout≈0, deterministic). Local runs count timeouts as kills and
|
|
77
|
+
// inflate scores significantly (prompt-budget: 99.6% local vs 68.3% CI;
|
|
78
|
+
// config-schema: 69.7% local vs 54.55% CI). Never set a floor from a
|
|
79
|
+
// local run without CI cross-check.
|
|
42
80
|
const COVERED = {
|
|
43
81
|
'context-utilization': {
|
|
44
82
|
cjs: 'gsd-core/bin/lib/context-utilization.cjs',
|
|
45
83
|
tests: [
|
|
46
84
|
'tests/context-utilization.property.test.cjs',
|
|
47
85
|
],
|
|
86
|
+
// After mutation-killer assertions added in #1187: measured 92.31% (2026-06-14).
|
|
87
|
+
// 3 survivors are __esModule boilerplate (genuinely equivalent CJS interop mutants).
|
|
88
|
+
// minScore raised to TARGET (80) — module now meets ADR-456 goal.
|
|
89
|
+
minScore: 80,
|
|
48
90
|
},
|
|
49
91
|
'prompt-budget': {
|
|
50
92
|
cjs: 'gsd-core/bin/lib/prompt-budget.cjs',
|
|
@@ -52,6 +94,9 @@ const COVERED = {
|
|
|
52
94
|
'tests/prompt-budget.property.test.cjs',
|
|
53
95
|
'tests/prompt-budget.unit.test.cjs',
|
|
54
96
|
],
|
|
97
|
+
// CI 68.33% timeout-free (164 killed / 1 timeout / 240 total) 2026-06-14;
|
|
98
|
+
// local was 99.6% — timeout inflation. Floor = 68 - 2 margin.
|
|
99
|
+
minScore: 66,
|
|
55
100
|
},
|
|
56
101
|
frontmatter: {
|
|
57
102
|
cjs: 'gsd-core/bin/lib/frontmatter.cjs',
|
|
@@ -59,6 +104,7 @@ const COVERED = {
|
|
|
59
104
|
'tests/frontmatter.property.test.cjs',
|
|
60
105
|
'tests/frontmatter.unit.test.cjs',
|
|
61
106
|
],
|
|
107
|
+
minScore: 62,
|
|
62
108
|
},
|
|
63
109
|
'adr-parser': {
|
|
64
110
|
cjs: 'gsd-core/bin/lib/adr-parser.cjs',
|
|
@@ -67,12 +113,16 @@ const COVERED = {
|
|
|
67
113
|
'tests/adr-parser.test.cjs',
|
|
68
114
|
'tests/adr-parser.unit.test.cjs',
|
|
69
115
|
],
|
|
116
|
+
minScore: 68,
|
|
70
117
|
},
|
|
71
118
|
'config-schema': {
|
|
72
119
|
cjs: 'gsd-core/bin/lib/config-schema.cjs',
|
|
73
120
|
tests: [
|
|
74
121
|
'tests/config-schema.property.test.cjs',
|
|
75
122
|
],
|
|
123
|
+
// CI 54.55% timeout-free (18 killed / 0 timeout / 33 total) 2026-06-14;
|
|
124
|
+
// local was 69.7% — timeout inflation. Floor = 54 - 2 margin.
|
|
125
|
+
minScore: 52,
|
|
76
126
|
},
|
|
77
127
|
'active-workstream-store': {
|
|
78
128
|
cjs: 'gsd-core/bin/lib/active-workstream-store.cjs',
|
|
@@ -80,6 +130,14 @@ const COVERED = {
|
|
|
80
130
|
'tests/active-workstream-store.test.cjs',
|
|
81
131
|
'tests/active-workstream-store.unit.test.cjs',
|
|
82
132
|
],
|
|
133
|
+
minScore: 80,
|
|
134
|
+
},
|
|
135
|
+
'core-utils': {
|
|
136
|
+
cjs: 'gsd-core/bin/lib/core-utils.cjs',
|
|
137
|
+
tests: [
|
|
138
|
+
'tests/core-utils.test.cjs',
|
|
139
|
+
],
|
|
140
|
+
minScore: 75, // measured 77.52% (2026-06-14, issue #1187); floor = 77 - 2
|
|
83
141
|
},
|
|
84
142
|
};
|
|
85
143
|
|
|
@@ -178,6 +236,7 @@ function buildResult(moduleNames) {
|
|
|
178
236
|
name,
|
|
179
237
|
mutate: COVERED[name].cjs,
|
|
180
238
|
tests: COVERED[name].tests.join(' '),
|
|
239
|
+
minScore: COVERED[name].minScore,
|
|
181
240
|
}));
|
|
182
241
|
|
|
183
242
|
return {
|
|
@@ -194,8 +253,9 @@ function printHuman(result, changedFiles) {
|
|
|
194
253
|
console.log(`Shards (${result.matrix.include.length}):`);
|
|
195
254
|
for (const shard of result.matrix.include) {
|
|
196
255
|
console.log(` [${shard.name}]`);
|
|
197
|
-
console.log(` mutate:
|
|
198
|
-
console.log(` tests:
|
|
256
|
+
console.log(` mutate: ${shard.mutate}`);
|
|
257
|
+
console.log(` tests: ${shard.tests}`);
|
|
258
|
+
console.log(` minScore: ${shard.minScore}`);
|
|
199
259
|
}
|
|
200
260
|
}
|
|
201
261
|
|
|
@@ -219,4 +279,45 @@ function main() {
|
|
|
219
279
|
}
|
|
220
280
|
}
|
|
221
281
|
|
|
222
|
-
|
|
282
|
+
// ── MUTATION_BREAK resolver ───────────────────────────────────────────────────
|
|
283
|
+
/**
|
|
284
|
+
* Resolves the per-shard mutation break threshold from the MUTATION_BREAK env var.
|
|
285
|
+
*
|
|
286
|
+
* Fail-closed contract:
|
|
287
|
+
* - undefined → 60 (local run: no env set, documented backstop)
|
|
288
|
+
* - set but empty (e.g. CI matrix.minScore missing) → throws (wiring error)
|
|
289
|
+
* - non-numeric or out-of-range [1, 100] → throws (invalid config)
|
|
290
|
+
* - valid integer string → returns that number
|
|
291
|
+
*
|
|
292
|
+
* This function is the single call site for reading MUTATION_BREAK.
|
|
293
|
+
* stryker.config.mjs imports and calls it so CI shards with a bad
|
|
294
|
+
* MUTATION_BREAK fail immediately rather than silently falling back to 60
|
|
295
|
+
* and bypassing a per-module floor above 60 (e.g. prompt-budget: 90).
|
|
296
|
+
*
|
|
297
|
+
* @param {string|undefined} raw - value of process.env.MUTATION_BREAK
|
|
298
|
+
* @returns {number}
|
|
299
|
+
*/
|
|
300
|
+
function resolveMutationBreak(raw) {
|
|
301
|
+
if (raw === undefined) {
|
|
302
|
+
// Local run with no MUTATION_BREAK set — use documented backstop.
|
|
303
|
+
return 60;
|
|
304
|
+
}
|
|
305
|
+
if (typeof raw !== 'string' || raw.trim() === '') {
|
|
306
|
+
throw new Error(
|
|
307
|
+
'MUTATION_BREAK is set but empty — CI shard wiring is broken (matrix.minScore missing?)'
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const n = Number(raw);
|
|
311
|
+
if (!Number.isFinite(n) || n < 1 || n > 100) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`MUTATION_BREAK invalid: "${raw}" (expected a per-module minScore 1-100)`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return n;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Export internals for programmatic use (tests/mutation-matrix-ratchet.test.cjs).
|
|
320
|
+
// The require.main guard prevents main() from running when this file is require()d.
|
|
321
|
+
module.exports = { COVERED, TARGET_MUTATION_SCORE, resolveMutationBreak };
|
|
322
|
+
|
|
323
|
+
if (require.main === module) runMain(main);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PR target-branch policy — ADR-230.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from .github/workflows/pr-target-validator.yml so the classification
|
|
7
|
+
* logic can be unit-tested independently and required by the workflow at checkout
|
|
8
|
+
* from the TRUSTED base-branch copy (fork-tamper-safe).
|
|
9
|
+
*
|
|
10
|
+
* Pure module: no I/O, no GitHub API calls, no side effects.
|
|
11
|
+
*
|
|
12
|
+
* See: docs/branching.md, docs/adr/230-introduce-next-integration-branch.md
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The five patterns that allow a PR to target `main`.
|
|
17
|
+
* Verbatim from the github-script in pr-target-validator.yml.
|
|
18
|
+
*
|
|
19
|
+
* @type {RegExp[]}
|
|
20
|
+
*/
|
|
21
|
+
const MAIN_ALLOWED_PATTERNS = [
|
|
22
|
+
/^release\/\d+\.\d+\.0$/, // release branches
|
|
23
|
+
/^hotfix\/\d+\.\d+\.\d+$/, // hotfix branches
|
|
24
|
+
/^fix\/critical-/, // production-down emergencies
|
|
25
|
+
/^chore\/backmerge-/, // auto-backmerge from this workflow
|
|
26
|
+
/^revert\/critical-/, // emergency reverts
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Classify a pull request by its base and head branch names.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} base - The PR's target branch (e.g. 'next', 'main', 'release/1.2.0').
|
|
33
|
+
* @param {string} head - The PR's source branch (e.g. 'feat/my-feature').
|
|
34
|
+
* @returns {{ decision: 'allowed' | 'blocked' | 'unusual' }}
|
|
35
|
+
*/
|
|
36
|
+
function classifyPrTarget(base, head) {
|
|
37
|
+
// PRs targeting `next` are always fine.
|
|
38
|
+
if (base === 'next') {
|
|
39
|
+
return { decision: 'allowed' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// PRs targeting `main`: only specific branch types allowed.
|
|
43
|
+
if (base === 'main') {
|
|
44
|
+
const allowed = MAIN_ALLOWED_PATTERNS.some(re => re.test(head));
|
|
45
|
+
if (allowed) {
|
|
46
|
+
return { decision: 'allowed' };
|
|
47
|
+
}
|
|
48
|
+
return { decision: 'blocked' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// PRs targeting release/X.Y.0 or hotfix/X.Y.Z are fine (stabilization PRs).
|
|
52
|
+
if (/^release\/\d+\.\d+\.0$/.test(base) || /^hotfix\/\d+\.\d+\.\d+$/.test(base)) {
|
|
53
|
+
return { decision: 'allowed' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Any other target is unusual but not forbidden.
|
|
57
|
+
return { decision: 'unusual' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
MAIN_ALLOWED_PATTERNS,
|
|
62
|
+
classifyPrTarget,
|
|
63
|
+
};
|
|
@@ -57,6 +57,11 @@ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
|
57
57
|
// https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
|
|
58
58
|
// ). Raise to 600 s (the same ceiling the before() helper uses for pack+install).
|
|
59
59
|
const CHILD_TIMEOUT_MS = process.platform === 'win32' ? 600_000 : 120_000;
|
|
60
|
+
const QUIET_NPM_ENV = Object.freeze({
|
|
61
|
+
npm_config_loglevel: 'error',
|
|
62
|
+
npm_config_update_notifier: 'false',
|
|
63
|
+
NO_UPDATE_NOTIFIER: '1',
|
|
64
|
+
});
|
|
60
65
|
|
|
61
66
|
// ---------------------------------------------------------------------------
|
|
62
67
|
// Frozen result-code enum
|
|
@@ -304,7 +309,7 @@ function runSmoke({
|
|
|
304
309
|
// Use the caller-supplied npmEnv if provided (allows HOME isolation on Docker
|
|
305
310
|
// hosts where HOME may be unwritable — same pattern as runNpm() in helpers.cjs).
|
|
306
311
|
// Falls back to process.env to preserve existing CLI / programmatic behaviour. (#131)
|
|
307
|
-
const effectiveNpmEnv = npmEnv !== undefined ? npmEnv : process.env;
|
|
312
|
+
const effectiveNpmEnv = { ...(npmEnv !== undefined ? npmEnv : process.env), ...QUIET_NPM_ENV };
|
|
308
313
|
const installResult = spawnSync(
|
|
309
314
|
npmCmd,
|
|
310
315
|
['install', '-g', '--prefix', installPrefix, tarballPath],
|
|
@@ -570,6 +575,7 @@ function cliMain() {
|
|
|
570
575
|
encoding: 'utf-8',
|
|
571
576
|
shell: process.platform === 'win32',
|
|
572
577
|
timeout: CHILD_TIMEOUT_MS,
|
|
578
|
+
env: { ...process.env, ...QUIET_NPM_ENV },
|
|
573
579
|
},
|
|
574
580
|
).trim();
|
|
575
581
|
// npm pack outputs the filename on stdout (last line when verbose)
|
|
@@ -24,7 +24,7 @@ const PROFILES = [
|
|
|
24
24
|
'Researches domain ecosystem before roadmap creation. Produces files in .planning/research/ consumed during roadmap creation. Spawned by /gsd:new-project or /gsd:new-milestone orchestrators.',
|
|
25
25
|
color: 'cyan',
|
|
26
26
|
tools:
|
|
27
|
-
'Read, Write, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
|
|
27
|
+
'Read, Write, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
|
|
28
28
|
requiredIncludes: [
|
|
29
29
|
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
|
|
30
30
|
'@~/.claude/gsd-core/references/research-philosophy.md',
|
|
@@ -46,7 +46,7 @@ const PROFILES = [
|
|
|
46
46
|
'Researches how to implement a phase before planning. Produces RESEARCH.md consumed by gsd-planner. Spawned by /gsd:plan-phase orchestrator.',
|
|
47
47
|
color: 'cyan',
|
|
48
48
|
tools:
|
|
49
|
-
'Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
|
|
49
|
+
'Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
|
|
50
50
|
requiredIncludes: [
|
|
51
51
|
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
|
|
52
52
|
'@~/.claude/gsd-core/references/research-philosophy.md',
|
|
@@ -68,7 +68,7 @@ const PROFILES = [
|
|
|
68
68
|
description:
|
|
69
69
|
'Researches a single gray area decision and returns a structured comparison table with rationale. Spawned by discuss-phase advisor mode.',
|
|
70
70
|
color: 'cyan',
|
|
71
|
-
tools: 'Read, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*',
|
|
71
|
+
tools: 'Read, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*',
|
|
72
72
|
requiredIncludes: [
|
|
73
73
|
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
|
|
74
74
|
],
|
|
@@ -117,7 +117,7 @@ const PROFILES = [
|
|
|
117
117
|
'Produces UI-SPEC.md design contract for frontend phases. Reads upstream artifacts, detects design system state, asks only unanswered questions. Spawned by /gsd:ui-phase orchestrator.',
|
|
118
118
|
color: 'purple',
|
|
119
119
|
tools:
|
|
120
|
-
'Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
|
|
120
|
+
'Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
|
|
121
121
|
requiredIncludes: [
|
|
122
122
|
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
|
|
123
123
|
],
|
|
@@ -134,7 +134,7 @@ const PROFILES = [
|
|
|
134
134
|
description:
|
|
135
135
|
'Synthesizes research outputs from parallel researcher agents into SUMMARY.md. Spawned by /gsd:new-project after 4 researcher agents complete.',
|
|
136
136
|
color: 'purple',
|
|
137
|
-
tools: 'Read, Write, Bash',
|
|
137
|
+
tools: 'Read, Write, Bash, Skill',
|
|
138
138
|
requiredIncludes: [],
|
|
139
139
|
requiredSeamCalls: [
|
|
140
140
|
'gsd_run query commit',
|