@opengsd/gsd-core 1.5.0 → 1.6.0-rc.1
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-plan-checker.md +34 -0
- package/agents/gsd-planner.md +2 -0
- package/bin/install.js +108 -34
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +677 -2
- package/gsd-core/bin/lib/adr-parser.cjs +24 -17
- package/gsd-core/bin/lib/audit.cjs +2 -2
- package/gsd-core/bin/lib/capability-consent.cjs +763 -0
- package/gsd-core/bin/lib/capability-ledger.cjs +831 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +1551 -0
- package/gsd-core/bin/lib/capability-loader.cjs +764 -0
- package/gsd-core/bin/lib/capability-lock.cjs +553 -0
- package/gsd-core/bin/lib/capability-registry.cjs +198 -4
- package/gsd-core/bin/lib/capability-source.cjs +1242 -0
- package/gsd-core/bin/lib/capability-state.cjs +9 -6
- package/gsd-core/bin/lib/capability-trust.cjs +550 -0
- package/gsd-core/bin/lib/capability-validator.cjs +2066 -0
- package/gsd-core/bin/lib/capability-writer.cjs +14 -5
- package/gsd-core/bin/lib/check-command-router.cjs +69 -18
- package/gsd-core/bin/lib/command-aliases.cjs +8 -0
- package/gsd-core/bin/lib/config-loader.cjs +92 -84
- package/gsd-core/bin/lib/config-schema.cjs +26 -7
- package/gsd-core/bin/lib/config.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +149 -60
- package/gsd-core/bin/lib/gap-checker.cjs +126 -11
- package/gsd-core/bin/lib/init.cjs +91 -22
- package/gsd-core/bin/lib/legacy-cleanup.cjs +96 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +26 -2
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +471 -0
- package/gsd-core/bin/lib/milestone.cjs +41 -2
- package/gsd-core/bin/lib/phase-command-router.cjs +5 -0
- package/gsd-core/bin/lib/phase-lifecycle.cjs +14 -5
- package/gsd-core/bin/lib/phase.cjs +29 -0
- package/gsd-core/bin/lib/project-root.cjs +89 -2
- package/gsd-core/bin/lib/resolution.cjs +26 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +44 -98
- package/gsd-core/bin/lib/runtime-homes.cjs +53 -1
- package/gsd-core/bin/lib/semver-compare.cjs +127 -0
- package/gsd-core/bin/lib/state-document.cjs +4 -2
- package/gsd-core/bin/lib/state.cjs +317 -161
- package/gsd-core/bin/lib/uat-predicate.cjs +7 -47
- package/gsd-core/bin/lib/uat.cjs +39 -26
- package/gsd-core/bin/lib/verify.cjs +29 -13
- package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -1
- package/gsd-core/references/execute-phase-between-wave-reset.md +43 -0
- package/gsd-core/references/execute-phase-wave-guard.md +33 -0
- package/gsd-core/references/planner-antipatterns.md +48 -0
- package/gsd-core/references/planning-config.md +3 -0
- package/gsd-core/references/scout-codebase.md +2 -2
- package/gsd-core/workflows/discuss-phase/templates/context.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -2
- package/gsd-core/workflows/execute-phase.md +4 -6
- package/package.json +3 -3
- package/scripts/gen-capability-matrix.cjs +284 -0
- package/scripts/gen-capability-registry.cjs +96 -1853
- package/scripts/lint-regression-test-names.allowlist.json +1 -0
- package/scripts/lint-resolution-provenance.allowlist.json +1 -0
- package/scripts/lint-resolution-provenance.cjs +192 -0
- package/scripts/lint-test-file-count.allowlist.json +9 -0
- package/scripts/run-tests.cjs +14 -0
- package/scripts/sync-manifest-versions.cjs +77 -5
|
@@ -647,6 +647,40 @@ issue:
|
|
|
647
647
|
fix_hint: "Add auth middleware pattern from PATTERNS.md ## Shared Patterns to plan"
|
|
648
648
|
```
|
|
649
649
|
|
|
650
|
+
## Dimension: Verify Command Format Sanity (#1478, #1479)
|
|
651
|
+
|
|
652
|
+
**Question:** Do `<verify>` commands use patterns that can actually match the tool's output? Are numeric counts measured? Are errors suppressed into comparison-feeding defaults?
|
|
653
|
+
|
|
654
|
+
**Red flags — BLOCKER:**
|
|
655
|
+
- `pnpm ls … | grep -E '^package'` — `^` anchor on tree-formatted package manager output (never matches tree-prefixed lines)
|
|
656
|
+
- Any verify block with `VAR=$(cmd 2>/dev/null || echo "0"); [ "$VAR" = ... ]` — swallowed error feeds passing comparison
|
|
657
|
+
- `|| true` or `|| :` as right-hand side of assignments that feed comparisons
|
|
658
|
+
|
|
659
|
+
**Red flags — WARNING:**
|
|
660
|
+
- Hard-coded count assertion (`grep '52 test files'`, `grep '714 passed'`) with no measurement provenance in the plan
|
|
661
|
+
|
|
662
|
+
**Process:**
|
|
663
|
+
1. For each `<automated>` block piping a package-manager list command into grep with a `^` anchor: BLOCKER.
|
|
664
|
+
2. For each `<automated>` block containing `2>/dev/null || echo` where the result feeds a `[ "$VAR" = ... ]` comparison: BLOCKER.
|
|
665
|
+
3. For each `<automated>` block asserting a specific numeric count not cited as measured in this plan: WARNING.
|
|
666
|
+
|
|
667
|
+
## Dimension: Numeric/Factual Claim Authority (#1480)
|
|
668
|
+
|
|
669
|
+
**Rule:** RESEARCH.md is produced at research time and may be stale. Numeric claims (test counts, file counts, version numbers) and factual state claims ("feature X is implemented") in RESEARCH.md may not reflect the current codebase. The plan may be more current. RESEARCH.md is authoritative for architectural decisions and constraints — not for measurements.
|
|
670
|
+
|
|
671
|
+
**Process when a plan's numeric/factual claim conflicts with RESEARCH.md:**
|
|
672
|
+
|
|
673
|
+
1. **Attempt live measurement first** with a targeted read-only command (e.g., `find . -name '*.test.*' | wc -l`). Run it. Use the result as ground truth:
|
|
674
|
+
- Measurement confirms plan → WARNING: RESEARCH.md is stale; recommend updating it.
|
|
675
|
+
- Measurement contradicts plan → BLOCKER: plan value is wrong; prescribe the measured value.
|
|
676
|
+
|
|
677
|
+
2. **If live measurement is not possible** (external system, future state): report the discrepancy WITHOUT prescribing which value is correct:
|
|
678
|
+
> Discrepancy: plan asserts X, RESEARCH.md asserts Y. Cannot determine ground truth without live measurement. Verify manually and update the stale artifact.
|
|
679
|
+
|
|
680
|
+
**NEVER** prescribe a specific value by assuming RESEARCH.md is authoritative for a numeric/factual claim.
|
|
681
|
+
|
|
682
|
+
**Note:** A targeted read-only shell command (counting files, reading a schema, checking a version file) is NOT "running the application" — it is live measurement. Such commands are permitted under this dimension even when the anti-pattern block says "DO NOT run the application."
|
|
683
|
+
|
|
650
684
|
</verification_dimensions>
|
|
651
685
|
|
|
652
686
|
<verification_process>
|
package/agents/gsd-planner.md
CHANGED
|
@@ -200,6 +200,8 @@ Full rules + worked examples: @gsd-core/references/planner-antipatterns.md ("Com
|
|
|
200
200
|
|
|
201
201
|
<region_scoped_negative_gate>
|
|
202
202
|
**Region-scoped negative gates (WARN, #968):** Region-scope a file-wide negative grep when a sibling task needs that construct elsewhere in the same file; `validate_plan` WARNS. See: @gsd-core/references/planner-antipatterns.md ("Region-Scoped Negative Gates").
|
|
203
|
+
|
|
204
|
+
**Verify-gate hygiene (#1478/#1479):** See @gsd-core/references/planner-antipatterns.md.
|
|
203
205
|
</region_scoped_negative_gate>
|
|
204
206
|
|
|
205
207
|
**<done>:** Acceptance criteria - measurable state of completion.
|
package/bin/install.js
CHANGED
|
@@ -7118,18 +7118,23 @@ function _runLegacyInstallMigrations(runtime, configDir, scope = 'global') {
|
|
|
7118
7118
|
* @param {'global'|'local'} [scope]
|
|
7119
7119
|
*/
|
|
7120
7120
|
function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
|
|
7121
|
-
//
|
|
7122
|
-
//
|
|
7123
|
-
// Claude
|
|
7124
|
-
//
|
|
7121
|
+
// commands/gsd/ is a legacy location for Qwen, Hermes, and all Claude installs.
|
|
7122
|
+
// Prior to #1367 fix, Claude-local used commands/gsd/<cmd>.md (colon-namespaced).
|
|
7123
|
+
// After #1367, Claude-local uses flat commands/gsd-<cmd>.md. The inline uninstall
|
|
7124
|
+
// block (1c) handles removal of flat files; this function handles the legacy
|
|
7125
|
+
// commands/gsd/ directory for all Claude scopes (global was already included,
|
|
7126
|
+
// local is now added since that layout is also legacy post-#1367).
|
|
7125
7127
|
// #2973 / Codex review (bd1f06c9): preserve user-owned dev-preferences.md
|
|
7126
7128
|
// before destructive wipe. Migration to skills/gsd-dev-preferences/SKILL.md
|
|
7127
7129
|
// is deferred and returned so the caller can apply it AFTER layout-driven
|
|
7128
7130
|
// removal — this prevents the layout's gsd-* prefix removal from wiping the
|
|
7129
7131
|
// freshly created skill dir (same pattern as _runLegacyInstallMigrations).
|
|
7130
7132
|
let savedLegacyArtifacts = null;
|
|
7131
|
-
// commands/gsd/ is a legacy location for Qwen, Hermes, and Claude
|
|
7132
|
-
// Claude
|
|
7133
|
+
// commands/gsd/ is a legacy location for Qwen, Hermes, and Claude global.
|
|
7134
|
+
// Claude local is intentionally excluded: the inline uninstall block (1c) handles
|
|
7135
|
+
// commands/gsd/ for claude local, preserving dev-preferences.md by restoring it
|
|
7136
|
+
// to the same location (#1423). Using migrateLegacyDevPreferencesToSkill here
|
|
7137
|
+
// (which would redirect to skills/) conflicts with the test contract for local installs.
|
|
7133
7138
|
const isLegacyCommandsGsd = runtime === 'qwen' || runtime === 'hermes' || (runtime === 'claude' && scope === 'global');
|
|
7134
7139
|
if (isLegacyCommandsGsd) {
|
|
7135
7140
|
const legacyCommandsGsd = path.join(configDir, 'commands', 'gsd');
|
|
@@ -8066,23 +8071,37 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8066
8071
|
} catch { /* best-effort */ }
|
|
8067
8072
|
}
|
|
8068
8073
|
|
|
8069
|
-
// 1c. Claude local: remove commands/
|
|
8070
|
-
//
|
|
8071
|
-
// flat command dirs (OpenCode/Kilo). Claude local files use no prefix inside
|
|
8072
|
-
// the namespaced directory, so layout does not remove them. Handle inline.
|
|
8073
|
-
// Preserve dev-preferences.md across the wipe (#1423).
|
|
8074
|
+
// 1c. Claude local: remove flat gsd-*.md commands from commands/ (current layout,
|
|
8075
|
+
// #1367 fix). Also remove legacy commands/gsd/ subdirectory from prior installs.
|
|
8074
8076
|
if (!isGlobal && runtime === 'claude') {
|
|
8075
|
-
const
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
fs.
|
|
8077
|
+
const commandsDir = path.join(targetDir, 'commands');
|
|
8078
|
+
// Remove flat gsd-*.md files (current layout after #1367 fix)
|
|
8079
|
+
if (fs.existsSync(commandsDir)) {
|
|
8080
|
+
let removed = 0;
|
|
8081
|
+
for (const f of fs.readdirSync(commandsDir)) {
|
|
8082
|
+
if (f.startsWith('gsd-') && f.endsWith('.md')) {
|
|
8083
|
+
fs.rmSync(path.join(commandsDir, f), { force: true });
|
|
8084
|
+
removed++;
|
|
8085
|
+
}
|
|
8086
|
+
}
|
|
8087
|
+
if (removed > 0) {
|
|
8088
|
+
removedCount++;
|
|
8089
|
+
console.log(` ${green}✓${reset} Removed ${removed} flat gsd-*.md commands from commands/`);
|
|
8090
|
+
}
|
|
8091
|
+
}
|
|
8092
|
+
// Remove legacy commands/gsd/ subdirectory if it still exists (pre-#1367 layout).
|
|
8093
|
+
// Preserve user-owned dev-preferences.md if present (#1423 parity).
|
|
8094
|
+
const legacyGsdCommandsDir = path.join(targetDir, 'commands', 'gsd');
|
|
8095
|
+
if (fs.existsSync(legacyGsdCommandsDir)) {
|
|
8096
|
+
const legacyDevPrefsPath = path.join(legacyGsdCommandsDir, 'dev-preferences.md');
|
|
8097
|
+
const savedDevPrefs = fs.existsSync(legacyDevPrefsPath) ? fs.readFileSync(legacyDevPrefsPath, 'utf-8') : null;
|
|
8098
|
+
fs.rmSync(legacyGsdCommandsDir, { recursive: true });
|
|
8080
8099
|
removedCount++;
|
|
8081
|
-
console.log(` ${green}✓${reset} Removed commands/gsd/`);
|
|
8082
|
-
if (
|
|
8100
|
+
console.log(` ${green}✓${reset} Removed legacy commands/gsd/`);
|
|
8101
|
+
if (savedDevPrefs) {
|
|
8083
8102
|
try {
|
|
8084
|
-
fs.mkdirSync(
|
|
8085
|
-
fs.writeFileSync(
|
|
8103
|
+
fs.mkdirSync(legacyGsdCommandsDir, { recursive: true });
|
|
8104
|
+
fs.writeFileSync(legacyDevPrefsPath, savedDevPrefs);
|
|
8086
8105
|
console.log(` ${green}✓${reset} Preserved commands/gsd/dev-preferences.md`);
|
|
8087
8106
|
} catch (err) {
|
|
8088
8107
|
console.error(` ${red}✗${reset} Failed to restore dev-preferences.md: ${err.message}`);
|
|
@@ -8849,7 +8868,11 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
8849
8868
|
const isKimi = runtime === 'kimi';
|
|
8850
8869
|
const isHermes = runtime === 'hermes';
|
|
8851
8870
|
const gsdDir = path.join(configDir, 'gsd-core');
|
|
8871
|
+
// #1367: Claude local now writes flat gsd-*.md files at commands/ (not commands/gsd/).
|
|
8872
|
+
// commandsDir points to the old location for Gemini (which still uses commands/gsd/).
|
|
8873
|
+
// Claude local uses flatCommandsDir instead for manifest recording.
|
|
8852
8874
|
const commandsDir = path.join(configDir, 'commands', 'gsd');
|
|
8875
|
+
const flatCommandsDir = path.join(configDir, 'commands');
|
|
8853
8876
|
const opencodeCommandDir = path.join(configDir, 'command');
|
|
8854
8877
|
// Hermes nests GSD skills under skills/gsd/ as a single category (#2841).
|
|
8855
8878
|
// All other runtimes that use the Codex-style skills layout use a flat skills/ root.
|
|
@@ -8875,17 +8898,27 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
8875
8898
|
if (USER_OWNED_ARTIFACTS.includes(rel)) continue;
|
|
8876
8899
|
manifest.files['gsd-core/' + rel] = hash;
|
|
8877
8900
|
}
|
|
8878
|
-
// Record commands
|
|
8879
|
-
//
|
|
8880
|
-
//
|
|
8881
|
-
//
|
|
8882
|
-
//
|
|
8883
|
-
if (fs.existsSync(commandsDir)) {
|
|
8901
|
+
// Record commands surface for runtimes that emit it:
|
|
8902
|
+
// Gemini: commands/gsd/<cmd>.toml (nested, colon-namespaced)
|
|
8903
|
+
// Claude local (#1367 fix): flat gsd-<cmd>.md at commands/ level
|
|
8904
|
+
// Manifest must reflect everything on disk so saveLocalPatches() can detect
|
|
8905
|
+
// user edits and per-runtime minimal-mode assertions can read manifest.files.
|
|
8906
|
+
if (isGemini && fs.existsSync(commandsDir)) {
|
|
8884
8907
|
const cmdHashes = generateManifest(commandsDir);
|
|
8885
8908
|
for (const [rel, hash] of Object.entries(cmdHashes)) {
|
|
8886
8909
|
manifest.files['commands/gsd/' + rel] = hash;
|
|
8887
8910
|
}
|
|
8888
8911
|
}
|
|
8912
|
+
// Claude local (#1367): flat gsd-*.md files at commands/ level.
|
|
8913
|
+
// Only claude local writes gsd-*.md here; global installs don't emit commands,
|
|
8914
|
+
// so this branch is a no-op for global (no matching files to find).
|
|
8915
|
+
if (runtime === 'claude' && fs.existsSync(flatCommandsDir)) {
|
|
8916
|
+
for (const file of fs.readdirSync(flatCommandsDir)) {
|
|
8917
|
+
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
|
8918
|
+
manifest.files['commands/' + file] = fileHash(path.join(flatCommandsDir, file));
|
|
8919
|
+
}
|
|
8920
|
+
}
|
|
8921
|
+
}
|
|
8889
8922
|
if ((isOpencode || isKilo) && fs.existsSync(opencodeCommandDir)) {
|
|
8890
8923
|
for (const file of fs.readdirSync(opencodeCommandDir)) {
|
|
8891
8924
|
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
|
@@ -9985,18 +10018,59 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9985
10018
|
}
|
|
9986
10019
|
}
|
|
9987
10020
|
} else {
|
|
9988
|
-
// Claude Code local:
|
|
9989
|
-
// commands from .claude/commands/
|
|
10021
|
+
// Claude Code local: flat gsd-<cmd>.md layout — Claude Code registers
|
|
10022
|
+
// commands from .claude/commands/ using the filename stem as the command
|
|
10023
|
+
// name, so gsd-<cmd>.md produces the /gsd-<cmd> hyphen form used everywhere
|
|
10024
|
+
// in the framework. The old commands/gsd/<cmd>.md subdirectory layout caused
|
|
10025
|
+
// Claude Code to namespace commands as /gsd:<cmd> (colon form). (#1367)
|
|
9990
10026
|
const commandsDir = path.join(targetDir, 'commands');
|
|
9991
10027
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
9992
10028
|
const gsdSrc = _stageSkills(_commandsDir);
|
|
9993
|
-
const
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
10029
|
+
const cmdNames = readGsdCommandNames();
|
|
10030
|
+
|
|
10031
|
+
// Remove stale gsd-*.md files before writing new ones (clean install)
|
|
10032
|
+
if (fs.existsSync(commandsDir)) {
|
|
10033
|
+
for (const f of fs.readdirSync(commandsDir)) {
|
|
10034
|
+
if (f.startsWith('gsd-') && f.endsWith('.md')) {
|
|
10035
|
+
fs.unlinkSync(path.join(commandsDir, f));
|
|
10036
|
+
}
|
|
10037
|
+
}
|
|
10038
|
+
}
|
|
10039
|
+
|
|
10040
|
+
// Write each command as gsd-<stem>.md (flat, hyphen-prefixed)
|
|
10041
|
+
let cmdCount = 0;
|
|
10042
|
+
if (fs.existsSync(gsdSrc)) {
|
|
10043
|
+
for (const entry of fs.readdirSync(gsdSrc, { withFileTypes: true })) {
|
|
10044
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
10045
|
+
const stem = entry.name.slice(0, -3);
|
|
10046
|
+
let content = fs.readFileSync(path.join(gsdSrc, entry.name), 'utf8');
|
|
10047
|
+
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal);
|
|
10048
|
+
content = normalizeAgentBodyForRuntime(content, runtime, cmdNames);
|
|
10049
|
+
fs.writeFileSync(path.join(commandsDir, `gsd-${stem}.md`), content);
|
|
10050
|
+
cmdCount++;
|
|
10051
|
+
}
|
|
10052
|
+
}
|
|
10053
|
+
|
|
10054
|
+
if (cmdCount > 0) {
|
|
10055
|
+
console.log(` ${green}✓${reset} Installed ${cmdCount} commands to commands/ (gsd-<cmd>.md flat form)`);
|
|
9998
10056
|
} else {
|
|
9999
|
-
failures.push('commands/gsd');
|
|
10057
|
+
failures.push('commands/gsd-*');
|
|
10058
|
+
}
|
|
10059
|
+
|
|
10060
|
+
// Legacy cleanup: remove old commands/gsd/ subdirectory from prior installs
|
|
10061
|
+
// that used the namespaced layout (wrote bare-name files under commands/gsd/).
|
|
10062
|
+
const legacyGsdDir = path.join(commandsDir, 'gsd');
|
|
10063
|
+
if (fs.existsSync(legacyGsdDir)) {
|
|
10064
|
+
// Preserve user-owned dev-preferences.md before wiping
|
|
10065
|
+
const devPrefsPath = path.join(legacyGsdDir, 'dev-preferences.md');
|
|
10066
|
+
const preservedDevPrefs = fs.existsSync(devPrefsPath) ? fs.readFileSync(devPrefsPath, 'utf-8') : null;
|
|
10067
|
+
fs.rmSync(legacyGsdDir, { recursive: true });
|
|
10068
|
+
console.log(` ${green}✓${reset} Removed legacy commands/gsd/ (migrated to flat gsd-<cmd>.md layout)`);
|
|
10069
|
+
if (preservedDevPrefs) {
|
|
10070
|
+
// Migrate dev-preferences to the new flat form
|
|
10071
|
+
fs.writeFileSync(path.join(commandsDir, 'gsd-dev-preferences.md'), preservedDevPrefs);
|
|
10072
|
+
console.log(` ${green}✓${reset} Migrated dev-preferences.md to commands/gsd-dev-preferences.md`);
|
|
10073
|
+
}
|
|
10000
10074
|
}
|
|
10001
10075
|
|
|
10002
10076
|
// Clean up any stale skills/ from a previous local install
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0-rc.1",
|
|
4
4
|
"description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|