openplanr 1.2.7 → 1.2.8
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 +34 -1
- package/dist/ai/prompts/prompt-builder.d.ts +48 -0
- package/dist/ai/prompts/prompt-builder.d.ts.map +1 -1
- package/dist/ai/prompts/prompt-builder.js +57 -1
- package/dist/ai/prompts/prompt-builder.js.map +1 -1
- package/dist/ai/prompts/system-prompts.d.ts +23 -0
- package/dist/ai/prompts/system-prompts.d.ts.map +1 -1
- package/dist/ai/prompts/system-prompts.js +98 -0
- package/dist/ai/prompts/system-prompts.js.map +1 -1
- package/dist/ai/schemas/ai-response-schemas.d.ts +68 -0
- package/dist/ai/schemas/ai-response-schemas.d.ts.map +1 -1
- package/dist/ai/schemas/ai-response-schemas.js +81 -0
- package/dist/ai/schemas/ai-response-schemas.js.map +1 -1
- package/dist/ai/types.d.ts +2 -0
- package/dist/ai/types.d.ts.map +1 -1
- package/dist/ai/types.js +2 -0
- package/dist/ai/types.js.map +1 -1
- package/dist/cli/commands/revise.d.ts +23 -0
- package/dist/cli/commands/revise.d.ts.map +1 -0
- package/dist/cli/commands/revise.js +502 -0
- package/dist/cli/commands/revise.js.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/models/types.d.ts +120 -0
- package/dist/models/types.d.ts.map +1 -1
- package/dist/services/atomic-write-service.d.ts +41 -0
- package/dist/services/atomic-write-service.d.ts.map +1 -0
- package/dist/services/atomic-write-service.js +87 -0
- package/dist/services/atomic-write-service.js.map +1 -0
- package/dist/services/audit-log-service.d.ts +50 -0
- package/dist/services/audit-log-service.d.ts.map +1 -0
- package/dist/services/audit-log-service.js +213 -0
- package/dist/services/audit-log-service.js.map +1 -0
- package/dist/services/cascade-service.d.ts +62 -0
- package/dist/services/cascade-service.d.ts.map +1 -0
- package/dist/services/cascade-service.js +189 -0
- package/dist/services/cascade-service.js.map +1 -0
- package/dist/services/diff-service.d.ts +18 -0
- package/dist/services/diff-service.d.ts.map +1 -0
- package/dist/services/diff-service.js +35 -0
- package/dist/services/diff-service.js.map +1 -0
- package/dist/services/evidence-verifier.d.ts +71 -0
- package/dist/services/evidence-verifier.d.ts.map +1 -0
- package/dist/services/evidence-verifier.js +171 -0
- package/dist/services/evidence-verifier.js.map +1 -0
- package/dist/services/git-service.d.ts +60 -0
- package/dist/services/git-service.d.ts.map +1 -0
- package/dist/services/git-service.js +137 -0
- package/dist/services/git-service.js.map +1 -0
- package/dist/services/graph-integrity.d.ts +36 -0
- package/dist/services/graph-integrity.d.ts.map +1 -0
- package/dist/services/graph-integrity.js +54 -0
- package/dist/services/graph-integrity.js.map +1 -0
- package/dist/services/prompt-service.d.ts +18 -0
- package/dist/services/prompt-service.d.ts.map +1 -1
- package/dist/services/prompt-service.js +47 -0
- package/dist/services/prompt-service.js.map +1 -1
- package/dist/services/revise-cache-service.d.ts +46 -0
- package/dist/services/revise-cache-service.d.ts.map +1 -0
- package/dist/services/revise-cache-service.js +88 -0
- package/dist/services/revise-cache-service.js.map +1 -0
- package/dist/services/revise-service.d.ts +108 -0
- package/dist/services/revise-service.d.ts.map +1 -0
- package/dist/services/revise-service.js +249 -0
- package/dist/services/revise-service.js.map +1 -0
- package/dist/services/template-sections.d.ts +28 -0
- package/dist/services/template-sections.d.ts.map +1 -0
- package/dist/services/template-sections.js +55 -0
- package/dist/services/template-sections.js.map +1 -0
- package/dist/utils/diff.d.ts +26 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +143 -0
- package/dist/utils/diff.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cascade execution for `planr revise` (EPIC-003, FEAT-012).
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Build cascade order** — top-down: epic → features → stories → tasks.
|
|
7
|
+
* The artifact hierarchy is a strict tree; no cycle detection is needed.
|
|
8
|
+
* 2. **Execute the cascade** — for each artifact in order, call the
|
|
9
|
+
* caller-provided `processOne` closure. The cascade stops on
|
|
10
|
+
* SIGINT, `[q]uit`, or an unrecoverable agent error, and always
|
|
11
|
+
* flushes audit entries immediately so an interrupted run leaves an
|
|
12
|
+
* accurate record.
|
|
13
|
+
* 3. **Progress tracking** — emits a `CascadeProgress` snapshot before
|
|
14
|
+
* each artifact, with a rolling ETA.
|
|
15
|
+
*
|
|
16
|
+
* This service owns NO rollback logic. Partial cascades that break the
|
|
17
|
+
* artifact graph are the concern of FEAT-013's post-flight rollback.
|
|
18
|
+
*/
|
|
19
|
+
import { logger } from '../utils/logger.js';
|
|
20
|
+
import { listArtifacts, readArtifact } from './artifact-service.js';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Cascade order (§1.0)
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the cascade plan for a root artifact, walking its descendants in
|
|
26
|
+
* top-down order. For an epic root, the plan is:
|
|
27
|
+
* EPIC → [features under epic] → [stories under those features] → [tasks under those stories]
|
|
28
|
+
* For a feature root, the EPIC level is empty and features contain only the root, etc.
|
|
29
|
+
*/
|
|
30
|
+
export async function buildCascadeOrder(projectDir, config, rootType, rootId) {
|
|
31
|
+
const levels = [
|
|
32
|
+
{ type: 'epic', label: 'epic', artifactIds: [] },
|
|
33
|
+
{ type: 'feature', label: 'features', artifactIds: [] },
|
|
34
|
+
{ type: 'story', label: 'stories', artifactIds: [] },
|
|
35
|
+
{ type: 'task', label: 'tasks', artifactIds: [] },
|
|
36
|
+
];
|
|
37
|
+
if (rootType === 'epic') {
|
|
38
|
+
levels[0].artifactIds = [rootId];
|
|
39
|
+
const features = await listArtifacts(projectDir, config, 'feature');
|
|
40
|
+
for (const f of features) {
|
|
41
|
+
const featureArtifact = await readArtifact(projectDir, config, 'feature', f.id);
|
|
42
|
+
if (featureArtifact?.data.epicId === rootId) {
|
|
43
|
+
levels[1].artifactIds.push(f.id);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const featureId of levels[1].artifactIds) {
|
|
47
|
+
const stories = await listArtifacts(projectDir, config, 'story');
|
|
48
|
+
for (const s of stories) {
|
|
49
|
+
const storyArtifact = await readArtifact(projectDir, config, 'story', s.id);
|
|
50
|
+
if (storyArtifact?.data.featureId === featureId) {
|
|
51
|
+
levels[2].artifactIds.push(s.id);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const storyId of levels[2].artifactIds) {
|
|
56
|
+
const tasks = await listArtifacts(projectDir, config, 'task');
|
|
57
|
+
for (const t of tasks) {
|
|
58
|
+
const taskArtifact = await readArtifact(projectDir, config, 'task', t.id);
|
|
59
|
+
if (taskArtifact?.data.storyId === storyId) {
|
|
60
|
+
levels[3].artifactIds.push(t.id);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (rootType === 'feature') {
|
|
66
|
+
levels[1].artifactIds = [rootId];
|
|
67
|
+
const stories = await listArtifacts(projectDir, config, 'story');
|
|
68
|
+
for (const s of stories) {
|
|
69
|
+
const storyArtifact = await readArtifact(projectDir, config, 'story', s.id);
|
|
70
|
+
if (storyArtifact?.data.featureId === rootId) {
|
|
71
|
+
levels[2].artifactIds.push(s.id);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const storyId of levels[2].artifactIds) {
|
|
75
|
+
const tasks = await listArtifacts(projectDir, config, 'task');
|
|
76
|
+
for (const t of tasks) {
|
|
77
|
+
const taskArtifact = await readArtifact(projectDir, config, 'task', t.id);
|
|
78
|
+
if (taskArtifact?.data.storyId === storyId) {
|
|
79
|
+
levels[3].artifactIds.push(t.id);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (rootType === 'story') {
|
|
85
|
+
levels[2].artifactIds = [rootId];
|
|
86
|
+
const tasks = await listArtifacts(projectDir, config, 'task');
|
|
87
|
+
for (const t of tasks) {
|
|
88
|
+
const taskArtifact = await readArtifact(projectDir, config, 'task', t.id);
|
|
89
|
+
if (taskArtifact?.data.storyId === rootId) {
|
|
90
|
+
levels[3].artifactIds.push(t.id);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (rootType === 'task') {
|
|
95
|
+
levels[3].artifactIds = [rootId];
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Non-hierarchy types (quick, backlog, sprint, adr, checklist) — cascade
|
|
99
|
+
// is a no-op scope that only processes the root. We stash the id at the
|
|
100
|
+
// tasks level to avoid leaving the plan empty.
|
|
101
|
+
levels[3].artifactIds = [rootId];
|
|
102
|
+
}
|
|
103
|
+
const orderedIds = levels.flatMap((l) => l.artifactIds);
|
|
104
|
+
return { rootId, rootType, levels, orderedIds };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Drive the cascade plan through the provided processor. Installs a SIGINT
|
|
108
|
+
* handler for the duration of the run; on Ctrl+C it waits for the current
|
|
109
|
+
* artifact to finish (so no half-written files) and then stops. The SIGINT
|
|
110
|
+
* handler is removed on completion whether or not it fired.
|
|
111
|
+
*/
|
|
112
|
+
export async function executeCascade(options) {
|
|
113
|
+
const { plan, processor } = options;
|
|
114
|
+
const signalTarget = options.signalTarget ?? process;
|
|
115
|
+
const total = plan.orderedIds.length;
|
|
116
|
+
let completed = 0;
|
|
117
|
+
let interruptedByUser = false;
|
|
118
|
+
const sigintHandler = () => {
|
|
119
|
+
interruptedByUser = true;
|
|
120
|
+
logger.warn('\nSIGINT received — cascade will stop after current artifact completes.');
|
|
121
|
+
};
|
|
122
|
+
signalTarget.once('SIGINT', sigintHandler);
|
|
123
|
+
const started = Date.now();
|
|
124
|
+
try {
|
|
125
|
+
for (const level of plan.levels) {
|
|
126
|
+
for (const artifactId of level.artifactIds) {
|
|
127
|
+
if (interruptedByUser) {
|
|
128
|
+
return {
|
|
129
|
+
completed,
|
|
130
|
+
total,
|
|
131
|
+
interrupted: { reason: 'sigint', atArtifactId: artifactId },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const progress = {
|
|
135
|
+
completed,
|
|
136
|
+
total,
|
|
137
|
+
currentArtifactId: artifactId,
|
|
138
|
+
currentLevelLabel: level.label,
|
|
139
|
+
etaSeconds: estimateEta(started, completed, total),
|
|
140
|
+
};
|
|
141
|
+
options.onProgress?.(progress);
|
|
142
|
+
let outcome;
|
|
143
|
+
try {
|
|
144
|
+
outcome = await processor({
|
|
145
|
+
artifactId,
|
|
146
|
+
levelLabel: level.label,
|
|
147
|
+
progress,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
logger.error(`Cascade aborted at ${artifactId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
152
|
+
return {
|
|
153
|
+
completed,
|
|
154
|
+
total,
|
|
155
|
+
interrupted: { reason: 'agent_error', atArtifactId: artifactId },
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
completed++;
|
|
159
|
+
if (!outcome.continue) {
|
|
160
|
+
return {
|
|
161
|
+
completed,
|
|
162
|
+
total,
|
|
163
|
+
interrupted: {
|
|
164
|
+
reason: outcome.stopReason === 'q' ? 'q' : 'agent_error',
|
|
165
|
+
atArtifactId: artifactId,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return { completed, total };
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
signalTarget.removeListener('SIGINT', sigintHandler);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Estimate seconds remaining based on observed pace. Returns null for the
|
|
179
|
+
* first couple of samples so the UI doesn't print wildly unstable numbers.
|
|
180
|
+
*/
|
|
181
|
+
function estimateEta(startedMs, completed, total) {
|
|
182
|
+
if (completed < 2 || total === 0)
|
|
183
|
+
return null;
|
|
184
|
+
const elapsed = (Date.now() - startedMs) / 1000;
|
|
185
|
+
const perItem = elapsed / completed;
|
|
186
|
+
const remaining = Math.max(0, total - completed);
|
|
187
|
+
return Math.round(perItem * remaining);
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=cascade-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cascade-service.js","sourceRoot":"","sources":["../../src/services/cascade-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colored diff preview for revise (EPIC-003, FEAT-011 §4.2).
|
|
3
|
+
*
|
|
4
|
+
* Wraps the pure `unifiedDiff` utility with chalk formatting. The returned
|
|
5
|
+
* string is what the CLI prints before asking the user to apply / skip /
|
|
6
|
+
* edit / re-view / quit.
|
|
7
|
+
*/
|
|
8
|
+
import { type UnifiedDiffOptions } from '../utils/diff.js';
|
|
9
|
+
export interface RenderDiffOptions extends UnifiedDiffOptions {
|
|
10
|
+
/** Set to false to skip ANSI color codes (useful for tests / non-TTY output). */
|
|
11
|
+
color?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Produce a unified diff between `oldText` and `newText`, color-coded for
|
|
15
|
+
* terminal display. Returns the empty string when the two are identical.
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderDiff(oldText: string, newText: string, options?: RenderDiffOptions): string;
|
|
18
|
+
//# sourceMappingURL=diff-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-service.d.ts","sourceRoot":"","sources":["../../src/services/diff-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,KAAK,kBAAkB,EAAe,MAAM,kBAAkB,CAAC;AAExE,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,iFAAiF;IACjF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,iBAAsB,GAC9B,MAAM,CAeR"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colored diff preview for revise (EPIC-003, FEAT-011 §4.2).
|
|
3
|
+
*
|
|
4
|
+
* Wraps the pure `unifiedDiff` utility with chalk formatting. The returned
|
|
5
|
+
* string is what the CLI prints before asking the user to apply / skip /
|
|
6
|
+
* edit / re-view / quit.
|
|
7
|
+
*/
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { unifiedDiff } from '../utils/diff.js';
|
|
10
|
+
/**
|
|
11
|
+
* Produce a unified diff between `oldText` and `newText`, color-coded for
|
|
12
|
+
* terminal display. Returns the empty string when the two are identical.
|
|
13
|
+
*/
|
|
14
|
+
export function renderDiff(oldText, newText, options = {}) {
|
|
15
|
+
const raw = unifiedDiff(oldText, newText, options);
|
|
16
|
+
if (raw.length === 0)
|
|
17
|
+
return '';
|
|
18
|
+
if (options.color === false)
|
|
19
|
+
return raw;
|
|
20
|
+
return raw
|
|
21
|
+
.split('\n')
|
|
22
|
+
.map((line) => {
|
|
23
|
+
if (line.startsWith('+++') || line.startsWith('---'))
|
|
24
|
+
return chalk.bold(line);
|
|
25
|
+
if (line.startsWith('@@'))
|
|
26
|
+
return chalk.cyan(line);
|
|
27
|
+
if (line.startsWith('+'))
|
|
28
|
+
return chalk.green(line);
|
|
29
|
+
if (line.startsWith('-'))
|
|
30
|
+
return chalk.red(line);
|
|
31
|
+
return chalk.dim(line);
|
|
32
|
+
})
|
|
33
|
+
.join('\n');
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=diff-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-service.js","sourceRoot":"","sources":["../../src/services/diff-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAA2B,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAOxE;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,OAAe,EACf,OAAe,EACf,UAA6B,EAAE;IAE/B,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACnD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK;QAAE,OAAO,GAAG,CAAC;IAExC,OAAO,GAAG;SACP,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9E,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjD,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-flight evidence verification for `planr revise` (EPIC-003, FEAT-011).
|
|
3
|
+
*
|
|
4
|
+
* The revise agent emits a ReviseDecision with typed evidence citations. Before
|
|
5
|
+
* the user ever sees a diff, this verifier checks each citation against the
|
|
6
|
+
* real repo:
|
|
7
|
+
*
|
|
8
|
+
* - `file_exists` / `file_absent` — fs.stat on the ref path
|
|
9
|
+
* - `grep_match` — literal check inside the codebase context
|
|
10
|
+
* the agent was given; rejects claims the
|
|
11
|
+
* agent could not have seen
|
|
12
|
+
* - `sibling_artifact` — artifact id must exist on disk
|
|
13
|
+
* - `source_quote` — source path must exist on disk (quote
|
|
14
|
+
* fuzzy-match is best-effort)
|
|
15
|
+
* - `pattern_rule` — rule id must be in the detected pattern
|
|
16
|
+
* rules for this run
|
|
17
|
+
*
|
|
18
|
+
* Unverifiable evidence is dropped with a reason. If a `revise` action has no
|
|
19
|
+
* surviving evidence after the sweep, the decision is demoted to `flag` with
|
|
20
|
+
* an explicit ambiguity entry — the agent's judgment wasn't necessarily wrong,
|
|
21
|
+
* but its *proof* can't be trusted, so a human owns the call.
|
|
22
|
+
*/
|
|
23
|
+
import type { OpenPlanrConfig, ReviseDecision, ReviseEvidence } from '../models/types.js';
|
|
24
|
+
/**
|
|
25
|
+
* Run-time context the verifier needs. Callers should populate this from the
|
|
26
|
+
* same inputs used to build the revise prompt, so the verifier checks evidence
|
|
27
|
+
* against exactly the material the agent had access to.
|
|
28
|
+
*/
|
|
29
|
+
export interface EvidenceVerifierContext {
|
|
30
|
+
projectDir: string;
|
|
31
|
+
config: OpenPlanrConfig;
|
|
32
|
+
/**
|
|
33
|
+
* Directory of the artifact being verified. Used to resolve relative
|
|
34
|
+
* evidence refs like `../features/FEAT-001-slug.md` that appear in
|
|
35
|
+
* markdown cross-reference links (those paths are relative to the
|
|
36
|
+
* artifact's file location, not to projectDir). Falls back to projectDir
|
|
37
|
+
* when omitted.
|
|
38
|
+
*/
|
|
39
|
+
artifactDir?: string;
|
|
40
|
+
/** Concatenated string from `formatCodebaseContext`; undefined in fast mode. */
|
|
41
|
+
codebaseContextFormatted?: string;
|
|
42
|
+
/** Labels (paths or URLs) of declared sources supplied to the agent. */
|
|
43
|
+
knownSourceRefs: string[];
|
|
44
|
+
/** Pattern rule ids detected in the codebase context (from pattern-rules). */
|
|
45
|
+
knownPatternRuleIds: string[];
|
|
46
|
+
}
|
|
47
|
+
export interface DroppedEvidence {
|
|
48
|
+
evidence: ReviseEvidence;
|
|
49
|
+
reason: string;
|
|
50
|
+
}
|
|
51
|
+
export interface DecisionVerificationResult {
|
|
52
|
+
/** Possibly-rewritten decision (evidence filtered; action demoted if needed). */
|
|
53
|
+
decision: ReviseDecision;
|
|
54
|
+
/** Evidence items that failed verification and were removed from the decision. */
|
|
55
|
+
dropped: DroppedEvidence[];
|
|
56
|
+
/** True when the verifier changed the action (e.g., revise → flag). */
|
|
57
|
+
demoted: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Verify every evidence item in a decision; drop anything unverifiable.
|
|
61
|
+
* Demote `revise` to `flag` when no verifiable evidence remains.
|
|
62
|
+
*/
|
|
63
|
+
export declare function verifyDecision(decision: ReviseDecision, ctx: EvidenceVerifierContext): Promise<DecisionVerificationResult>;
|
|
64
|
+
interface EvidenceCheck {
|
|
65
|
+
ok: boolean;
|
|
66
|
+
reason: string;
|
|
67
|
+
}
|
|
68
|
+
/** Verify a single evidence item. Exported primarily for testing. */
|
|
69
|
+
export declare function verifyEvidence(ev: ReviseEvidence, ctx: EvidenceVerifierContext): Promise<EvidenceCheck>;
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=evidence-verifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence-verifier.d.ts","sourceRoot":"","sources":["../../src/services/evidence-verifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAEV,eAAe,EAEf,cAAc,EACd,cAAc,EACf,MAAM,oBAAoB,CAAC;AAI5B;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;IACxB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,wEAAwE;IACxE,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,8EAA8E;IAC9E,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,iFAAiF;IACjF,QAAQ,EAAE,cAAc,CAAC;IACzB,kFAAkF;IAClF,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,cAAc,EACxB,GAAG,EAAE,uBAAuB,GAC3B,OAAO,CAAC,0BAA0B,CAAC,CAgErC;AAED,UAAU,aAAa;IACrB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qEAAqE;AACrE,wBAAsB,cAAc,CAClC,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,uBAAuB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAwDxB"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-flight evidence verification for `planr revise` (EPIC-003, FEAT-011).
|
|
3
|
+
*
|
|
4
|
+
* The revise agent emits a ReviseDecision with typed evidence citations. Before
|
|
5
|
+
* the user ever sees a diff, this verifier checks each citation against the
|
|
6
|
+
* real repo:
|
|
7
|
+
*
|
|
8
|
+
* - `file_exists` / `file_absent` — fs.stat on the ref path
|
|
9
|
+
* - `grep_match` — literal check inside the codebase context
|
|
10
|
+
* the agent was given; rejects claims the
|
|
11
|
+
* agent could not have seen
|
|
12
|
+
* - `sibling_artifact` — artifact id must exist on disk
|
|
13
|
+
* - `source_quote` — source path must exist on disk (quote
|
|
14
|
+
* fuzzy-match is best-effort)
|
|
15
|
+
* - `pattern_rule` — rule id must be in the detected pattern
|
|
16
|
+
* rules for this run
|
|
17
|
+
*
|
|
18
|
+
* Unverifiable evidence is dropped with a reason. If a `revise` action has no
|
|
19
|
+
* surviving evidence after the sweep, the decision is demoted to `flag` with
|
|
20
|
+
* an explicit ambiguity entry — the agent's judgment wasn't necessarily wrong,
|
|
21
|
+
* but its *proof* can't be trusted, so a human owns the call.
|
|
22
|
+
*/
|
|
23
|
+
import { stat } from 'node:fs/promises';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { logger } from '../utils/logger.js';
|
|
26
|
+
import { findArtifactTypeById, listArtifacts, readArtifact } from './artifact-service.js';
|
|
27
|
+
/**
|
|
28
|
+
* Verify every evidence item in a decision; drop anything unverifiable.
|
|
29
|
+
* Demote `revise` to `flag` when no verifiable evidence remains.
|
|
30
|
+
*/
|
|
31
|
+
export async function verifyDecision(decision, ctx) {
|
|
32
|
+
const dropped = [];
|
|
33
|
+
const verified = [];
|
|
34
|
+
for (const ev of decision.evidence) {
|
|
35
|
+
const check = await verifyEvidence(ev, ctx);
|
|
36
|
+
if (check.ok) {
|
|
37
|
+
verified.push(ev);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
dropped.push({ evidence: ev, reason: check.reason });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (dropped.length > 0) {
|
|
44
|
+
logger.debug(`evidence-verifier: dropped ${dropped.length}/${decision.evidence.length} citations for ${decision.artifactId}`);
|
|
45
|
+
}
|
|
46
|
+
// Demotion rules for a `revise` action. We never silently apply a rewrite
|
|
47
|
+
// whose evidence base is weak. Two triggers:
|
|
48
|
+
//
|
|
49
|
+
// (a) `verified.length === 0` — no evidence survived at all
|
|
50
|
+
// (b) `dropped.length > verified.length` — a majority of the agent's
|
|
51
|
+
// evidence was unverifiable. This catches the failure mode where
|
|
52
|
+
// the agent cites one true thing and several hallucinated ones;
|
|
53
|
+
// without this rule a 1-out-of-6 verification rate passes through
|
|
54
|
+
// because at least one citation survives.
|
|
55
|
+
//
|
|
56
|
+
// Both rules target `revise` specifically — `flag` decisions are already
|
|
57
|
+
// asking for human review, and `skip` decisions don't change anything.
|
|
58
|
+
const shouldDemote = decision.action === 'revise' && (verified.length === 0 || dropped.length > verified.length);
|
|
59
|
+
if (shouldDemote) {
|
|
60
|
+
const reason = verified.length === 0
|
|
61
|
+
? 'Agent proposed a revision but none of its evidence citations could be verified against the repo. Human review required.'
|
|
62
|
+
: `Agent proposed a revision but a majority of its evidence citations (${dropped.length}/${dropped.length + verified.length}) could not be verified. Most likely hallucinated — human review required.`;
|
|
63
|
+
const tag = verified.length === 0 ? 'all evidence unverifiable' : 'majority evidence unverifiable';
|
|
64
|
+
const ambiguity = {
|
|
65
|
+
section: '(evidence verification)',
|
|
66
|
+
reason,
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
decision: {
|
|
70
|
+
...decision,
|
|
71
|
+
action: 'flag',
|
|
72
|
+
evidence: verified, // keep any surviving citations in the flag record
|
|
73
|
+
revisedMarkdown: undefined,
|
|
74
|
+
ambiguous: [...decision.ambiguous, ambiguity],
|
|
75
|
+
rationale: `${decision.rationale} [demoted: ${tag}]`,
|
|
76
|
+
},
|
|
77
|
+
dropped,
|
|
78
|
+
demoted: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
decision: { ...decision, evidence: verified },
|
|
83
|
+
dropped,
|
|
84
|
+
demoted: false,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Verify a single evidence item. Exported primarily for testing. */
|
|
88
|
+
export async function verifyEvidence(ev, ctx) {
|
|
89
|
+
switch (ev.type) {
|
|
90
|
+
case 'file_exists':
|
|
91
|
+
return (await pathExists(ctx.projectDir, ev.ref, ctx.artifactDir))
|
|
92
|
+
? ok()
|
|
93
|
+
: fail(`cited file does not exist: ${ev.ref}`);
|
|
94
|
+
case 'file_absent':
|
|
95
|
+
return (await pathExists(ctx.projectDir, ev.ref, ctx.artifactDir))
|
|
96
|
+
? fail(`cited file IS present in the repo, contradicting 'file_absent': ${ev.ref}`)
|
|
97
|
+
: ok();
|
|
98
|
+
case 'grep_match':
|
|
99
|
+
if (!ctx.codebaseContextFormatted) {
|
|
100
|
+
return fail('grep_match citation requires codebase context, but context was not loaded (fast mode)');
|
|
101
|
+
}
|
|
102
|
+
// Require either ref OR quote to appear in the context the agent saw.
|
|
103
|
+
if (ctx.codebaseContextFormatted.includes(ev.ref) ||
|
|
104
|
+
(ev.quote && ctx.codebaseContextFormatted.includes(ev.quote))) {
|
|
105
|
+
return ok();
|
|
106
|
+
}
|
|
107
|
+
return fail(`grep_match token not found in codebase context: ${ev.ref}`);
|
|
108
|
+
case 'sibling_artifact': {
|
|
109
|
+
const type = findArtifactTypeById(ev.ref);
|
|
110
|
+
if (!type) {
|
|
111
|
+
return fail(`sibling_artifact ref is not a recognizable artifact id: ${ev.ref}`);
|
|
112
|
+
}
|
|
113
|
+
return (await artifactExists(ctx.projectDir, ctx.config, type, ev.ref))
|
|
114
|
+
? ok()
|
|
115
|
+
: fail(`sibling_artifact not found on disk: ${ev.ref}`);
|
|
116
|
+
}
|
|
117
|
+
case 'source_quote':
|
|
118
|
+
// Source refs are labels/paths declared in .planr/revise.yaml; the
|
|
119
|
+
// caller has already globbed them, so we validate against that list.
|
|
120
|
+
return ctx.knownSourceRefs.includes(ev.ref)
|
|
121
|
+
? ok()
|
|
122
|
+
: fail(`source_quote ref is not a declared source: ${ev.ref}`);
|
|
123
|
+
case 'pattern_rule':
|
|
124
|
+
return ctx.knownPatternRuleIds.includes(ev.ref)
|
|
125
|
+
? ok()
|
|
126
|
+
: fail(`pattern_rule ref is not a detected pattern rule: ${ev.ref}`);
|
|
127
|
+
default: {
|
|
128
|
+
// Exhaustive check — if a new evidence type is added to the TS union
|
|
129
|
+
// but not here, the compiler will complain.
|
|
130
|
+
const _exhaustive = ev.type;
|
|
131
|
+
return fail(`unhandled evidence type: ${String(_exhaustive)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const ok = () => ({ ok: true, reason: '' });
|
|
136
|
+
const fail = (reason) => ({ ok: false, reason });
|
|
137
|
+
async function pathExists(projectDir, ref, artifactDir) {
|
|
138
|
+
// Choose resolution base:
|
|
139
|
+
// - Relative refs (`./` or `../`) resolve against the artifact's directory
|
|
140
|
+
// when available, because OpenPlanr's cross-reference convention writes
|
|
141
|
+
// paths like `../features/FEAT-001-slug.md` relative to the artifact file.
|
|
142
|
+
// - Everything else (repo-root-style refs like `src/services/foo.ts`) resolves
|
|
143
|
+
// against projectDir.
|
|
144
|
+
const looksRelative = ref.startsWith('./') || ref.startsWith('../');
|
|
145
|
+
const base = looksRelative && artifactDir ? artifactDir : projectDir;
|
|
146
|
+
const resolved = path.resolve(base, ref);
|
|
147
|
+
const normalized = path.normalize(resolved);
|
|
148
|
+
// Traversal guard — the final path must still land inside projectDir
|
|
149
|
+
// (after following any `../`). This blocks probes like `../../../etc/passwd`
|
|
150
|
+
// while allowing legitimate artifact→sibling-dir references.
|
|
151
|
+
if (!normalized.startsWith(path.normalize(projectDir)))
|
|
152
|
+
return false;
|
|
153
|
+
try {
|
|
154
|
+
await stat(normalized);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function artifactExists(projectDir, config, type, id) {
|
|
162
|
+
// readArtifact is the authoritative existence check — it looks for files
|
|
163
|
+
// matching `${id}-*.md` in the correct artifact-type directory.
|
|
164
|
+
const artifact = await readArtifact(projectDir, config, type, id);
|
|
165
|
+
if (artifact !== null)
|
|
166
|
+
return true;
|
|
167
|
+
// Fallback: scan the listing in case id-case or prefix routing differs.
|
|
168
|
+
const list = await listArtifacts(projectDir, config, type);
|
|
169
|
+
return list.some((a) => a.id === id);
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=evidence-verifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence-verifier.js","sourceRoot":"","sources":["../../src/services/evidence-verifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git integration for `planr revise` (EPIC-003, FEAT-011 + FEAT-013).
|
|
3
|
+
*
|
|
4
|
+
* Two responsibilities:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Clean-tree gate (FEAT-011 §2.0):** revise refuses to run with a dirty
|
|
7
|
+
* working tree by default. Users can override with `--allow-dirty`, but
|
|
8
|
+
* post-flight rollback depends on a clean pre-run state, so the gate is
|
|
9
|
+
* the load-bearing safety net.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Capture + rollback anchor (FEAT-013 §4.0):** before bulk writes,
|
|
12
|
+
* revise captures HEAD and the set of touched paths so a post-flight
|
|
13
|
+
* graph-integrity failure can restore via `git checkout`.
|
|
14
|
+
*
|
|
15
|
+
* All git operations use `execFile` (not shell), matching the pattern in
|
|
16
|
+
* github-service.ts. If git is not available or the project is not a git
|
|
17
|
+
* repo, clean-tree checks fail closed (revise refuses to run) unless
|
|
18
|
+
* --allow-dirty is passed, because without git there is no safety net.
|
|
19
|
+
*/
|
|
20
|
+
export type GitTreeStatus = {
|
|
21
|
+
kind: 'clean';
|
|
22
|
+
head: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'dirty';
|
|
25
|
+
head: string;
|
|
26
|
+
changedPaths: string[];
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'not-a-repo';
|
|
29
|
+
reason: string;
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'git-missing';
|
|
32
|
+
reason: string;
|
|
33
|
+
};
|
|
34
|
+
export interface GitCleanTreeCheckOptions {
|
|
35
|
+
allowDirty: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface GitCleanTreeCheckResult {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
status: GitTreeStatus;
|
|
40
|
+
/** User-facing message describing why the gate opened or closed. */
|
|
41
|
+
message: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Inspect the working tree. Never throws — always returns a typed status so
|
|
45
|
+
* callers can render errors consistently.
|
|
46
|
+
*/
|
|
47
|
+
export declare function inspectGitTree(projectDir: string): Promise<GitTreeStatus>;
|
|
48
|
+
/**
|
|
49
|
+
* The clean-tree gate: clean → pass; dirty → pass only when --allow-dirty;
|
|
50
|
+
* missing git / not a repo → fail closed unless --allow-dirty was passed
|
|
51
|
+
* (because without git there is no post-flight rollback safety net).
|
|
52
|
+
*/
|
|
53
|
+
export declare function checkCleanTree(projectDir: string, options: GitCleanTreeCheckOptions): Promise<GitCleanTreeCheckResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Restore a set of paths from HEAD — the primitive FEAT-013's post-flight
|
|
56
|
+
* rollback invokes when graph integrity breaks after writes. Paths are
|
|
57
|
+
* relative to `projectDir`. Empty list is a no-op.
|
|
58
|
+
*/
|
|
59
|
+
export declare function checkoutPaths(projectDir: string, relativePaths: string[]): Promise<void>;
|
|
60
|
+
//# sourceMappingURL=git-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-service.d.ts","sourceRoot":"","sources":["../../src/services/git-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,aAAa,CAAC;IACtB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAuC/E;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CA0DlC;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAM9F"}
|