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:
36
commands/gsd/import.md
Normal file
36
commands/gsd/import.md
Normal 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>
|
||||
274
get-shit-done/workflows/import.md
Normal file
274
get-shit-done/workflows/import.md
Normal 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
|
||||
121
tests/import-command.test.cjs
Normal file
121
tests/import-command.test.cjs
Normal 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'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user