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,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `planr revise` — core service (EPIC-003, FEAT-010 + FEAT-011).
|
|
3
|
+
*
|
|
4
|
+
* Exposes composable primitives; the CLI and future cascade service
|
|
5
|
+
* orchestrate them. Phase 1 (FEAT-010) ships `reviseArtifact` for dry-run
|
|
6
|
+
* decision generation; Phase 2 (FEAT-011) adds `applyDecision` for the
|
|
7
|
+
* write path and exposes the verifier context so callers can run
|
|
8
|
+
* `verifyDecision` against the same inputs the agent saw.
|
|
9
|
+
*/
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { buildCodebaseContext, extractKeywords, formatCodebaseContext, } from '../ai/codebase/context-builder.js';
|
|
12
|
+
import { buildRevisePrompt, } from '../ai/prompts/prompt-builder.js';
|
|
13
|
+
import { aiReviseDecisionSchema } from '../ai/schemas/ai-response-schemas.js';
|
|
14
|
+
import { TOKEN_BUDGETS } from '../ai/types.js';
|
|
15
|
+
import { logger } from '../utils/logger.js';
|
|
16
|
+
import { generateJSON } from './ai-service.js';
|
|
17
|
+
import { findArtifactTypeById, getArtifactDir, getParentChain, listArtifacts, readArtifact, readArtifactRaw, resolveArtifactFilename, } from './artifact-service.js';
|
|
18
|
+
import { atomicWriteFile } from './atomic-write-service.js';
|
|
19
|
+
import { renderDiff } from './diff-service.js';
|
|
20
|
+
import { getCanonicalSections } from './template-sections.js';
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when an artifact id cannot be resolved to an artifact type or
|
|
23
|
+
* the artifact file does not exist on disk.
|
|
24
|
+
*/
|
|
25
|
+
export class ReviseArtifactNotFoundError extends Error {
|
|
26
|
+
artifactId;
|
|
27
|
+
constructor(artifactId, message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = 'ReviseArtifactNotFoundError';
|
|
30
|
+
this.artifactId = artifactId;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Revise a single artifact — Phase 1 dry-run.
|
|
35
|
+
*
|
|
36
|
+
* Does NOT write any files. The returned decision is the agent output after
|
|
37
|
+
* schema validation; evidence verification, diff preview, and write are all
|
|
38
|
+
* Phase 2 responsibilities (FEAT-011). Cascade, siblings, and declared
|
|
39
|
+
* sources are Phase 3 (FEAT-012) and onwards.
|
|
40
|
+
*/
|
|
41
|
+
export async function reviseArtifact(projectDir, config, provider, artifactId, options) {
|
|
42
|
+
const artifactType = findArtifactTypeById(artifactId);
|
|
43
|
+
if (!artifactType) {
|
|
44
|
+
throw new ReviseArtifactNotFoundError(artifactId, `Cannot determine artifact type from ID: ${artifactId}. Expected format: EPIC-001, FEAT-001, US-001, TASK-001.`);
|
|
45
|
+
}
|
|
46
|
+
const artifactRaw = await readArtifactRaw(projectDir, config, artifactType, artifactId);
|
|
47
|
+
if (artifactRaw === null) {
|
|
48
|
+
throw new ReviseArtifactNotFoundError(artifactId, `Artifact ${artifactId} not found under ${artifactType}/ directory.`);
|
|
49
|
+
}
|
|
50
|
+
const parents = await loadParentPromptArtifacts(projectDir, config, artifactType, artifactId);
|
|
51
|
+
const siblings = options.noSiblingContext
|
|
52
|
+
? []
|
|
53
|
+
: await loadSiblingPromptArtifacts(projectDir, config, artifactType, artifactId, options.maxSiblings ?? 8);
|
|
54
|
+
let codebaseContextFormatted;
|
|
55
|
+
let codebaseCtx;
|
|
56
|
+
if (!options.noCodeContext) {
|
|
57
|
+
const keywords = extractKeywords(artifactRaw);
|
|
58
|
+
codebaseCtx = await buildCodebaseContext(projectDir, keywords);
|
|
59
|
+
codebaseContextFormatted = formatCodebaseContext(codebaseCtx);
|
|
60
|
+
}
|
|
61
|
+
const messages = buildRevisePrompt({
|
|
62
|
+
artifact: { id: artifactId, type: artifactType, content: artifactRaw },
|
|
63
|
+
parents,
|
|
64
|
+
siblings,
|
|
65
|
+
codebaseContextFormatted,
|
|
66
|
+
sources: [], // Declared-sources loader lands with the revise.yaml config work
|
|
67
|
+
writableScope: options.writableScope ?? 'all',
|
|
68
|
+
canonicalSections: getCanonicalSections(artifactType),
|
|
69
|
+
});
|
|
70
|
+
logger.debug(`reviseArtifact: calling AI for ${artifactId}`);
|
|
71
|
+
const result = await generateJSON(provider, messages, aiReviseDecisionSchema, {
|
|
72
|
+
maxTokens: TOKEN_BUDGETS.revise,
|
|
73
|
+
});
|
|
74
|
+
// resolveArtifactFilename strips the `.md` extension (other callers want the
|
|
75
|
+
// slug form). Revise must write to the full `.md` file, so append the
|
|
76
|
+
// extension explicitly. Defensive fallback if a future version ever returns
|
|
77
|
+
// a filename with the extension intact.
|
|
78
|
+
const filenameNoExt = await resolveArtifactFilename(projectDir, config, artifactType, artifactId);
|
|
79
|
+
const artifactDir = path.join(projectDir, getArtifactDir(config, artifactType));
|
|
80
|
+
const fullFilename = filenameNoExt.endsWith('.md') ? filenameNoExt : `${filenameNoExt}.md`;
|
|
81
|
+
const artifactPath = path.join(artifactDir, fullFilename);
|
|
82
|
+
const verifierContext = {
|
|
83
|
+
projectDir,
|
|
84
|
+
config,
|
|
85
|
+
artifactDir: path.dirname(artifactPath),
|
|
86
|
+
codebaseContextFormatted,
|
|
87
|
+
knownSourceRefs: [], // Phase 2 sources loader will populate
|
|
88
|
+
knownPatternRuleIds: codebaseCtx ? codebaseCtx.patternRules.map((r) => r.name) : [],
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
decision: result.result,
|
|
92
|
+
usage: result.usage ?? { inputTokens: 0, outputTokens: 0 },
|
|
93
|
+
contextStats: {
|
|
94
|
+
parentsLoaded: parents.length,
|
|
95
|
+
siblingsLoaded: siblings.length,
|
|
96
|
+
codebaseContextIncluded: !!codebaseContextFormatted,
|
|
97
|
+
sourcesLoaded: 0,
|
|
98
|
+
},
|
|
99
|
+
artifactPath,
|
|
100
|
+
originalContent: artifactRaw,
|
|
101
|
+
verifierContext,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Apply a (verified) decision: write the artifact atomically when
|
|
106
|
+
* `action === 'revise'` and `dryRun` is false, emit an audit entry
|
|
107
|
+
* describing the outcome either way.
|
|
108
|
+
*
|
|
109
|
+
* Caller is expected to have already run `verifyDecision` — `applyDecision`
|
|
110
|
+
* trusts that whatever decision arrives is allowed to be written.
|
|
111
|
+
*/
|
|
112
|
+
export async function applyDecision(options) {
|
|
113
|
+
const { decision, audit, dryRun, originalContent, artifactPath, backupDir } = options;
|
|
114
|
+
const timestamp = new Date().toISOString();
|
|
115
|
+
if (decision.action === 'skip') {
|
|
116
|
+
audit.appendEntry({
|
|
117
|
+
artifactId: decision.artifactId,
|
|
118
|
+
artifactPath,
|
|
119
|
+
outcome: 'skipped-by-agent',
|
|
120
|
+
rationale: decision.rationale,
|
|
121
|
+
evidence: decision.evidence,
|
|
122
|
+
ambiguous: decision.ambiguous,
|
|
123
|
+
cascadeLevel: options.cascadeLevel,
|
|
124
|
+
timestamp,
|
|
125
|
+
});
|
|
126
|
+
return { outcome: 'skipped-by-agent', wrote: false, diff: '' };
|
|
127
|
+
}
|
|
128
|
+
if (decision.action === 'flag') {
|
|
129
|
+
audit.appendEntry({
|
|
130
|
+
artifactId: decision.artifactId,
|
|
131
|
+
artifactPath,
|
|
132
|
+
outcome: 'flagged',
|
|
133
|
+
rationale: decision.rationale,
|
|
134
|
+
evidence: decision.evidence,
|
|
135
|
+
ambiguous: decision.ambiguous,
|
|
136
|
+
cascadeLevel: options.cascadeLevel,
|
|
137
|
+
timestamp,
|
|
138
|
+
});
|
|
139
|
+
return { outcome: 'flagged', wrote: false, diff: '' };
|
|
140
|
+
}
|
|
141
|
+
// action === 'revise'
|
|
142
|
+
const diff = decision.revisedMarkdown
|
|
143
|
+
? renderDiff(originalContent, decision.revisedMarkdown, {
|
|
144
|
+
color: false,
|
|
145
|
+
oldLabel: `${decision.artifactId} (before)`,
|
|
146
|
+
newLabel: `${decision.artifactId} (proposed)`,
|
|
147
|
+
})
|
|
148
|
+
: '';
|
|
149
|
+
if (dryRun) {
|
|
150
|
+
audit.appendEntry({
|
|
151
|
+
artifactId: decision.artifactId,
|
|
152
|
+
artifactPath,
|
|
153
|
+
outcome: 'would-apply',
|
|
154
|
+
rationale: decision.rationale,
|
|
155
|
+
evidence: decision.evidence,
|
|
156
|
+
ambiguous: decision.ambiguous,
|
|
157
|
+
cascadeLevel: options.cascadeLevel,
|
|
158
|
+
diff,
|
|
159
|
+
timestamp,
|
|
160
|
+
});
|
|
161
|
+
return { outcome: 'would-apply', wrote: false, diff };
|
|
162
|
+
}
|
|
163
|
+
// Real apply: atomic write + sidecar backup.
|
|
164
|
+
const backupPath = path.join(backupDir, `${decision.artifactId}.md.bak`);
|
|
165
|
+
await atomicWriteFile(artifactPath, decision.revisedMarkdown ?? '', { backupPath });
|
|
166
|
+
audit.appendEntry({
|
|
167
|
+
artifactId: decision.artifactId,
|
|
168
|
+
artifactPath,
|
|
169
|
+
outcome: 'applied',
|
|
170
|
+
rationale: decision.rationale,
|
|
171
|
+
evidence: decision.evidence,
|
|
172
|
+
ambiguous: decision.ambiguous,
|
|
173
|
+
cascadeLevel: options.cascadeLevel,
|
|
174
|
+
diff,
|
|
175
|
+
timestamp,
|
|
176
|
+
});
|
|
177
|
+
return { outcome: 'applied', wrote: true, diff };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Load immediate-sibling artifacts (same artifact type, same parent) as
|
|
181
|
+
* prompt entries. Lazy-reads each sibling's body only if it's going to be
|
|
182
|
+
* included — the listing check is cheap, the reads are budgeted by
|
|
183
|
+
* `maxSiblings` (default 8) so the prompt stays within token budget even on
|
|
184
|
+
* epics with many features.
|
|
185
|
+
*
|
|
186
|
+
* For epic scope, there are no siblings (epics live at the top). For types
|
|
187
|
+
* without a parent-id relationship (quick, backlog, etc.), returns empty.
|
|
188
|
+
*/
|
|
189
|
+
export async function loadSiblingPromptArtifacts(projectDir, config, type, id, maxSiblings) {
|
|
190
|
+
const parentFieldByType = {
|
|
191
|
+
feature: 'epicId',
|
|
192
|
+
story: 'featureId',
|
|
193
|
+
task: 'storyId',
|
|
194
|
+
};
|
|
195
|
+
const parentField = parentFieldByType[type];
|
|
196
|
+
if (!parentField)
|
|
197
|
+
return [];
|
|
198
|
+
const self = await readArtifact(projectDir, config, type, id);
|
|
199
|
+
if (!self)
|
|
200
|
+
return [];
|
|
201
|
+
const parentId = self.data[parentField];
|
|
202
|
+
if (!parentId)
|
|
203
|
+
return [];
|
|
204
|
+
const siblings = [];
|
|
205
|
+
const listing = await listArtifacts(projectDir, config, type);
|
|
206
|
+
for (const entry of listing) {
|
|
207
|
+
if (siblings.length >= maxSiblings)
|
|
208
|
+
break;
|
|
209
|
+
if (entry.id === id)
|
|
210
|
+
continue;
|
|
211
|
+
const sib = await readArtifact(projectDir, config, type, entry.id);
|
|
212
|
+
if (sib && sib.data[parentField] === parentId) {
|
|
213
|
+
siblings.push({ id: entry.id, type, content: sib.content });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return siblings;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Resolve the parent chain for an artifact and return it as the ordered
|
|
220
|
+
* `RevisePromptArtifact[]` the prompt builder expects (epic → feature →
|
|
221
|
+
* story). Empty array for top-level artifacts (epic scope).
|
|
222
|
+
*/
|
|
223
|
+
export async function loadParentPromptArtifacts(projectDir, config, type, id) {
|
|
224
|
+
const chain = await getParentChain(projectDir, config, type, id);
|
|
225
|
+
const parents = [];
|
|
226
|
+
if (chain.epic) {
|
|
227
|
+
parents.push({
|
|
228
|
+
id: String(chain.epic.data.id),
|
|
229
|
+
type: 'epic',
|
|
230
|
+
content: chain.epic.content,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (chain.feature) {
|
|
234
|
+
parents.push({
|
|
235
|
+
id: String(chain.feature.data.id),
|
|
236
|
+
type: 'feature',
|
|
237
|
+
content: chain.feature.content,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (chain.story) {
|
|
241
|
+
parents.push({
|
|
242
|
+
id: String(chain.story.data.id),
|
|
243
|
+
type: 'story',
|
|
244
|
+
content: chain.story.content,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return parents;
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=revise-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revise-service.js","sourceRoot":"","sources":["../../src/services/revise-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical section lists per artifact type (EPIC-003, FEAT-014 follow-up).
|
|
3
|
+
*
|
|
4
|
+
* The revise prompt uses these to give the agent a *soft* conformance hint:
|
|
5
|
+
* "these are the sections a ${type} artifact canonically has — don't invent
|
|
6
|
+
* new ones; flag instead." This prevents the failure mode where the agent
|
|
7
|
+
* helpfully adds something like `## Relevant Files` to an epic (a task-level
|
|
8
|
+
* convention) when there is no drift justifying it.
|
|
9
|
+
*
|
|
10
|
+
* The lists mirror what the Handlebars templates in `src/templates/<type>/`
|
|
11
|
+
* actually emit. Kept hardcoded rather than parsed from the .hbs files at
|
|
12
|
+
* runtime because:
|
|
13
|
+
*
|
|
14
|
+
* 1. Handlebars templates contain `{{ }}` interpolation that would need
|
|
15
|
+
* stripping before we could grep `## ` headings.
|
|
16
|
+
* 2. If a template gains a new section, we want an explicit review step
|
|
17
|
+
* in this file, not silent pickup.
|
|
18
|
+
* 3. Parsing templates would couple revise to handlebars internals.
|
|
19
|
+
*
|
|
20
|
+
* When you add or rename a template section, update the matching entry here.
|
|
21
|
+
*/
|
|
22
|
+
import type { ArtifactType } from '../models/types.js';
|
|
23
|
+
/**
|
|
24
|
+
* Return the canonical section list for an artifact type, or `undefined`
|
|
25
|
+
* when the type has no enforced convention (e.g., `backlog`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function getCanonicalSections(type: ArtifactType): readonly string[] | undefined;
|
|
28
|
+
//# sourceMappingURL=template-sections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-sections.d.ts","sourceRoot":"","sources":["../../src/services/template-sections.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AA6BvD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAEtF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical section lists per artifact type (EPIC-003, FEAT-014 follow-up).
|
|
3
|
+
*
|
|
4
|
+
* The revise prompt uses these to give the agent a *soft* conformance hint:
|
|
5
|
+
* "these are the sections a ${type} artifact canonically has — don't invent
|
|
6
|
+
* new ones; flag instead." This prevents the failure mode where the agent
|
|
7
|
+
* helpfully adds something like `## Relevant Files` to an epic (a task-level
|
|
8
|
+
* convention) when there is no drift justifying it.
|
|
9
|
+
*
|
|
10
|
+
* The lists mirror what the Handlebars templates in `src/templates/<type>/`
|
|
11
|
+
* actually emit. Kept hardcoded rather than parsed from the .hbs files at
|
|
12
|
+
* runtime because:
|
|
13
|
+
*
|
|
14
|
+
* 1. Handlebars templates contain `{{ }}` interpolation that would need
|
|
15
|
+
* stripping before we could grep `## ` headings.
|
|
16
|
+
* 2. If a template gains a new section, we want an explicit review step
|
|
17
|
+
* in this file, not silent pickup.
|
|
18
|
+
* 3. Parsing templates would couple revise to handlebars internals.
|
|
19
|
+
*
|
|
20
|
+
* When you add or rename a template section, update the matching entry here.
|
|
21
|
+
*/
|
|
22
|
+
const CANONICAL_SECTIONS = {
|
|
23
|
+
epic: [
|
|
24
|
+
'Business Value',
|
|
25
|
+
'Target Users',
|
|
26
|
+
'Problem Statement',
|
|
27
|
+
'Solution Overview',
|
|
28
|
+
'Success Criteria',
|
|
29
|
+
'Key Features',
|
|
30
|
+
'Dependencies',
|
|
31
|
+
'Risks',
|
|
32
|
+
'Features',
|
|
33
|
+
],
|
|
34
|
+
feature: [
|
|
35
|
+
'Overview',
|
|
36
|
+
'Functional Requirements',
|
|
37
|
+
'User Stories',
|
|
38
|
+
'Dependencies',
|
|
39
|
+
'Technical Considerations',
|
|
40
|
+
'Risks',
|
|
41
|
+
'Success Metrics',
|
|
42
|
+
],
|
|
43
|
+
story: ['User Story', 'Acceptance Criteria', 'Additional Notes', 'Tasks'],
|
|
44
|
+
task: ['Artifact Sources', 'Tasks', 'Acceptance Criteria Mapping', 'Relevant Files', 'Notes'],
|
|
45
|
+
// `quick`, `backlog`, `sprint`, `adr`, `checklist` — no canonical list
|
|
46
|
+
// enforced. Revise will skip the template-structure hint for these types.
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Return the canonical section list for an artifact type, or `undefined`
|
|
50
|
+
* when the type has no enforced convention (e.g., `backlog`).
|
|
51
|
+
*/
|
|
52
|
+
export function getCanonicalSections(type) {
|
|
53
|
+
return CANONICAL_SECTIONS[type];
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=template-sections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-sections.js","sourceRoot":"","sources":["../../src/services/template-sections.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,MAAM,kBAAkB,GAAqD;IAC3E,IAAI,EAAE;QACJ,gBAAgB;QAChB,cAAc;QACd,mBAAmB;QACnB,mBAAmB;QACnB,kBAAkB;QAClB,cAAc;QACd,cAAc;QACd,OAAO;QACP,UAAU;KACX;IACD,OAAO,EAAE;QACP,UAAU;QACV,yBAAyB;QACzB,cAAc;QACd,cAAc;QACd,0BAA0B;QAC1B,OAAO;QACP,iBAAiB;KAClB;IACD,KAAK,EAAE,CAAC,YAAY,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,OAAO,CAAC;IACzE,IAAI,EAAE,CAAC,kBAAkB,EAAE,OAAO,EAAE,6BAA6B,EAAE,gBAAgB,EAAE,OAAO,CAAC;IAC7F,uEAAuE;IACvE,0EAA0E;CAC3E,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAkB;IACrD,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal line-based unified diff for revise preview (EPIC-003, FEAT-011 §4.0).
|
|
3
|
+
*
|
|
4
|
+
* Implements a Wagner–Fischer LCS over lines, then prints hunks with
|
|
5
|
+
* `+` / `-` prefixes. Not a general-purpose diff tool — scoped to small
|
|
6
|
+
* planning artifacts (typically <1K lines), where O(m×n) time/memory is
|
|
7
|
+
* comfortable. Line equality is exact after trimming trailing newlines.
|
|
8
|
+
*
|
|
9
|
+
* We don't pull in an npm diff library because (a) the algorithm is small,
|
|
10
|
+
* (b) the format we emit is fixed and narrow, and (c) keeping the
|
|
11
|
+
* dependency footprint tight is a stated project preference.
|
|
12
|
+
*/
|
|
13
|
+
export interface UnifiedDiffOptions {
|
|
14
|
+
/** Number of unchanged context lines around each change. Default: 3. */
|
|
15
|
+
context?: number;
|
|
16
|
+
/** Labels printed on the file-header `---` / `+++` rows. */
|
|
17
|
+
oldLabel?: string;
|
|
18
|
+
newLabel?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compute a unified diff between two strings. Empty string on either side
|
|
22
|
+
* is valid. Trailing newlines are normalized so a file that ends in `\n`
|
|
23
|
+
* does not spuriously diff against one that does not.
|
|
24
|
+
*/
|
|
25
|
+
export declare function unifiedDiff(oldText: string, newText: string, options?: UnifiedDiffOptions): string;
|
|
26
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/utils/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAaH,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAsBR"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal line-based unified diff for revise preview (EPIC-003, FEAT-011 §4.0).
|
|
3
|
+
*
|
|
4
|
+
* Implements a Wagner–Fischer LCS over lines, then prints hunks with
|
|
5
|
+
* `+` / `-` prefixes. Not a general-purpose diff tool — scoped to small
|
|
6
|
+
* planning artifacts (typically <1K lines), where O(m×n) time/memory is
|
|
7
|
+
* comfortable. Line equality is exact after trimming trailing newlines.
|
|
8
|
+
*
|
|
9
|
+
* We don't pull in an npm diff library because (a) the algorithm is small,
|
|
10
|
+
* (b) the format we emit is fixed and narrow, and (c) keeping the
|
|
11
|
+
* dependency footprint tight is a stated project preference.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Compute a unified diff between two strings. Empty string on either side
|
|
15
|
+
* is valid. Trailing newlines are normalized so a file that ends in `\n`
|
|
16
|
+
* does not spuriously diff against one that does not.
|
|
17
|
+
*/
|
|
18
|
+
export function unifiedDiff(oldText, newText, options = {}) {
|
|
19
|
+
const contextLines = options.context ?? 3;
|
|
20
|
+
const oldLines = splitLines(oldText);
|
|
21
|
+
const newLines = splitLines(newText);
|
|
22
|
+
const items = lcsDiff(oldLines, newLines);
|
|
23
|
+
const hunks = buildHunks(items, contextLines);
|
|
24
|
+
if (hunks.length === 0)
|
|
25
|
+
return ''; // identical
|
|
26
|
+
const out = [];
|
|
27
|
+
out.push(`--- ${options.oldLabel ?? 'a'}`);
|
|
28
|
+
out.push(`+++ ${options.newLabel ?? 'b'}`);
|
|
29
|
+
for (const hunk of hunks) {
|
|
30
|
+
const oldLen = hunk.items.filter((i) => i.kind !== 'add').length;
|
|
31
|
+
const newLen = hunk.items.filter((i) => i.kind !== 'remove').length;
|
|
32
|
+
out.push(`@@ -${hunk.oldStart + 1},${oldLen} +${hunk.newStart + 1},${newLen} @@`);
|
|
33
|
+
for (const it of hunk.items) {
|
|
34
|
+
const prefix = it.kind === 'add' ? '+' : it.kind === 'remove' ? '-' : ' ';
|
|
35
|
+
out.push(`${prefix}${it.line}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out.join('\n');
|
|
39
|
+
}
|
|
40
|
+
function splitLines(s) {
|
|
41
|
+
if (s.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
// Preserve empty-trailing-line behavior: drop a single trailing '' so the
|
|
44
|
+
// diff of "a\n" vs "a" is empty rather than noisy.
|
|
45
|
+
const lines = s.split('\n');
|
|
46
|
+
if (lines.length > 0 && lines[lines.length - 1] === '')
|
|
47
|
+
lines.pop();
|
|
48
|
+
return lines;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build a sequence of DiffItems from two line arrays using LCS backtracking.
|
|
52
|
+
*/
|
|
53
|
+
function lcsDiff(a, b) {
|
|
54
|
+
const m = a.length;
|
|
55
|
+
const n = b.length;
|
|
56
|
+
// dp[i][j] = length of LCS of a[0..i) and b[0..j)
|
|
57
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
58
|
+
for (let i = 0; i < m; i++) {
|
|
59
|
+
for (let j = 0; j < n; j++) {
|
|
60
|
+
dp[i + 1][j + 1] = a[i] === b[j] ? dp[i][j] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const items = [];
|
|
64
|
+
let i = m;
|
|
65
|
+
let j = n;
|
|
66
|
+
while (i > 0 && j > 0) {
|
|
67
|
+
if (a[i - 1] === b[j - 1]) {
|
|
68
|
+
items.push({ kind: 'same', line: a[i - 1] });
|
|
69
|
+
i--;
|
|
70
|
+
j--;
|
|
71
|
+
}
|
|
72
|
+
else if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
73
|
+
items.push({ kind: 'remove', line: a[i - 1] });
|
|
74
|
+
i--;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
items.push({ kind: 'add', line: b[j - 1] });
|
|
78
|
+
j--;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
while (i > 0) {
|
|
82
|
+
items.push({ kind: 'remove', line: a[--i] });
|
|
83
|
+
}
|
|
84
|
+
while (j > 0) {
|
|
85
|
+
items.push({ kind: 'add', line: b[--j] });
|
|
86
|
+
}
|
|
87
|
+
items.reverse();
|
|
88
|
+
return items;
|
|
89
|
+
}
|
|
90
|
+
/** Group items into hunks that include `context` unchanged lines around each change. */
|
|
91
|
+
function buildHunks(items, context) {
|
|
92
|
+
const hunks = [];
|
|
93
|
+
let oldIdx = 0;
|
|
94
|
+
let newIdx = 0;
|
|
95
|
+
// First pass: mark index of every item in old/new streams
|
|
96
|
+
const marks = items.map((it) => {
|
|
97
|
+
const m = { item: it, oldIdx, newIdx };
|
|
98
|
+
if (it.kind !== 'add')
|
|
99
|
+
oldIdx++;
|
|
100
|
+
if (it.kind !== 'remove')
|
|
101
|
+
newIdx++;
|
|
102
|
+
return m;
|
|
103
|
+
});
|
|
104
|
+
let i = 0;
|
|
105
|
+
while (i < marks.length) {
|
|
106
|
+
if (marks[i].item.kind === 'same') {
|
|
107
|
+
i++;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Change region: expand left by `context`, then run forward including
|
|
111
|
+
// intermediate small same-runs (≤ 2*context apart → merge).
|
|
112
|
+
const start = Math.max(0, i - context);
|
|
113
|
+
let end = i;
|
|
114
|
+
while (end < marks.length) {
|
|
115
|
+
if (marks[end].item.kind !== 'same') {
|
|
116
|
+
end++;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Look ahead: is the next change within 2*context?
|
|
120
|
+
let sameRun = 0;
|
|
121
|
+
let k = end;
|
|
122
|
+
while (k < marks.length && marks[k].item.kind === 'same') {
|
|
123
|
+
sameRun++;
|
|
124
|
+
k++;
|
|
125
|
+
}
|
|
126
|
+
if (k >= marks.length || sameRun > 2 * context) {
|
|
127
|
+
// Close the hunk after `context` trailing same-lines.
|
|
128
|
+
end += Math.min(context, sameRun);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
end = k;
|
|
132
|
+
}
|
|
133
|
+
const slice = marks.slice(start, Math.min(end, marks.length));
|
|
134
|
+
hunks.push({
|
|
135
|
+
oldStart: marks[start].oldIdx,
|
|
136
|
+
newStart: marks[start].newIdx,
|
|
137
|
+
items: slice.map((s) => s.item),
|
|
138
|
+
});
|
|
139
|
+
i = end;
|
|
140
|
+
}
|
|
141
|
+
return hunks;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/utils/diff.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openplanr",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"description": "AI-powered planning CLI — backlog, sprints, task templates, estimation, GitHub sync, and AI agent rules for Cursor, Claude Code, and Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cli/index.js",
|