@opengsd/gsd-core 1.5.0-rc.5 → 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/LICENSE +1 -1
- package/agents/gsd-executor.md +22 -0
- package/agents/gsd-plan-checker.md +34 -0
- package/agents/gsd-planner.md +2 -0
- package/bin/install.js +161 -133
- package/commands/gsd/autonomous.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/plan-phase.md +1 -1
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +713 -7
- 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 +154 -47
- 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-id.cjs +1 -1
- package/gsd-core/bin/lib/phase-lifecycle.cjs +14 -5
- package/gsd-core/bin/lib/phase.cjs +70 -6
- package/gsd-core/bin/lib/probe-core.cjs +7 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +201 -26
- 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 +69 -118
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +4 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +53 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +24 -3
- 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 +379 -174
- package/gsd-core/bin/lib/teams-status.cjs +74 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +7 -47
- package/gsd-core/bin/lib/uat.cjs +75 -6
- package/gsd-core/bin/lib/verify.cjs +29 -13
- package/gsd-core/bin/lib/worktree-safety.cjs +7 -1
- 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/prohibition-probe.md +48 -19
- package/gsd-core/references/scout-codebase.md +2 -2
- package/gsd-core/references/worktree-branch-check.md +11 -5
- package/gsd-core/workflows/discuss-phase/templates/context.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -2
- package/gsd-core/workflows/docs-update.md +23 -31
- package/gsd-core/workflows/execute-phase.md +6 -7
- package/gsd-core/workflows/map-codebase.md +8 -10
- package/gsd-core/workflows/plan-phase.md +6 -0
- package/gsd-core/workflows/quick.md +23 -2
- package/gsd-core/workflows/spec-phase.md +11 -6
- package/gsd-core/workflows/verify-phase.md +3 -3
- package/hooks/dist/gsd-worktree-path-guard.js +34 -18
- package/hooks/gsd-worktree-path-guard.js +34 -18
- package/package.json +4 -4
- package/scripts/ci-prepare-test-scope.cjs +56 -14
- package/scripts/diff-touches-shipped-paths.cjs +5 -11
- 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 +15 -0
- package/scripts/sync-manifest-versions.cjs +77 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-core",
|
|
3
3
|
"displayName": "GSD Core",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0-rc.1",
|
|
5
5
|
"description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "open-gsd",
|
package/LICENSE
CHANGED
package/agents/gsd-executor.md
CHANGED
|
@@ -103,6 +103,24 @@ PLAN_START_EPOCH=$(date +%s)
|
|
|
103
103
|
```
|
|
104
104
|
</step>
|
|
105
105
|
|
|
106
|
+
<worktree_metadata_capture>
|
|
107
|
+
If running inside a git worktree, capture authoritative worktree identity before
|
|
108
|
+
any task commit changes HEAD. The execute-phase orchestrator consumes this from
|
|
109
|
+
your final `<worktree_metadata>` return block to build the wave cleanup manifest
|
|
110
|
+
without relying on runtime harness metadata (#1297).
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
GSD_WORKTREE_PATH=""
|
|
114
|
+
GSD_WORKTREE_BRANCH=""
|
|
115
|
+
GSD_WORKTREE_EXPECTED_BASE=""
|
|
116
|
+
if [ -f .git ]; then
|
|
117
|
+
GSD_WORKTREE_PATH=$(git rev-parse --show-toplevel)
|
|
118
|
+
GSD_WORKTREE_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
119
|
+
GSD_WORKTREE_EXPECTED_BASE=$(git rev-parse HEAD)
|
|
120
|
+
fi
|
|
121
|
+
```
|
|
122
|
+
</worktree_metadata_capture>
|
|
123
|
+
|
|
106
124
|
<step name="determine_execution_pattern">
|
|
107
125
|
```bash
|
|
108
126
|
grep -n "type=\"checkpoint" [plan-path]
|
|
@@ -761,6 +779,10 @@ into the user's project history.
|
|
|
761
779
|
**Tasks:** {completed}/{total}
|
|
762
780
|
**SUMMARY:** {path to SUMMARY.md}
|
|
763
781
|
|
|
782
|
+
<worktree_metadata>
|
|
783
|
+
{"agent_id":"{phase}-{plan}","worktree_path":"${GSD_WORKTREE_PATH:-}","branch":"${GSD_WORKTREE_BRANCH:-}","expected_base":"${GSD_WORKTREE_EXPECTED_BASE:-}"}
|
|
784
|
+
</worktree_metadata>
|
|
785
|
+
|
|
764
786
|
**Commits:**
|
|
765
787
|
- {hash}: {message}
|
|
766
788
|
- {hash}: {message}
|
|
@@ -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
|
@@ -1796,6 +1796,10 @@ function skillFrontmatterName(skillDirName) {
|
|
|
1796
1796
|
return skillDirName;
|
|
1797
1797
|
}
|
|
1798
1798
|
|
|
1799
|
+
function normalizeClaudeSkillEffort(effort) {
|
|
1800
|
+
return effort === 'xhigh' ? 'max' : effort;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1799
1803
|
/**
|
|
1800
1804
|
* Qwen Code skills accept an optional numeric `priority` frontmatter field.
|
|
1801
1805
|
* Per the Qwen skills spec (qwen-code/docs/users/features/skills.md, verified
|
|
@@ -1892,7 +1896,7 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
1892
1896
|
// token-budget tier). Fields are Claude-specific; unknown frontmatter
|
|
1893
1897
|
// fields are silently ignored by other runtimes (backward-compatible).
|
|
1894
1898
|
if (context) fm += `context: ${context}\n`;
|
|
1895
|
-
if (effort) fm += `effort: ${effort}\n`;
|
|
1899
|
+
if (effort) fm += `effort: ${normalizeClaudeSkillEffort(effort)}\n`;
|
|
1896
1900
|
if (toolsBlock) fm += toolsBlock;
|
|
1897
1901
|
fm += '---';
|
|
1898
1902
|
|
|
@@ -3194,110 +3198,55 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
3194
3198
|
}
|
|
3195
3199
|
|
|
3196
3200
|
/**
|
|
3197
|
-
*
|
|
3201
|
+
* Remove stale agents/openai.yaml sidecar files from GSD-managed Codex skill dirs.
|
|
3198
3202
|
*
|
|
3199
|
-
*
|
|
3200
|
-
*
|
|
3201
|
-
*
|
|
3202
|
-
*
|
|
3203
|
+
* Prior to #1326, GSD's Codex install path wrote an agents/openai.yaml file
|
|
3204
|
+
* alongside each gsd-* SKILL.md. Recent Codex builds index BOTH SKILL.md and
|
|
3205
|
+
* the sidecar, causing each GSD skill to appear twice in autocomplete. This
|
|
3206
|
+
* function removes those stale sidecars and — if the agents/ subdirectory is
|
|
3207
|
+
* now empty — prunes it too.
|
|
3203
3208
|
*
|
|
3204
|
-
*
|
|
3205
|
-
*
|
|
3206
|
-
*
|
|
3207
|
-
*
|
|
3208
|
-
*
|
|
3209
|
-
*
|
|
3210
|
-
*
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
// quotes, newlines, control characters, and Unicode escapes.
|
|
3217
|
-
return [
|
|
3218
|
-
'interface:',
|
|
3219
|
-
` display_name: ${yamlQuote(displayName)}`,
|
|
3220
|
-
` short_description: ${yamlQuote(shortDescription)}`,
|
|
3221
|
-
'',
|
|
3222
|
-
].join('\n');
|
|
3223
|
-
}
|
|
3224
|
-
|
|
3225
|
-
/**
|
|
3226
|
-
* Write agents/openai.yaml TUI chip metadata for each gsd-* skill directory.
|
|
3227
|
-
*
|
|
3228
|
-
* Called after layout-driven skill install for Codex. Iterates every gsd-*
|
|
3229
|
-
* skill directory in skillsDir, reads the SKILL.md frontmatter to extract the
|
|
3230
|
-
* short-description already emitted by convertClaudeCommandToCodexSkill, then
|
|
3231
|
-
* writes <skill-dir>/agents/openai.yaml using generateCodexSkillMetadataYaml.
|
|
3232
|
-
*
|
|
3233
|
-
* Fails open: individual skill directories that cannot be processed are silently
|
|
3234
|
-
* skipped so a single malformed SKILL.md cannot block the whole install.
|
|
3235
|
-
*
|
|
3236
|
-
* User-owned skill directories (e.g. gsd-dev-preferences) are explicitly
|
|
3237
|
-
* skipped so existing user-authored agents/openai.yaml files are never
|
|
3238
|
-
* overwritten. These dirs are listed in the same USER_OWNED_SKILL_DIRS
|
|
3239
|
-
* constant used by installOpencodeFamilySkills.
|
|
3240
|
-
*
|
|
3241
|
-
* The YAML-quoted description value is unescaped before embedding so that
|
|
3242
|
-
* YAML escape sequences (e.g. \" in a double-quoted scalar) become the
|
|
3243
|
-
* literal characters they represent rather than being double-escaped in the
|
|
3244
|
-
* output.
|
|
3209
|
+
* Behaviour:
|
|
3210
|
+
* - Returns immediately if skillsDir does not exist (fails open).
|
|
3211
|
+
* - Only touches directories whose names start with "gsd-".
|
|
3212
|
+
* - Skips user-owned dirs (gsd-dev-preferences) — their agents/ content is
|
|
3213
|
+
* never modified, mirroring the same USER_OWNED_SKILL_DIRS guard used by
|
|
3214
|
+
* installOpencodeFamilySkills.
|
|
3215
|
+
* - For each managed gsd-* dir, if agents/openai.yaml exists, deletes it.
|
|
3216
|
+
* - If agents/ is now empty, removes the directory; if it still contains
|
|
3217
|
+
* other files (e.g. user-added content), leaves it in place.
|
|
3218
|
+
* - Non-gsd-* dirs and their agents/ content are never touched.
|
|
3219
|
+
* - Individual failures are caught and swallowed so a single bad dir cannot
|
|
3220
|
+
* block the install (fail-open, matching the original design).
|
|
3245
3221
|
*
|
|
3246
3222
|
* @param {string} skillsDir - Path to the skills/ directory (e.g. ~/.codex/skills)
|
|
3247
3223
|
*/
|
|
3248
|
-
function
|
|
3224
|
+
function cleanupCodexSkillMetadataSidecars(skillsDir) {
|
|
3249
3225
|
if (!fs.existsSync(skillsDir)) return;
|
|
3250
3226
|
// Mirror the user-owned list from installOpencodeFamilySkills (#2973).
|
|
3251
3227
|
// We MUST skip these dirs — their contents are user-generated and must
|
|
3252
|
-
// never be
|
|
3228
|
+
// never be modified by GSD's install path.
|
|
3253
3229
|
const _userOwnedSkillDirs = new Set(['gsd-dev-preferences']);
|
|
3254
3230
|
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
3255
3231
|
if (!entry.isDirectory() || !entry.name.startsWith('gsd-')) continue;
|
|
3256
3232
|
if (_userOwnedSkillDirs.has(entry.name)) continue; // preserve user content
|
|
3257
|
-
const
|
|
3258
|
-
const
|
|
3233
|
+
const agentsSubdir = path.join(skillsDir, entry.name, 'agents');
|
|
3234
|
+
const sidecarPath = path.join(agentsSubdir, 'openai.yaml');
|
|
3259
3235
|
try {
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
if (
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
const metaBlock = metaMatch[1];
|
|
3272
|
-
const sdMatch = metaBlock.match(/^[ \t]+short-description\s*:\s*(.+)$/m);
|
|
3273
|
-
if (sdMatch) {
|
|
3274
|
-
// Unescape YAML double-quoted scalar escapes before embedding.
|
|
3275
|
-
// convertClaudeCommandToCodexSkill always emits a double-quoted
|
|
3276
|
-
// value (via yamlQuote) so only double-quote unescaping is needed.
|
|
3277
|
-
let raw = sdMatch[1].trim();
|
|
3278
|
-
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
3279
|
-
// Strip outer double-quotes and decode \" → " and \\ → \
|
|
3280
|
-
raw = raw.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
3281
|
-
} else {
|
|
3282
|
-
// Single-quoted or unquoted: strip surrounding quotes/whitespace
|
|
3283
|
-
raw = raw.replace(/^["']|["']$/g, '');
|
|
3284
|
-
}
|
|
3285
|
-
shortDesc = raw;
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
if (!shortDesc) {
|
|
3289
|
-
shortDesc = extractFrontmatterField(frontmatter, 'description') || '';
|
|
3290
|
-
}
|
|
3291
|
-
}
|
|
3292
|
-
if (!shortDesc) {
|
|
3293
|
-
shortDesc = `Run GSD workflow ${entry.name}.`;
|
|
3236
|
+
// Symlink guard: if agents/ is a symlink pointing outside the skills tree,
|
|
3237
|
+
// deleting through it could escape the tree. Skip this dir entirely.
|
|
3238
|
+
let agentsStat;
|
|
3239
|
+
try { agentsStat = fs.lstatSync(agentsSubdir); } catch (_e) { continue; }
|
|
3240
|
+
if (agentsStat.isSymbolicLink()) continue;
|
|
3241
|
+
if (fs.existsSync(sidecarPath)) {
|
|
3242
|
+
fs.rmSync(sidecarPath);
|
|
3243
|
+
}
|
|
3244
|
+
// Prune the agents/ dir only if it is now empty (leave it if other files remain).
|
|
3245
|
+
if (fs.existsSync(agentsSubdir) && fs.readdirSync(agentsSubdir).length === 0) {
|
|
3246
|
+
fs.rmdirSync(agentsSubdir);
|
|
3294
3247
|
}
|
|
3295
|
-
const yamlContent = generateCodexSkillMetadataYaml(entry.name, shortDesc);
|
|
3296
|
-
const agentsSubdir = path.join(skillDir, 'agents');
|
|
3297
|
-
fs.mkdirSync(agentsSubdir, { recursive: true });
|
|
3298
|
-
fs.writeFileSync(path.join(agentsSubdir, 'openai.yaml'), yamlContent);
|
|
3299
3248
|
} catch (_err) {
|
|
3300
|
-
// Fail open —
|
|
3249
|
+
// Fail open — a single bad dir must not block the install.
|
|
3301
3250
|
}
|
|
3302
3251
|
}
|
|
3303
3252
|
}
|
|
@@ -6843,6 +6792,12 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false) {
|
|
|
6843
6792
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
6844
6793
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
6845
6794
|
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
6795
|
+
// Bare forms (no trailing slash) — use (?![\w-]) instead of \b so that
|
|
6796
|
+
// .claude-plugin / .claudeignore are NOT corrupted (the \b word-boundary
|
|
6797
|
+
// fires between 'e' and '-', which rewrites .claude-plugin → .cursor-plugin).
|
|
6798
|
+
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
6799
|
+
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
6800
|
+
content = content.replace(/\.\/\.claude(?![\w-])/g, `./${dirName}`);
|
|
6846
6801
|
content = content.replace(/~\/\.cursor\//g, pathPrefix);
|
|
6847
6802
|
content = processAttribution(content, getCommitAttribution(runtime));
|
|
6848
6803
|
break;
|
|
@@ -7163,18 +7118,23 @@ function _runLegacyInstallMigrations(runtime, configDir, scope = 'global') {
|
|
|
7163
7118
|
* @param {'global'|'local'} [scope]
|
|
7164
7119
|
*/
|
|
7165
7120
|
function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
|
|
7166
|
-
//
|
|
7167
|
-
//
|
|
7168
|
-
// Claude
|
|
7169
|
-
//
|
|
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).
|
|
7170
7127
|
// #2973 / Codex review (bd1f06c9): preserve user-owned dev-preferences.md
|
|
7171
7128
|
// before destructive wipe. Migration to skills/gsd-dev-preferences/SKILL.md
|
|
7172
7129
|
// is deferred and returned so the caller can apply it AFTER layout-driven
|
|
7173
7130
|
// removal — this prevents the layout's gsd-* prefix removal from wiping the
|
|
7174
7131
|
// freshly created skill dir (same pattern as _runLegacyInstallMigrations).
|
|
7175
7132
|
let savedLegacyArtifacts = null;
|
|
7176
|
-
// commands/gsd/ is a legacy location for Qwen, Hermes, and Claude
|
|
7177
|
-
// 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.
|
|
7178
7138
|
const isLegacyCommandsGsd = runtime === 'qwen' || runtime === 'hermes' || (runtime === 'claude' && scope === 'global');
|
|
7179
7139
|
if (isLegacyCommandsGsd) {
|
|
7180
7140
|
const legacyCommandsGsd = path.join(configDir, 'commands', 'gsd');
|
|
@@ -8111,23 +8071,37 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8111
8071
|
} catch { /* best-effort */ }
|
|
8112
8072
|
}
|
|
8113
8073
|
|
|
8114
|
-
// 1c. Claude local: remove commands/
|
|
8115
|
-
//
|
|
8116
|
-
// flat command dirs (OpenCode/Kilo). Claude local files use no prefix inside
|
|
8117
|
-
// the namespaced directory, so layout does not remove them. Handle inline.
|
|
8118
|
-
// 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.
|
|
8119
8076
|
if (!isGlobal && runtime === 'claude') {
|
|
8120
|
-
const
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
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 });
|
|
8125
8099
|
removedCount++;
|
|
8126
|
-
console.log(` ${green}✓${reset} Removed commands/gsd/`);
|
|
8127
|
-
if (
|
|
8100
|
+
console.log(` ${green}✓${reset} Removed legacy commands/gsd/`);
|
|
8101
|
+
if (savedDevPrefs) {
|
|
8128
8102
|
try {
|
|
8129
|
-
fs.mkdirSync(
|
|
8130
|
-
fs.writeFileSync(
|
|
8103
|
+
fs.mkdirSync(legacyGsdCommandsDir, { recursive: true });
|
|
8104
|
+
fs.writeFileSync(legacyDevPrefsPath, savedDevPrefs);
|
|
8131
8105
|
console.log(` ${green}✓${reset} Preserved commands/gsd/dev-preferences.md`);
|
|
8132
8106
|
} catch (err) {
|
|
8133
8107
|
console.error(` ${red}✗${reset} Failed to restore dev-preferences.md: ${err.message}`);
|
|
@@ -8894,7 +8868,11 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
8894
8868
|
const isKimi = runtime === 'kimi';
|
|
8895
8869
|
const isHermes = runtime === 'hermes';
|
|
8896
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.
|
|
8897
8874
|
const commandsDir = path.join(configDir, 'commands', 'gsd');
|
|
8875
|
+
const flatCommandsDir = path.join(configDir, 'commands');
|
|
8898
8876
|
const opencodeCommandDir = path.join(configDir, 'command');
|
|
8899
8877
|
// Hermes nests GSD skills under skills/gsd/ as a single category (#2841).
|
|
8900
8878
|
// All other runtimes that use the Codex-style skills layout use a flat skills/ root.
|
|
@@ -8920,17 +8898,27 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
8920
8898
|
if (USER_OWNED_ARTIFACTS.includes(rel)) continue;
|
|
8921
8899
|
manifest.files['gsd-core/' + rel] = hash;
|
|
8922
8900
|
}
|
|
8923
|
-
// Record commands
|
|
8924
|
-
//
|
|
8925
|
-
//
|
|
8926
|
-
//
|
|
8927
|
-
//
|
|
8928
|
-
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)) {
|
|
8929
8907
|
const cmdHashes = generateManifest(commandsDir);
|
|
8930
8908
|
for (const [rel, hash] of Object.entries(cmdHashes)) {
|
|
8931
8909
|
manifest.files['commands/gsd/' + rel] = hash;
|
|
8932
8910
|
}
|
|
8933
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
|
+
}
|
|
8934
8922
|
if ((isOpencode || isKilo) && fs.existsSync(opencodeCommandDir)) {
|
|
8935
8923
|
for (const file of fs.readdirSync(opencodeCommandDir)) {
|
|
8936
8924
|
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
|
@@ -9837,14 +9825,14 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9837
9825
|
const scope = isGlobal ? 'global' : 'local';
|
|
9838
9826
|
installRuntimeArtifacts(runtime, targetDir, scope, _resolvedProfile);
|
|
9839
9827
|
|
|
9840
|
-
// #
|
|
9841
|
-
//
|
|
9842
|
-
//
|
|
9843
|
-
//
|
|
9844
|
-
//
|
|
9845
|
-
//
|
|
9828
|
+
// #1326 — Codex only: remove stale agents/openai.yaml sidecars from managed
|
|
9829
|
+
// gsd-* skill dirs. Prior installs wrote these files so Codex would show a
|
|
9830
|
+
// display name and description in the /skills TUI popup. Recent Codex builds
|
|
9831
|
+
// index BOTH SKILL.md and the sidecar, causing each GSD skill to appear twice
|
|
9832
|
+
// in autocomplete. Cleaning them up fixes the duplication; SKILL.md alone is
|
|
9833
|
+
// sufficient for Codex discovery. User-owned dirs are never touched.
|
|
9846
9834
|
if (isCodex) {
|
|
9847
|
-
|
|
9835
|
+
cleanupCodexSkillMetadataSidecars(path.join(targetDir, 'skills'));
|
|
9848
9836
|
}
|
|
9849
9837
|
|
|
9850
9838
|
// Hermes only: write DESCRIPTION.md for the gsd/ category after layout install
|
|
@@ -10030,18 +10018,59 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10030
10018
|
}
|
|
10031
10019
|
}
|
|
10032
10020
|
} else {
|
|
10033
|
-
// Claude Code local:
|
|
10034
|
-
// 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)
|
|
10035
10026
|
const commandsDir = path.join(targetDir, 'commands');
|
|
10036
10027
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
10037
10028
|
const gsdSrc = _stageSkills(_commandsDir);
|
|
10038
|
-
const
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
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)`);
|
|
10043
10056
|
} else {
|
|
10044
|
-
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
|
+
}
|
|
10045
10074
|
}
|
|
10046
10075
|
|
|
10047
10076
|
// Clean up any stale skills/ from a previous local install
|
|
@@ -12126,8 +12155,7 @@ module.exports = {
|
|
|
12126
12155
|
convertClaudeToGeminiAgent,
|
|
12127
12156
|
convertClaudeAgentToCodexAgent,
|
|
12128
12157
|
generateCodexAgentToml,
|
|
12129
|
-
|
|
12130
|
-
writeCodexSkillMetadataFiles,
|
|
12158
|
+
cleanupCodexSkillMetadataSidecars,
|
|
12131
12159
|
generateCodexConfigBlock,
|
|
12132
12160
|
stripGsdFromCodexConfig,
|
|
12133
12161
|
migrateCodexHooksMapFormat,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: gsd:plan-phase
|
|
3
3
|
description: Create detailed phase plan (PLAN.md) with verification loop
|
|
4
4
|
argument-hint: "[phase] [--auto] [--research] [--skip-research] [--research-phase <N>] [--view] [--gaps] [--skip-verify] [--prd <file>] [--ingest <path-or-glob>] [--ingest-format <auto|nygard|madr|narrative>] [--reviews] [--text] [--tdd] [--mvp]"
|
|
5
|
-
effort:
|
|
5
|
+
effort: max
|
|
6
6
|
allowed-tools:
|
|
7
7
|
- Read
|
|
8
8
|
- Write
|
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
|
}
|