fix(gsd): enforce backtick file paths in task plan IO sections (#1548)
* fix(gsd): enforce backtick file paths in task plan IO sections The reactive task graph (ADR-004) derives dependencies from backtick-wrapped file paths in ## Inputs and ## Expected Output sections. Without concrete paths, the graph is ambiguous and falls back to sequential execution. Changes: - task-plan.md template: add comments explaining paths are machine-parsed - plan-slice.md prompt: explicitly instruct planner to write backtick file paths in IO sections, add self-audit check for path presence - observability-validator.ts: new validation rules missing_output_file_paths (warning) and missing_input_file_paths (info) catch plans without paths - plan-quality-validator.test.ts: 4 new test cases for IO path validation * fix(ci): increase max_tokens and add JSON parse error handling in ai-triage max_tokens: 300 was too low, causing truncated JSON responses from Claude that failed to parse. Bumped to 1024 and added try/catch with raw text logging for easier debugging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
567751471a
commit
e4cd141503
5 changed files with 159 additions and 6 deletions
10
.github/workflows/ai-triage.yml
vendored
10
.github/workflows/ai-triage.yml
vendored
|
|
@ -105,7 +105,7 @@ jobs:
|
|||
},
|
||||
body: JSON.stringify({
|
||||
model: 'claude-haiku-4-5-20251001',
|
||||
max_tokens: 300,
|
||||
max_tokens: 1024,
|
||||
messages: [{ role: 'user', content: prompt }]
|
||||
})
|
||||
});
|
||||
|
|
@ -126,7 +126,13 @@ jobs:
|
|||
return;
|
||||
}
|
||||
|
||||
const result = JSON.parse(jsonMatch[0]);
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(jsonMatch[0]);
|
||||
} catch (e) {
|
||||
core.setFailed(`JSON parse error: ${e.message}\nRaw text: ${text}`);
|
||||
return;
|
||||
}
|
||||
core.info(`Triage result: ${JSON.stringify(result, null, 2)}`);
|
||||
|
||||
// Apply labels
|
||||
|
|
|
|||
|
|
@ -235,6 +235,33 @@ export function validateTaskPlanContent(file: string, content: string): Validati
|
|||
}
|
||||
}
|
||||
|
||||
// Rule: Inputs and Expected Output should contain backtick-wrapped file paths
|
||||
const inputsSection = getSection(content, "Inputs", 2);
|
||||
const outputSection = getSection(content, "Expected Output", 2);
|
||||
const backtickPathPattern = /`[^`]*[./][^`]*`/;
|
||||
|
||||
if (outputSection === null || !backtickPathPattern.test(outputSection)) {
|
||||
issues.push({
|
||||
severity: "warning",
|
||||
scope: "task-plan",
|
||||
file,
|
||||
ruleId: "missing_output_file_paths",
|
||||
message: "Task plan `## Expected Output` is missing or has no backtick-wrapped file paths.",
|
||||
suggestion: "List concrete output file paths in backticks (e.g. `src/types.ts`). These are machine-parsed to derive task dependencies.",
|
||||
});
|
||||
}
|
||||
|
||||
if (inputsSection !== null && inputsSection.trim().length > 0 && !backtickPathPattern.test(inputsSection)) {
|
||||
issues.push({
|
||||
severity: "info",
|
||||
scope: "task-plan",
|
||||
file,
|
||||
ruleId: "missing_input_file_paths",
|
||||
message: "Task plan `## Inputs` has content but no backtick-wrapped file paths.",
|
||||
suggestion: "List input file paths in backticks (e.g. `src/config.json`). These are machine-parsed to derive task dependencies.",
|
||||
});
|
||||
}
|
||||
|
||||
// ── Observability rules (gated by runtime relevance) ──
|
||||
|
||||
const relevant = textSuggestsObservabilityRelevant(content);
|
||||
|
|
|
|||
|
|
@ -61,13 +61,14 @@ Then:
|
|||
- a concrete, action-oriented title
|
||||
- the inline task entry fields defined in the plan.md template (Why / Files / Do / Verify / Done when)
|
||||
- a matching task plan file with description, steps, must-haves, verification, inputs, and expected output
|
||||
- **Inputs and Expected Output must list concrete backtick-wrapped file paths** (e.g. `` `src/types.ts` ``). These are machine-parsed to derive task dependencies — vague prose without paths breaks parallel execution. Every task must have at least one output file path.
|
||||
- Observability Impact section **only if the task touches runtime boundaries, async flows, or error paths** — omit it otherwise
|
||||
6. Write `{{outputPath}}`
|
||||
7. Write individual task plans in `{{slicePath}}/tasks/`: `T01-PLAN.md`, `T02-PLAN.md`, etc.
|
||||
8. **Self-audit the plan.** Walk through each check — if any fail, fix the plan files before moving on:
|
||||
- **Completion semantics:** If every task were completed exactly as written, the slice goal/demo should actually be true.
|
||||
- **Requirement coverage:** Every must-have in the slice maps to at least one task. No must-have is orphaned. If `REQUIREMENTS.md` exists, every Active requirement this slice owns maps to at least one task.
|
||||
- **Task completeness:** Every task has steps, must-haves, verification, inputs, and expected output — none are blank or vague.
|
||||
- **Task completeness:** Every task has steps, must-haves, verification, inputs, and expected output — none are blank or vague. Inputs and Expected Output list backtick-wrapped file paths, not prose descriptions.
|
||||
- **Dependency correctness:** Task ordering is consistent. No task references work from a later task.
|
||||
- **Key links planned:** For every pair of artifacts that must connect, there is an explicit step that wires them.
|
||||
- **Scope sanity:** Target 2–5 steps and 3–8 files per task. 10+ steps or 12+ files — must split. Each task must be completable in a single fresh context window.
|
||||
|
|
|
|||
|
|
@ -42,11 +42,19 @@ estimated_files: {{estimatedFiles}}
|
|||
|
||||
## Inputs
|
||||
|
||||
<!-- Every input MUST be a backtick-wrapped file path. These paths are machine-parsed to
|
||||
derive task dependencies — vague descriptions without paths break dependency detection.
|
||||
For the first task in a slice with no prior task outputs, list the existing source files
|
||||
this task reads or modifies. -->
|
||||
|
||||
- `{{filePath}}` — {{whatThisTaskNeedsFromPriorWork}}
|
||||
- {{priorTaskSummaryInsight}}
|
||||
|
||||
## Expected Output
|
||||
|
||||
<!-- This task should produce a real increment toward making the slice goal/demo true. A full slice plan should not be able to mark every task complete while the claimed slice behavior still does not work at the stated proof level. -->
|
||||
<!-- Every output MUST be a backtick-wrapped file path — the specific files this task creates
|
||||
or modifies. These paths are machine-parsed to derive task dependencies.
|
||||
This task should produce a real increment toward making the slice goal/demo true. A full
|
||||
slice plan should not be able to mark every task complete while the claimed slice behavior
|
||||
still does not work at the stated proof level. -->
|
||||
|
||||
- `{{filePath}}` — {{whatThisTaskShouldProduceOrModify}}
|
||||
- `{{filePath}}` — {{whatThisTaskCreatesOrModifies}}
|
||||
|
|
|
|||
|
|
@ -360,4 +360,115 @@ console.log('\n=== Clean slice plan: no plan-quality issues ===');
|
|||
assertEq(planQualityIssues.length, 0, 'clean slice plan produces no empty_task_entry issues');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// validateTaskPlanContent — missing output file paths
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
console.log('\n=== validateTaskPlanContent: missing output file paths ===');
|
||||
{
|
||||
const content = `# T01: Some Task
|
||||
|
||||
## Description
|
||||
|
||||
Do something.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Do the thing
|
||||
|
||||
## Verification
|
||||
|
||||
- Check it works
|
||||
|
||||
## Expected Output
|
||||
|
||||
This task produces the main output.
|
||||
`;
|
||||
|
||||
const issues = validateTaskPlanContent('T01-PLAN.md', content);
|
||||
const outputIssues = issues.filter(i => i.ruleId === 'missing_output_file_paths');
|
||||
assertTrue(outputIssues.length >= 1, 'Expected Output without file paths triggers missing_output_file_paths');
|
||||
}
|
||||
|
||||
console.log('\n=== validateTaskPlanContent: valid output file paths ===');
|
||||
{
|
||||
const content = `# T01: Some Task
|
||||
|
||||
## Description
|
||||
|
||||
Do something.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Do the thing
|
||||
|
||||
## Verification
|
||||
|
||||
- Check it works
|
||||
|
||||
## Expected Output
|
||||
|
||||
- \`src/types.ts\` — New type definitions
|
||||
`;
|
||||
|
||||
const issues = validateTaskPlanContent('T01-PLAN.md', content);
|
||||
const outputIssues = issues.filter(i => i.ruleId === 'missing_output_file_paths');
|
||||
assertEq(outputIssues.length, 0, 'Expected Output with file paths does not trigger warning');
|
||||
}
|
||||
|
||||
console.log('\n=== validateTaskPlanContent: missing input file paths (info severity) ===');
|
||||
{
|
||||
const content = `# T01: Some Task
|
||||
|
||||
## Description
|
||||
|
||||
Do something.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Do the thing
|
||||
|
||||
## Verification
|
||||
|
||||
- Check it works
|
||||
|
||||
## Inputs
|
||||
|
||||
Prior task summary insights about the architecture.
|
||||
|
||||
## Expected Output
|
||||
|
||||
- \`src/output.ts\` — Output file
|
||||
`;
|
||||
|
||||
const issues = validateTaskPlanContent('T01-PLAN.md', content);
|
||||
const inputIssues = issues.filter(i => i.ruleId === 'missing_input_file_paths');
|
||||
assertTrue(inputIssues.length >= 1, 'Inputs without file paths triggers missing_input_file_paths');
|
||||
if (inputIssues.length > 0) {
|
||||
assertEq(inputIssues[0].severity, 'info', 'missing_input_file_paths is info severity (not warning)');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n=== validateTaskPlanContent: no Expected Output section at all ===');
|
||||
{
|
||||
const content = `# T01: Some Task
|
||||
|
||||
## Description
|
||||
|
||||
Do something.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Do the thing
|
||||
|
||||
## Verification
|
||||
|
||||
- Check it works
|
||||
`;
|
||||
|
||||
const issues = validateTaskPlanContent('T01-PLAN.md', content);
|
||||
const outputIssues = issues.filter(i => i.ruleId === 'missing_output_file_paths');
|
||||
assertTrue(outputIssues.length >= 1, 'Missing Expected Output section triggers missing_output_file_paths');
|
||||
}
|
||||
|
||||
report();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue