feat(commands): add external plan import command /gsd-import (#1801)

* feat(commands): add external plan import command /gsd-import

Adds a new /gsd-import command for importing external plan files into
the GSD planning system with conflict detection against PROJECT.md
decisions and CONTEXT.md locked decisions.

Scoped to --from mode only (plan file import). Uses validatePath()
from security.cjs for file path validation. Surfaces all conflicts
before writing and never auto-resolves. Handles missing PROJECT.md
gracefully by skipping constraint checks.

--prd mode (PRD extraction) is noted as future work.

Closes #1731

* fix(commands): address review feedback for /gsd-import

- Add structural tests for command/workflow files (13 assertions)
- Add REQUIREMENTS.md to conflict detection context loading
- Replace security.cjs CLI invocation with inline path validation
- Move PBR naming check from blocker list to conversion step
- Add Edit to allowed-tools for ROADMAP.md/STATE.md patching
- Remove emoji from completion banner and validation message
This commit is contained in:
Rezolv
2026-04-05 18:33:24 -04:00
committed by GitHub
parent 567736f23d
commit 02d2533eac
3 changed files with 431 additions and 0 deletions

36
commands/gsd/import.md Normal file
View File

@@ -0,0 +1,36 @@
---
name: gsd:import
description: Ingest external plans with conflict detection against project decisions before writing anything.
argument-hint: "--from <filepath>"
allowed-tools:
- Read
- Write
- Edit
- Bash
- Glob
- Grep
- AskUserQuestion
- Task
---
<objective>
Import external plan files into the GSD planning system with conflict detection against PROJECT.md decisions.
- **--from**: Import an external plan file, detect conflicts, write as GSD PLAN.md, validate via gsd-plan-checker.
Future: `--prd` mode for PRD extraction is planned for a follow-up PR.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/import.md
@~/.claude/get-shit-done/references/ui-brand.md
@~/.claude/get-shit-done/references/gate-prompts.md
</execution_context>
<context>
$ARGUMENTS
</context>
<process>
Execute the import workflow end-to-end.
</process>

View File

@@ -0,0 +1,274 @@
# Import Workflow
External plan ingestion with conflict detection and agent delegation.
- **--from**: Import external plan → conflict detection → write PLAN.md → validate via gsd-plan-checker
Future: `--prd` mode (PRD extraction into PROJECT.md + REQUIREMENTS.md + ROADMAP.md) is planned for a follow-up PR.
---
<step name="banner">
Display the stage banner:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► IMPORT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
</step>
<step name="parse_arguments">
Parse `$ARGUMENTS` to determine the execution mode:
- If `--from` is present: extract FILEPATH (the next token after `--from`), set MODE=plan
- If `--prd` is present: display message that `--prd` is not yet implemented and exit:
```
GSD > --prd mode is planned for a future release. Use --from to import plan files.
```
- If neither flag is found: display usage and exit:
```
Usage: /gsd-import --from <path>
--from <path> Import an external plan file into GSD format
```
**Validate the file path:**
Verify the path does not contain traversal sequences and the file exists:
```bash
case "{FILEPATH}" in
*..* ) echo "SECURITY_ERROR: path contains traversal sequence"; exit 1 ;;
esac
test -f "{FILEPATH}" || echo "FILE_NOT_FOUND"
```
If FILE_NOT_FOUND: display error and exit:
```
╔══════════════════════════════════════════════════════════════╗
║ ERROR ║
╚══════════════════════════════════════════════════════════════╝
File not found: {FILEPATH}
**To fix:** Verify the file path and try again.
```
</step>
---
## Path A: MODE=plan (--from)
<step name="plan_load_context">
Load project context for conflict detection:
1. Read `.planning/ROADMAP.md` — extract phase structure, phase numbers, dependencies
2. Read `.planning/PROJECT.md` — extract project constraints, tech stack, scope boundaries.
**If PROJECT.md does not exist:** skip constraint checks that rely on it and display:
```
GSD > Note: No PROJECT.md found. Conflict checks against project constraints will be skipped.
```
3. Read `.planning/REQUIREMENTS.md` — extract existing requirements for overlap and contradiction checks.
**If REQUIREMENTS.md does not exist:** skip requirement conflict checks and continue.
4. Glob for all CONTEXT.md files across phase directories:
```bash
find .planning/phases/ -name "*-CONTEXT.md" -o -name "CONTEXT.md" 2>/dev/null
```
Read each CONTEXT.md found — extract locked decisions (any decision in a `<decisions>` block)
Store loaded context for conflict detection in the next step.
</step>
<step name="plan_read_input">
Read the imported file at FILEPATH.
Determine the format:
- **GSD PLAN.md format**: Has YAML frontmatter with `phase:`, `plan:`, `type:` fields
- **Freeform document**: Any other format (markdown spec, design doc, task list, etc.)
Extract from the imported content:
- **Phase target**: Which phase this plan belongs to (from frontmatter or inferred from content)
- **Plan objectives**: What the plan aims to accomplish
- **Tasks listed**: Individual work items described in the plan
- **Files modified**: Any files mentioned as targets
- **Dependencies**: Any referenced prerequisites
</step>
<step name="plan_conflict_detection">
Run conflict checks against the loaded project context. Output as a plain-text conflict report using [BLOCKER], [WARNING], and [INFO] labels. Do NOT use markdown tables (no `|---|` format).
### BLOCKER checks (any one prevents import):
- Plan targets a phase number that does not exist in ROADMAP.md → [BLOCKER]
- Plan specifies a tech stack that contradicts PROJECT.md constraints → [BLOCKER]
- Plan contradicts a locked decision in any CONTEXT.md `<decisions>` block → [BLOCKER]
- Plan contradicts an existing requirement in REQUIREMENTS.md → [BLOCKER]
### WARNING checks (user confirmation required):
- Plan partially overlaps existing requirement coverage in REQUIREMENTS.md → [WARNING]
- Plan has `depends_on` referencing plans that are not yet complete → [WARNING]
- Plan modifies files that overlap with existing incomplete plans → [WARNING]
- Plan phase number conflicts with existing phase numbering in ROADMAP.md → [WARNING]
### INFO checks (informational, no action needed):
- Plan uses a library not currently in the project tech stack → [INFO]
- Plan adds a new phase to the ROADMAP.md structure → [INFO]
Display the full Conflict Detection Report:
```
## Conflict Detection Report
### BLOCKERS ({N})
[BLOCKER] {Short title}
Found: {what the imported plan says}
Expected: {what project context requires}
→ {Specific action to resolve}
### WARNINGS ({N})
[WARNING] {Short title}
Found: {what was detected}
Impact: {what could go wrong}
→ {Suggested action}
### INFO ({N})
[INFO] {Short title}
Note: {relevant information}
```
**If any [BLOCKER] exists:**
Display:
```
GSD > BLOCKED: {N} blockers must be resolved before import can proceed.
```
Exit WITHOUT writing any files. This is the safety gate — no PLAN.md is written when blockers exist.
**If only WARNINGS and/or INFO (no blockers):**
Ask via AskUserQuestion using the approve-revise-abort pattern:
- question: "Review the warnings above. Proceed with import?"
- header: "Approve?"
- options: Approve | Abort
If user selects "Abort": exit cleanly with message "Import cancelled."
</step>
<step name="plan_convert">
Convert the imported content to GSD PLAN.md format.
Ensure the PLAN.md has all required frontmatter fields:
```yaml
---
phase: "{NN}-{slug}"
plan: "{NN}-{MM}"
type: "feature|refactor|config|test|docs"
wave: 1
depends_on: []
files_modified: []
autonomous: true
must_haves:
truths: []
artifacts: []
---
```
**Reject PBR naming conventions in source content:**
If the imported plan references PBR plan naming (e.g., `PLAN-01.md`, `plan-01.md`), rename all references to GSD `{NN}-{MM}-PLAN.md` convention during conversion.
Apply GSD naming convention for the output filename:
- Format: `{NN}-{MM}-PLAN.md` (e.g., `04-01-PLAN.md`)
- NEVER use `PLAN-01.md`, `plan-01.md`, or any other format
- NN = phase number (zero-padded), MM = plan number within the phase (zero-padded)
Determine the target directory:
```
.planning/phases/{NN}-{slug}/
```
If the directory does not exist, create it:
```bash
mkdir -p ".planning/phases/{NN}-{slug}/"
```
Write the PLAN.md file to the target directory.
</step>
<step name="plan_validate">
Delegate validation to gsd-plan-checker:
```
Task({
subagent_type: "gsd-plan-checker",
prompt: "Validate: .planning/phases/{phase}/{plan}-PLAN.md — check frontmatter completeness, task structure, and GSD conventions. Report any issues."
})
```
If the checker returns errors:
- Display the errors to the user
- Ask the user to resolve issues before the plan is considered imported
- Do not delete the written file — the user can fix and re-validate manually
If the checker returns clean:
- Display: "Plan validation passed"
</step>
<step name="plan_finalize">
Update `.planning/ROADMAP.md` to reflect the new plan:
- Add the plan to the Plans list under the correct phase section
- Include the plan name and description
Update `.planning/STATE.md` if appropriate (e.g., increment total plan count).
Commit the imported plan and updated files:
```bash
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs({phase}): import plan from {basename FILEPATH}" --files .planning/phases/{phase}/{plan}-PLAN.md .planning/ROADMAP.md
```
Display completion:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► IMPORT COMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
Show: plan filename written, phase directory, validation result, next steps.
</step>
---
## Anti-Patterns
Do NOT:
- Use markdown tables (`|---|`) in the conflict detection report — use plain-text [BLOCKER]/[WARNING]/[INFO] labels
- Write PLAN.md files as `PLAN-01.md` or `plan-01.md` — always use `{NN}-{MM}-PLAN.md`
- Use `pbr:plan-checker` or `pbr:planner` — use `gsd-plan-checker` and `gsd-planner`
- Write `.planning/.active-skill` — this is a PBR pattern with no GSD equivalent
- Reference `pbr-tools`, `pbr:`, or `PLAN-BUILD-RUN` anywhere
- Write any PLAN.md file when blockers exist — the safety gate must hold
- Skip path validation on the --from file argument

View File

@@ -0,0 +1,121 @@
/**
* Import Command Tests — import-command.test.cjs
*
* Structural assertions for the /gsd-import command and workflow files.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const CMD_PATH = path.join(__dirname, '..', 'commands', 'gsd', 'import.md');
const WF_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'import.md');
// ─── File Existence ────────────────────────────────────────────────────────────
describe('import command file structure', () => {
test('command file exists', () => {
assert.ok(fs.existsSync(CMD_PATH), 'commands/gsd/import.md should exist');
});
test('workflow file exists', () => {
assert.ok(fs.existsSync(WF_PATH), 'get-shit-done/workflows/import.md should exist');
});
});
// ─── Command Frontmatter ───────────────────────────────────────────────────────
describe('import command frontmatter', () => {
const content = fs.readFileSync(CMD_PATH, 'utf-8');
test('has name field', () => {
assert.match(content, /^name:\s*gsd:import$/m);
});
test('has description field', () => {
assert.match(content, /^description:\s*.+$/m);
});
test('has argument-hint with --from', () => {
assert.match(content, /^argument-hint:.*--from/m);
});
});
// ─── Command References Workflow ───────────────────────────────────────────────
describe('import command references', () => {
const content = fs.readFileSync(CMD_PATH, 'utf-8');
test('references the import workflow', () => {
assert.ok(
content.includes('@~/.claude/get-shit-done/workflows/import.md'),
'command should reference the workflow via @~/.claude/get-shit-done/workflows/import.md'
);
});
});
// ─── Workflow Content ──────────────────────────────────────────────────────────
describe('import workflow content', () => {
const content = fs.readFileSync(WF_PATH, 'utf-8');
test('contains --from mode handling', () => {
assert.ok(
content.includes('--from'),
'workflow should contain --from mode handling'
);
});
test('does NOT contain --prd implementation', () => {
// --prd should be mentioned as deferred/future only, not implemented
assert.ok(
content.includes('--prd'),
'workflow should mention --prd exists'
);
assert.ok(
content.includes('not yet implemented') || content.includes('follow-up PR') || content.includes('future release'),
'workflow should defer --prd to a future release'
);
// Should not have a full "Path B: MODE=prd" implementation section
assert.ok(
!content.includes('## Path B: MODE=prd'),
'workflow should NOT have a Path B implementation for --prd'
);
});
test('references path validation for --from argument', () => {
// After fix: inline path check instead of security.cjs CLI invocation
assert.ok(
content.includes('traversal') || content.includes('validatePath') || content.includes('..'),
'workflow should validate the file path'
);
});
test('includes REQUIREMENTS.md in conflict detection context loading', () => {
assert.ok(
content.includes('REQUIREMENTS.md'),
'workflow should load REQUIREMENTS.md for conflict detection'
);
});
test('includes BLOCKER/WARNING/INFO conflict severity model', () => {
assert.ok(content.includes('[BLOCKER]'), 'workflow should include BLOCKER severity');
assert.ok(content.includes('[WARNING]'), 'workflow should include WARNING severity');
assert.ok(content.includes('[INFO]'), 'workflow should include INFO severity');
});
test('includes plan-checker validation gate', () => {
assert.ok(
content.includes('gsd-plan-checker'),
'workflow should delegate validation to gsd-plan-checker'
);
});
test('no-args usage display is present', () => {
assert.ok(
content.includes('Usage: /gsd-import'),
'workflow should display usage when no arguments provided'
);
});
});