@davidorex/pi-workflows 0.5.0 → 0.9.0
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/README.md +1 -1
- package/agents/audit-finding-verifier.agent.yaml +40 -0
- package/agents/audit-results-router.agent.yaml +30 -0
- package/agents/gap-resolution-assessor.agent.yaml +40 -0
- package/dist/expression.d.ts.map +1 -1
- package/dist/expression.js +6 -0
- package/dist/expression.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/step-agent.d.ts.map +1 -1
- package/dist/step-agent.js +13 -1
- package/dist/step-agent.js.map +1 -1
- package/dist/step-block.d.ts +18 -0
- package/dist/step-block.d.ts.map +1 -0
- package/dist/step-block.js +204 -0
- package/dist/step-block.js.map +1 -0
- package/dist/template-validation.d.ts +25 -0
- package/dist/template-validation.d.ts.map +1 -0
- package/dist/template-validation.js +341 -0
- package/dist/template-validation.js.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow-executor.d.ts.map +1 -1
- package/dist/workflow-executor.js +31 -5
- package/dist/workflow-executor.js.map +1 -1
- package/dist/workflow-sdk.d.ts +1 -0
- package/dist/workflow-sdk.d.ts.map +1 -1
- package/dist/workflow-sdk.js +7 -0
- package/dist/workflow-sdk.js.map +1 -1
- package/dist/workflow-spec.d.ts.map +1 -1
- package/dist/workflow-spec.js +106 -0
- package/dist/workflow-spec.js.map +1 -1
- package/package.json +7 -8
- package/schemas/audit-routing-manifest.schema.json +38 -0
- package/schemas/finding-verification.schema.json +34 -0
- package/schemas/resolution-assessment.schema.json +36 -0
- package/skills/pi-workflows/SKILL.md +1 -1
- package/skills/pi-workflows/references/bundled-resources.md +12 -3
- package/templates/audit-finding-verifier/task.md +59 -0
- package/templates/audit-results-router/task.md +37 -0
- package/templates/gap-resolution-assessor/task.md +48 -0
- package/templates/handoff-writer/task.md +16 -6
- package/templates/phase-author/task.md +8 -0
- package/workflows/create-handoff.workflow.yaml +56 -35
- package/workflows/create-phase.workflow.yaml +27 -32
- package/workflows/do-gap.workflow.yaml +48 -28
- package/workflows/fix-audit.workflow.yaml +89 -127
- package/workflows/gap-to-phase.workflow.yaml +47 -42
- package/workflows/parallel-analysis.workflow.yaml +20 -8
- package/workflows/plan-from-requirements.workflow.yaml +16 -27
|
@@ -13,21 +13,26 @@ input:
|
|
|
13
13
|
file: .project/gaps.json
|
|
14
14
|
array: gaps
|
|
15
15
|
filter: { status: open }
|
|
16
|
-
label: id
|
|
16
|
+
label: [id, description]
|
|
17
17
|
value: id
|
|
18
18
|
|
|
19
19
|
steps:
|
|
20
|
+
load-blocks:
|
|
21
|
+
block:
|
|
22
|
+
read: gaps
|
|
23
|
+
output:
|
|
24
|
+
format: json
|
|
25
|
+
|
|
20
26
|
load:
|
|
21
27
|
command: |
|
|
22
|
-
node
|
|
23
|
-
|
|
24
|
-
const gapId = process.argv[
|
|
25
|
-
const
|
|
26
|
-
const gap = data.gaps.find(g => g.id === gapId);
|
|
28
|
+
node -e "
|
|
29
|
+
const gaps = JSON.parse(process.argv[1]);
|
|
30
|
+
const gapId = process.argv[2];
|
|
31
|
+
const gap = gaps.gaps.find(g => g.id === gapId);
|
|
27
32
|
if (!gap) { console.error('Gap not found: ' + gapId); process.exit(1); }
|
|
28
|
-
if (gap.status === 'resolved') { console.error('Gap already resolved
|
|
33
|
+
if (gap.status === 'resolved') { console.error('Gap already resolved'); process.exit(1); }
|
|
29
34
|
console.log(JSON.stringify({ gap }));
|
|
30
|
-
" '${{ input.gap_id }}'
|
|
35
|
+
" '${{ steps.load-blocks.output | json }}' '${{ input.gap_id }}'
|
|
31
36
|
output:
|
|
32
37
|
format: json
|
|
33
38
|
|
|
@@ -74,7 +79,7 @@ steps:
|
|
|
74
79
|
format: json
|
|
75
80
|
schema: ../schemas/execution-results.schema.json
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
run-checks:
|
|
78
83
|
command: |
|
|
79
84
|
node --experimental-strip-types -e "
|
|
80
85
|
import fs from 'fs';
|
|
@@ -100,7 +105,9 @@ steps:
|
|
|
100
105
|
if (stderr.includes('error')) errors.push('Lint errors in ' + f);
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
|
-
} catch {
|
|
108
|
+
} catch (e) {
|
|
109
|
+
errors.push('Could not determine changed files: ' + (e.message || 'unknown'));
|
|
110
|
+
}
|
|
104
111
|
|
|
105
112
|
// Validate blocks (JSON parsability)
|
|
106
113
|
try {
|
|
@@ -115,7 +122,9 @@ steps:
|
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
|
-
} catch {
|
|
125
|
+
} catch (e) {
|
|
126
|
+
errors.push('Block validation error: ' + (e.message || 'unknown'));
|
|
127
|
+
}
|
|
119
128
|
|
|
120
129
|
const passed = errors.length === 0;
|
|
121
130
|
console.log(JSON.stringify({ status: passed ? 'passed' : 'failed', errors, passed }));
|
|
@@ -123,33 +132,44 @@ steps:
|
|
|
123
132
|
output:
|
|
124
133
|
format: json
|
|
125
134
|
|
|
135
|
+
assess-resolution:
|
|
136
|
+
when: "${{ steps.run-checks.output.passed }}"
|
|
137
|
+
agent: gap-resolution-assessor
|
|
138
|
+
input:
|
|
139
|
+
gap: "${{ steps.load.output.gap }}"
|
|
140
|
+
investigation: "${{ steps.investigate.output }}"
|
|
141
|
+
implementation_results: "${{ steps.implement.output }}"
|
|
142
|
+
check_results: "${{ steps.run-checks.output }}"
|
|
143
|
+
context: [implement]
|
|
144
|
+
output:
|
|
145
|
+
format: json
|
|
146
|
+
schema: ../schemas/resolution-assessment.schema.json
|
|
147
|
+
|
|
126
148
|
route:
|
|
127
|
-
when: "${{ steps.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
fs.writeFileSync(gapsPath, JSON.stringify(data, null, 2) + '\n');
|
|
138
|
-
console.log(JSON.stringify({ routed: true, gap_id: gapId, status: 'resolved' }));
|
|
139
|
-
" '${{ input.gap_id }}'
|
|
149
|
+
when: "${{ steps.assess-resolution.output.resolved }}"
|
|
150
|
+
block:
|
|
151
|
+
update:
|
|
152
|
+
name: gaps
|
|
153
|
+
key: gaps
|
|
154
|
+
match:
|
|
155
|
+
id: "${{ input.gap_id }}"
|
|
156
|
+
set:
|
|
157
|
+
status: resolved
|
|
158
|
+
resolved_by: "${{ steps.assess-resolution.output.resolution_summary }}"
|
|
140
159
|
output:
|
|
141
160
|
format: json
|
|
142
161
|
|
|
143
162
|
check:
|
|
144
|
-
when: "${{ steps.route.output.
|
|
163
|
+
when: "${{ steps.route.output.updated }}"
|
|
145
164
|
gate:
|
|
146
|
-
check: "test '${{ steps.
|
|
165
|
+
check: "test '${{ steps.assess-resolution.output.resolved }}' = 'true'"
|
|
147
166
|
onFail: fail
|
|
148
167
|
|
|
149
168
|
completion:
|
|
150
169
|
message: |
|
|
151
|
-
Gap ${{ input.gap_id }} — ${{ steps.
|
|
170
|
+
Gap ${{ input.gap_id }} — ${{ steps.assess-resolution.output.verdict }}
|
|
152
171
|
Investigation complexity: ${{ steps.investigate.output.complexity }}
|
|
153
172
|
Specs implemented: ${{ steps.decompose.output.specs | length }}
|
|
173
|
+
Resolution: ${{ steps.assess-resolution.output.resolution_summary }}
|
|
154
174
|
include:
|
|
155
|
-
- steps.
|
|
175
|
+
- steps.assess-resolution.output
|
|
@@ -3,41 +3,35 @@ description: Fix audit findings grouped by related clusters
|
|
|
3
3
|
version: "1"
|
|
4
4
|
|
|
5
5
|
steps:
|
|
6
|
-
load:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
try { conformance_reference = JSON.parse(fs.readFileSync('.project/conformance-reference.json', 'utf8')); } catch {}
|
|
17
|
-
console.log(JSON.stringify({ audit, conformance_reference, audit_path: auditPath }));
|
|
18
|
-
"
|
|
6
|
+
load-audits:
|
|
7
|
+
block:
|
|
8
|
+
readDir: audits
|
|
9
|
+
output:
|
|
10
|
+
format: json
|
|
11
|
+
|
|
12
|
+
load-conformance:
|
|
13
|
+
block:
|
|
14
|
+
read: conformance-reference
|
|
15
|
+
optional: [conformance-reference]
|
|
19
16
|
output:
|
|
20
17
|
format: json
|
|
21
18
|
|
|
19
|
+
select-audit:
|
|
20
|
+
transform:
|
|
21
|
+
mapping:
|
|
22
|
+
audit: "${{ steps.load-audits.output | last }}"
|
|
23
|
+
conformance_reference: "${{ steps.load-conformance.output }}"
|
|
24
|
+
|
|
22
25
|
cluster:
|
|
23
26
|
command: |
|
|
24
|
-
node
|
|
25
|
-
|
|
26
|
-
import { execSync } from 'child_process';
|
|
27
|
-
const audit = JSON.parse(fs.readFileSync('.project/audits/' + fs.readdirSync('.project/audits').filter(f => f.endsWith('.json')).sort().pop(), 'utf8'));
|
|
27
|
+
node -e "
|
|
28
|
+
const audit = JSON.parse(process.argv[1]);
|
|
28
29
|
|
|
29
|
-
// Filter to findings that
|
|
30
|
+
// Filter to findings that have fixes and are not already resolved
|
|
30
31
|
const findings = audit.findings.filter(f => {
|
|
31
32
|
if (!f.fix) return false;
|
|
32
|
-
if (f.fix.verify_method === 'grep' && f.fix.verify_pattern) {
|
|
33
|
-
try {
|
|
34
|
-
execSync('grep -rn ' + JSON.stringify(f.fix.verify_pattern) + ' ' + f.locations.map(l => l.file).join(' '), { stdio: 'pipe' });
|
|
35
|
-
return true; // pattern still matches — needs fixing
|
|
36
|
-
} catch { return false; } // pattern gone — already fixed
|
|
37
|
-
}
|
|
38
|
-
// inspect findings: check resolution field, skip if already resolved
|
|
39
33
|
if (f.resolution && f.resolution.status === 'passed') return false;
|
|
40
|
-
return true;
|
|
34
|
+
return true;
|
|
41
35
|
});
|
|
42
36
|
|
|
43
37
|
if (findings.length === 0) {
|
|
@@ -67,14 +61,11 @@ steps:
|
|
|
67
61
|
principle: cluster[0].principle,
|
|
68
62
|
category: cluster[0].category,
|
|
69
63
|
severity: cluster.some(c => c.severity === 'error') ? 'error' : cluster[0].severity,
|
|
70
|
-
acceptance_criteria: cluster.map(c =>
|
|
71
|
-
if (c.fix && c.fix.verify_method === 'grep' && c.fix.verify_pattern) return 'grep pattern should not match: ' + c.fix.verify_pattern + ' in ' + c.locations.map(l=>l.file).join(', ');
|
|
72
|
-
return 'Fix: ' + (c.fix?.suggestion || c.description);
|
|
73
|
-
})
|
|
64
|
+
acceptance_criteria: cluster.map(c => 'Fix: ' + (c.fix?.suggestion || c.description))
|
|
74
65
|
});
|
|
75
66
|
}
|
|
76
67
|
console.log(JSON.stringify({ tasks }));
|
|
77
|
-
"
|
|
68
|
+
" '${{ steps.select-audit.output.audit | json }}'
|
|
78
69
|
output:
|
|
79
70
|
format: json
|
|
80
71
|
|
|
@@ -84,121 +75,92 @@ steps:
|
|
|
84
75
|
agent: audit-fixer
|
|
85
76
|
input:
|
|
86
77
|
task: "${{ task }}"
|
|
87
|
-
conformance_reference: "${{ steps.
|
|
78
|
+
conformance_reference: "${{ steps.select-audit.output.conformance_reference }}"
|
|
88
79
|
output:
|
|
89
80
|
format: json
|
|
90
81
|
schema: ../schemas/execution-results.schema.json
|
|
91
82
|
path: .project/fix-audit-results.json
|
|
92
83
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
for (const r of items) {
|
|
105
|
-
for (const dec of r.decisions || []) {
|
|
106
|
-
if (!existingIds.has(dec.id)) { d.decisions.push(dec); existingIds.add(dec.id); }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
fs.writeFileSync(decisionsPath, JSON.stringify(d, null, 2) + '\n');
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const gapsPath = '.project/gaps.json';
|
|
113
|
-
if (fs.existsSync(gapsPath)) {
|
|
114
|
-
const g = JSON.parse(fs.readFileSync(gapsPath, 'utf8'));
|
|
115
|
-
const existingIds = new Set(g.gaps.map(x => x.id));
|
|
116
|
-
for (const r of items) {
|
|
117
|
-
for (const issue of r.issues || []) {
|
|
118
|
-
const id = 'issue-' + issue.description.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 50);
|
|
119
|
-
if (!existingIds.has(id)) {
|
|
120
|
-
g.gaps.push({ id, description: issue.description, status: 'open', category: 'issue', priority: issue.severity === 'critical' ? 'high' : issue.severity === 'error' ? 'medium' : 'low', source: 'agent' });
|
|
121
|
-
existingIds.add(id);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
fs.writeFileSync(gapsPath, JSON.stringify(g, null, 2) + '\n');
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const invPath = '.project/inventory.json';
|
|
129
|
-
if (fs.existsSync(invPath)) {
|
|
130
|
-
const inv = JSON.parse(fs.readFileSync(invPath, 'utf8'));
|
|
131
|
-
let maxTests = inv.test_count || 0;
|
|
132
|
-
for (const r of items) { if (r.test_count && r.test_count > maxTests) maxTests = r.test_count; }
|
|
133
|
-
if (maxTests > inv.test_count) { inv.test_count = maxTests; fs.writeFileSync(invPath, JSON.stringify(inv, null, 2) + '\n'); }
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const statePath = '.project/state.json';
|
|
137
|
-
if (fs.existsSync(statePath)) {
|
|
138
|
-
const s = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
139
|
-
const lastCommit = items.filter(r => r.commit_hash).pop()?.commit_hash;
|
|
140
|
-
if (lastCommit) s.last_commit = lastCommit;
|
|
141
|
-
const maxTests = Math.max(...items.map(r => r.test_count || 0));
|
|
142
|
-
if (maxTests > 0) s.test_count = maxTests;
|
|
143
|
-
s.session.last_action = 'fix-audit workflow: ' + items.length + ' tasks completed';
|
|
144
|
-
fs.writeFileSync(statePath, JSON.stringify(s, null, 2) + '\n');
|
|
145
|
-
}
|
|
84
|
+
verify-findings:
|
|
85
|
+
agent: audit-finding-verifier
|
|
86
|
+
input:
|
|
87
|
+
audit: "${{ steps.select-audit.output.audit }}"
|
|
88
|
+
implementation_results: "${{ steps.implement.output }}"
|
|
89
|
+
conformance_reference: "${{ steps.select-audit.output.conformance_reference }}"
|
|
90
|
+
context: [implement]
|
|
91
|
+
output:
|
|
92
|
+
format: json
|
|
93
|
+
schema: ../schemas/finding-verification.schema.json
|
|
94
|
+
path: .project/fix-audit-verify.json
|
|
146
95
|
|
|
147
|
-
|
|
148
|
-
|
|
96
|
+
prepare-routing:
|
|
97
|
+
agent: audit-results-router
|
|
98
|
+
input:
|
|
99
|
+
implementation_results: "${{ steps.implement.output }}"
|
|
100
|
+
verification: "${{ steps.verify-findings.output }}"
|
|
101
|
+
context: [implement, verify-findings]
|
|
102
|
+
output:
|
|
103
|
+
format: json
|
|
104
|
+
schema: ../schemas/audit-routing-manifest.schema.json
|
|
105
|
+
|
|
106
|
+
route-decisions:
|
|
107
|
+
when: "${{ steps.prepare-routing.output.decisions | length }}"
|
|
108
|
+
forEach: "${{ steps.prepare-routing.output.decisions }}"
|
|
109
|
+
as: decision
|
|
110
|
+
block:
|
|
111
|
+
append:
|
|
112
|
+
name: decisions
|
|
113
|
+
key: decisions
|
|
114
|
+
item: "${{ decision }}"
|
|
149
115
|
output:
|
|
150
116
|
format: json
|
|
151
117
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const fix = finding.fix;
|
|
162
|
-
if (!fix) { results.push({ id: finding.id, status: 'skipped', reason: 'no fix defined' }); continue; }
|
|
163
|
-
if (fix.verify_method === 'grep' && fix.verify_pattern) {
|
|
164
|
-
try {
|
|
165
|
-
execSync('grep -rn ' + JSON.stringify(fix.verify_pattern) + ' ' + finding.locations.map(l => l.file).join(' '), { encoding: 'utf8' });
|
|
166
|
-
results.push({ id: finding.id, status: 'failed', reason: 'pattern still matches' });
|
|
167
|
-
} catch { results.push({ id: finding.id, status: 'passed' }); }
|
|
168
|
-
} else {
|
|
169
|
-
results.push({ id: finding.id, status: 'needs_inspect', reason: fix.verify_method });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const passed = results.filter(r => r.status === 'passed').length;
|
|
173
|
-
const failed = results.filter(r => r.status === 'failed').length;
|
|
174
|
-
const inspect = results.filter(r => r.status === 'needs_inspect').length;
|
|
175
|
-
const total = results.length;
|
|
176
|
-
const status = failed === 0 ? (inspect > 0 ? 'passed_with_inspect' : 'passed') : 'gaps_found';
|
|
177
|
-
console.log(JSON.stringify({ status, score: passed + '/' + total, passed, failed, needs_inspect: inspect, results }));
|
|
178
|
-
"
|
|
118
|
+
route-issues:
|
|
119
|
+
when: "${{ steps.prepare-routing.output.new_gaps | length }}"
|
|
120
|
+
forEach: "${{ steps.prepare-routing.output.new_gaps }}"
|
|
121
|
+
as: gap_item
|
|
122
|
+
block:
|
|
123
|
+
append:
|
|
124
|
+
name: gaps
|
|
125
|
+
key: gaps
|
|
126
|
+
item: "${{ gap_item }}"
|
|
179
127
|
output:
|
|
180
128
|
format: json
|
|
181
|
-
path: .project/fix-audit-verify.json
|
|
182
129
|
|
|
183
130
|
update-audit:
|
|
184
131
|
command: |
|
|
185
|
-
node
|
|
186
|
-
|
|
187
|
-
const
|
|
132
|
+
node -e "
|
|
133
|
+
const fs = require('fs');
|
|
134
|
+
const auditsDir = '.project/audits';
|
|
135
|
+
const auditFile = fs.readdirSync(auditsDir).filter(f => f.endsWith('.json')).sort().pop();
|
|
136
|
+
if (!auditFile) { console.error('No audit files found'); process.exit(1); }
|
|
137
|
+
const auditPath = auditsDir + '/' + auditFile;
|
|
188
138
|
const verifyPath = '.project/fix-audit-verify.json';
|
|
189
139
|
let verifyResults;
|
|
190
|
-
try {
|
|
140
|
+
try {
|
|
141
|
+
verifyResults = JSON.parse(fs.readFileSync(verifyPath, 'utf8'));
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.error('Cannot read verification output: ' + e.message);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
191
147
|
const audit = JSON.parse(fs.readFileSync(auditPath, 'utf8'));
|
|
192
|
-
|
|
148
|
+
let resolved = 0;
|
|
149
|
+
for (const vr of verifyResults.findings || []) {
|
|
193
150
|
const finding = audit.findings.find(f => f.id === vr.id);
|
|
194
151
|
if (finding) {
|
|
195
|
-
finding.resolution = {
|
|
152
|
+
finding.resolution = {
|
|
153
|
+
status: vr.status,
|
|
154
|
+
verified_at: new Date().toISOString(),
|
|
155
|
+
verified_by: 'agent',
|
|
156
|
+
evidence: vr.evidence || null
|
|
157
|
+
};
|
|
158
|
+
if (vr.status === 'passed') resolved++;
|
|
196
159
|
}
|
|
197
160
|
}
|
|
198
|
-
|
|
199
|
-
audit.summary.resolved = passed;
|
|
161
|
+
audit.summary.resolved = resolved;
|
|
200
162
|
fs.writeFileSync(auditPath, JSON.stringify(audit, null, 2) + '\n');
|
|
201
|
-
console.log(JSON.stringify({ updated: auditPath, resolved
|
|
163
|
+
console.log(JSON.stringify({ updated: auditPath, resolved, total: audit.findings.length }));
|
|
202
164
|
"
|
|
203
165
|
output:
|
|
204
166
|
format: json
|
|
@@ -206,12 +168,12 @@ steps:
|
|
|
206
168
|
check:
|
|
207
169
|
when: "${{ steps.update-audit.output.updated }}"
|
|
208
170
|
gate:
|
|
209
|
-
check: "test '${{ steps.verify.output.
|
|
171
|
+
check: "test '${{ steps.verify-findings.output.status }}' != 'gaps_found'"
|
|
210
172
|
onFail: fail
|
|
211
173
|
|
|
212
174
|
completion:
|
|
213
175
|
message: |
|
|
214
176
|
Audit fix workflow completed.
|
|
215
|
-
Verification: ${{ steps.verify.output.status }} (${{ steps.verify.output.score }})
|
|
177
|
+
Verification: ${{ steps.verify-findings.output.status }} (${{ steps.verify-findings.output.score }})
|
|
216
178
|
include:
|
|
217
|
-
- steps.verify.output.
|
|
179
|
+
- steps.verify-findings.output.findings
|
|
@@ -17,45 +17,45 @@ input:
|
|
|
17
17
|
value: id
|
|
18
18
|
|
|
19
19
|
steps:
|
|
20
|
+
load-gap-blocks:
|
|
21
|
+
block:
|
|
22
|
+
read: gaps
|
|
23
|
+
output:
|
|
24
|
+
format: json
|
|
25
|
+
|
|
20
26
|
load-gap:
|
|
21
27
|
command: |
|
|
22
|
-
node
|
|
23
|
-
|
|
24
|
-
const gapId = process.argv[
|
|
25
|
-
const
|
|
26
|
-
const gap = data.gaps.find(g => g.id === gapId);
|
|
28
|
+
node -e "
|
|
29
|
+
const gaps = JSON.parse(process.argv[1]);
|
|
30
|
+
const gapId = process.argv[2];
|
|
31
|
+
const gap = gaps.gaps.find(g => g.id === gapId);
|
|
27
32
|
if (!gap) { console.error('Gap not found: ' + gapId); process.exit(1); }
|
|
28
|
-
if (gap.status === 'resolved') { console.error('Gap already resolved
|
|
33
|
+
if (gap.status === 'resolved') { console.error('Gap already resolved'); process.exit(1); }
|
|
29
34
|
console.log(JSON.stringify({ gap }));
|
|
30
|
-
" '${{ input.gap_id }}'
|
|
35
|
+
" '${{ steps.load-gap-blocks.output | json }}' '${{ input.gap_id }}'
|
|
31
36
|
output:
|
|
32
37
|
format: json
|
|
33
38
|
|
|
34
|
-
load-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const phases = [];
|
|
41
|
-
const phasesDir = '.project/phases';
|
|
42
|
-
if (fs.existsSync(phasesDir)) {
|
|
43
|
-
for (const f of fs.readdirSync(phasesDir).filter(f => f.endsWith('.json')).sort()) {
|
|
44
|
-
phases.push(JSON.parse(fs.readFileSync(path.join(phasesDir, f), 'utf8')));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
39
|
+
load-phases:
|
|
40
|
+
block:
|
|
41
|
+
readDir: phases
|
|
42
|
+
output:
|
|
43
|
+
format: json
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
let inventory = {};
|
|
55
|
-
try { inventory = JSON.parse(fs.readFileSync('.project/inventory.json', 'utf8')); } catch {}
|
|
45
|
+
load-context:
|
|
46
|
+
block:
|
|
47
|
+
read: [architecture, conventions, gaps, inventory]
|
|
48
|
+
optional: [architecture, conventions, inventory]
|
|
49
|
+
output:
|
|
50
|
+
format: json
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
filter-gaps:
|
|
53
|
+
command: |
|
|
54
|
+
node -e "
|
|
55
|
+
const data = JSON.parse(process.argv[1]);
|
|
56
|
+
const open = (data.gaps || []).filter(g => g.status === 'open');
|
|
57
|
+
console.log(JSON.stringify(open));
|
|
58
|
+
" '${{ steps.load-context.output.gaps | json }}'
|
|
59
59
|
output:
|
|
60
60
|
format: json
|
|
61
61
|
|
|
@@ -64,34 +64,39 @@ steps:
|
|
|
64
64
|
input:
|
|
65
65
|
intent: "Implement gap ${{ steps.load-gap.output.gap.id }}: ${{ steps.load-gap.output.gap.description }}"
|
|
66
66
|
gap: "${{ steps.load-gap.output.gap }}"
|
|
67
|
-
phases: "${{ steps.load-
|
|
67
|
+
phases: "${{ steps.load-phases.output }}"
|
|
68
68
|
architecture: "${{ steps.load-context.output.architecture }}"
|
|
69
69
|
conventions: "${{ steps.load-context.output.conventions }}"
|
|
70
|
-
gaps: "${{ steps.
|
|
70
|
+
gaps: "${{ steps.filter-gaps.output }}"
|
|
71
71
|
inventory: "${{ steps.load-context.output.inventory }}"
|
|
72
72
|
output:
|
|
73
73
|
format: json
|
|
74
74
|
schema: block:phase
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
compute-phase-path:
|
|
77
77
|
command: |
|
|
78
|
-
node
|
|
79
|
-
import fs from 'fs';
|
|
78
|
+
node -e "
|
|
80
79
|
const phase = JSON.parse(process.argv[1]);
|
|
81
80
|
const num = String(phase.number).padStart(2, '0');
|
|
82
|
-
const name = phase.name.toLowerCase().replace(
|
|
83
|
-
|
|
84
|
-
fs.mkdirSync('.project/phases', { recursive: true });
|
|
85
|
-
fs.writeFileSync(filename, JSON.stringify(phase, null, 2));
|
|
86
|
-
console.log(JSON.stringify({ path: filename, phase_number: phase.number, spec_count: (phase.specs || []).length }));
|
|
81
|
+
const name = phase.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-+$/g, '');
|
|
82
|
+
console.log(JSON.stringify({ path: 'phases/' + num + '-' + name }));
|
|
87
83
|
" '${{ steps.author.output | json }}'
|
|
88
84
|
output:
|
|
89
85
|
format: json
|
|
90
86
|
|
|
87
|
+
write-phase:
|
|
88
|
+
block:
|
|
89
|
+
write:
|
|
90
|
+
name: phase
|
|
91
|
+
data: "${{ steps.author.output }}"
|
|
92
|
+
path: "${{ steps.compute-phase-path.output.path }}"
|
|
93
|
+
output:
|
|
94
|
+
format: json
|
|
95
|
+
|
|
91
96
|
completion:
|
|
92
97
|
message: |
|
|
93
|
-
Phase ${{ steps.
|
|
94
|
-
Specs: ${{ steps.
|
|
98
|
+
Phase ${{ steps.author.output.number }} created: ${{ steps.write-phase.output.path }}
|
|
99
|
+
Specs: ${{ steps.author.output.specs | length }}
|
|
95
100
|
From gap: ${{ input.gap_id }}
|
|
96
101
|
include:
|
|
97
102
|
- steps.author.output
|
|
@@ -25,38 +25,50 @@ steps:
|
|
|
25
25
|
analyze-structure:
|
|
26
26
|
agent: structure-analyzer
|
|
27
27
|
input:
|
|
28
|
-
exploration: ${{ steps.explore.
|
|
28
|
+
exploration: ${{ steps.explore.output }}
|
|
29
29
|
path: ${{ input.path }}
|
|
30
|
+
output:
|
|
31
|
+
format: json
|
|
32
|
+
schema: ../schemas/structure-analysis.schema.json
|
|
30
33
|
|
|
31
34
|
analyze-quality:
|
|
32
35
|
agent: quality-analyzer
|
|
33
36
|
input:
|
|
34
|
-
exploration: ${{ steps.explore.
|
|
37
|
+
exploration: ${{ steps.explore.output }}
|
|
35
38
|
path: ${{ input.path }}
|
|
39
|
+
output:
|
|
40
|
+
format: json
|
|
41
|
+
schema: ../schemas/quality-analysis.schema.json
|
|
36
42
|
|
|
37
43
|
analyze-patterns:
|
|
38
44
|
agent: pattern-analyzer
|
|
39
45
|
input:
|
|
40
|
-
exploration: ${{ steps.explore.
|
|
46
|
+
exploration: ${{ steps.explore.output }}
|
|
41
47
|
path: ${{ input.path }}
|
|
48
|
+
output:
|
|
49
|
+
format: json
|
|
50
|
+
schema: ../schemas/pattern-analysis.schema.json
|
|
42
51
|
|
|
43
52
|
# This step depends on all three analyzers — it must wait for all to complete
|
|
44
53
|
synthesize:
|
|
45
54
|
agent: synthesizer
|
|
46
55
|
input:
|
|
47
|
-
structure: ${{ steps.analyze-structure.
|
|
48
|
-
quality: ${{ steps.analyze-quality.
|
|
49
|
-
patterns: ${{ steps.analyze-patterns.
|
|
56
|
+
structure: ${{ steps.analyze-structure.output }}
|
|
57
|
+
quality: ${{ steps.analyze-quality.output }}
|
|
58
|
+
patterns: ${{ steps.analyze-patterns.output }}
|
|
50
59
|
focus: ${{ input.focus }}
|
|
60
|
+
output:
|
|
61
|
+
format: json
|
|
62
|
+
schema: ../schemas/synthesis.schema.json
|
|
51
63
|
|
|
52
64
|
artifacts:
|
|
53
65
|
report:
|
|
54
66
|
path: latest-analysis.md
|
|
55
|
-
from: steps.synthesize.
|
|
67
|
+
from: steps.synthesize.output
|
|
56
68
|
|
|
57
69
|
completion:
|
|
58
70
|
message: |
|
|
59
71
|
Present this code analysis report to the user. Highlight the most important
|
|
60
72
|
findings and any areas that need immediate attention.
|
|
61
73
|
include:
|
|
62
|
-
- steps.synthesize.
|
|
74
|
+
- steps.synthesize.output
|
|
@@ -3,33 +3,22 @@ description: Decompose accepted requirements into phases and tasks
|
|
|
3
3
|
version: "1"
|
|
4
4
|
|
|
5
5
|
steps:
|
|
6
|
-
load-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const projectDir = '.project';
|
|
12
|
-
const context = {};
|
|
13
|
-
|
|
14
|
-
// Read requirements
|
|
15
|
-
try { context.requirements = JSON.parse(fs.readFileSync(path.join(projectDir, 'requirements.json'), 'utf-8')); } catch { context.requirements = { requirements: [] }; }
|
|
16
|
-
|
|
17
|
-
// Read architecture
|
|
18
|
-
try { context.architecture = JSON.parse(fs.readFileSync(path.join(projectDir, 'architecture.json'), 'utf-8')); } catch { context.architecture = null; }
|
|
19
|
-
|
|
20
|
-
// Read project identity
|
|
21
|
-
try { context.project = JSON.parse(fs.readFileSync(path.join(projectDir, 'project.json'), 'utf-8')); } catch { context.project = null; }
|
|
6
|
+
load-requirements:
|
|
7
|
+
block:
|
|
8
|
+
read: requirements
|
|
9
|
+
output:
|
|
10
|
+
format: json
|
|
22
11
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} catch { context.existing_phases = []; }
|
|
12
|
+
load-context:
|
|
13
|
+
block:
|
|
14
|
+
read: [architecture, project]
|
|
15
|
+
optional: [architecture, project]
|
|
16
|
+
output:
|
|
17
|
+
format: json
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
load-phases:
|
|
20
|
+
block:
|
|
21
|
+
readDir: phases
|
|
33
22
|
output:
|
|
34
23
|
format: json
|
|
35
24
|
|
|
@@ -37,9 +26,9 @@ steps:
|
|
|
37
26
|
agent: plan-creator
|
|
38
27
|
input:
|
|
39
28
|
project: ${{ steps.load-context.output.project }}
|
|
40
|
-
requirements: ${{ steps.load-
|
|
29
|
+
requirements: ${{ steps.load-requirements.output }}
|
|
41
30
|
architecture: ${{ steps.load-context.output.architecture }}
|
|
42
|
-
existing_phases: ${{ steps.load-
|
|
31
|
+
existing_phases: ${{ steps.load-phases.output }}
|
|
43
32
|
output:
|
|
44
33
|
format: json
|
|
45
34
|
schema: block:tasks
|