openplanr 1.2.7 → 1.3.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.
Files changed (164) hide show
  1. package/README.md +41 -4
  2. package/dist/agents/task-parser.d.ts.map +1 -1
  3. package/dist/agents/task-parser.js +8 -34
  4. package/dist/agents/task-parser.js.map +1 -1
  5. package/dist/ai/prompts/prompt-builder.d.ts +48 -0
  6. package/dist/ai/prompts/prompt-builder.d.ts.map +1 -1
  7. package/dist/ai/prompts/prompt-builder.js +57 -1
  8. package/dist/ai/prompts/prompt-builder.js.map +1 -1
  9. package/dist/ai/prompts/system-prompts.d.ts +24 -1
  10. package/dist/ai/prompts/system-prompts.d.ts.map +1 -1
  11. package/dist/ai/prompts/system-prompts.js +104 -6
  12. package/dist/ai/prompts/system-prompts.js.map +1 -1
  13. package/dist/ai/schemas/ai-response-schemas.d.ts +68 -0
  14. package/dist/ai/schemas/ai-response-schemas.d.ts.map +1 -1
  15. package/dist/ai/schemas/ai-response-schemas.js +81 -0
  16. package/dist/ai/schemas/ai-response-schemas.js.map +1 -1
  17. package/dist/ai/types.d.ts +2 -0
  18. package/dist/ai/types.d.ts.map +1 -1
  19. package/dist/ai/types.js +4 -0
  20. package/dist/ai/types.js.map +1 -1
  21. package/dist/cli/commands/backlog.d.ts +12 -0
  22. package/dist/cli/commands/backlog.d.ts.map +1 -1
  23. package/dist/cli/commands/backlog.js +88 -2
  24. package/dist/cli/commands/backlog.js.map +1 -1
  25. package/dist/cli/commands/config.d.ts.map +1 -1
  26. package/dist/cli/commands/config.js +8 -2
  27. package/dist/cli/commands/config.js.map +1 -1
  28. package/dist/cli/commands/linear.d.ts +8 -0
  29. package/dist/cli/commands/linear.d.ts.map +1 -0
  30. package/dist/cli/commands/linear.js +550 -0
  31. package/dist/cli/commands/linear.js.map +1 -0
  32. package/dist/cli/commands/quick.d.ts +17 -0
  33. package/dist/cli/commands/quick.d.ts.map +1 -1
  34. package/dist/cli/commands/quick.js +31 -15
  35. package/dist/cli/commands/quick.js.map +1 -1
  36. package/dist/cli/commands/revise.d.ts +24 -0
  37. package/dist/cli/commands/revise.d.ts.map +1 -0
  38. package/dist/cli/commands/revise.js +570 -0
  39. package/dist/cli/commands/revise.js.map +1 -0
  40. package/dist/cli/index.js +4 -0
  41. package/dist/cli/index.js.map +1 -1
  42. package/dist/models/schema.d.ts +43 -0
  43. package/dist/models/schema.d.ts.map +1 -1
  44. package/dist/models/schema.js +49 -0
  45. package/dist/models/schema.js.map +1 -1
  46. package/dist/models/types.d.ts +296 -0
  47. package/dist/models/types.d.ts.map +1 -1
  48. package/dist/services/artifact-gathering.d.ts +4 -0
  49. package/dist/services/artifact-gathering.d.ts.map +1 -1
  50. package/dist/services/artifact-gathering.js +1 -1
  51. package/dist/services/artifact-gathering.js.map +1 -1
  52. package/dist/services/artifact-service.d.ts +12 -1
  53. package/dist/services/artifact-service.d.ts.map +1 -1
  54. package/dist/services/artifact-service.js +49 -6
  55. package/dist/services/artifact-service.js.map +1 -1
  56. package/dist/services/atomic-write-service.d.ts +41 -0
  57. package/dist/services/atomic-write-service.d.ts.map +1 -0
  58. package/dist/services/atomic-write-service.js +87 -0
  59. package/dist/services/atomic-write-service.js.map +1 -0
  60. package/dist/services/audit-log-service.d.ts +47 -0
  61. package/dist/services/audit-log-service.d.ts.map +1 -0
  62. package/dist/services/audit-log-service.js +210 -0
  63. package/dist/services/audit-log-service.js.map +1 -0
  64. package/dist/services/cascade-service.d.ts +62 -0
  65. package/dist/services/cascade-service.d.ts.map +1 -0
  66. package/dist/services/cascade-service.js +189 -0
  67. package/dist/services/cascade-service.js.map +1 -0
  68. package/dist/services/credentials-service.js +2 -2
  69. package/dist/services/credentials-service.js.map +1 -1
  70. package/dist/services/diff-service.d.ts +18 -0
  71. package/dist/services/diff-service.d.ts.map +1 -0
  72. package/dist/services/diff-service.js +35 -0
  73. package/dist/services/diff-service.js.map +1 -0
  74. package/dist/services/evidence-verifier.d.ts +71 -0
  75. package/dist/services/evidence-verifier.d.ts.map +1 -0
  76. package/dist/services/evidence-verifier.js +174 -0
  77. package/dist/services/evidence-verifier.js.map +1 -0
  78. package/dist/services/git-service.d.ts +60 -0
  79. package/dist/services/git-service.d.ts.map +1 -0
  80. package/dist/services/git-service.js +137 -0
  81. package/dist/services/git-service.js.map +1 -0
  82. package/dist/services/graph-integrity.d.ts +35 -0
  83. package/dist/services/graph-integrity.d.ts.map +1 -0
  84. package/dist/services/graph-integrity.js +53 -0
  85. package/dist/services/graph-integrity.js.map +1 -0
  86. package/dist/services/linear/body-formatters.d.ts +69 -0
  87. package/dist/services/linear/body-formatters.d.ts.map +1 -0
  88. package/dist/services/linear/body-formatters.js +183 -0
  89. package/dist/services/linear/body-formatters.js.map +1 -0
  90. package/dist/services/linear/constants.d.ts +61 -0
  91. package/dist/services/linear/constants.d.ts.map +1 -0
  92. package/dist/services/linear/constants.js +84 -0
  93. package/dist/services/linear/constants.js.map +1 -0
  94. package/dist/services/linear/errors.d.ts +14 -0
  95. package/dist/services/linear/errors.d.ts.map +1 -0
  96. package/dist/services/linear/errors.js +106 -0
  97. package/dist/services/linear/errors.js.map +1 -0
  98. package/dist/services/linear/estimate-resolver.d.ts +50 -0
  99. package/dist/services/linear/estimate-resolver.d.ts.map +1 -0
  100. package/dist/services/linear/estimate-resolver.js +82 -0
  101. package/dist/services/linear/estimate-resolver.js.map +1 -0
  102. package/dist/services/linear/plan-builders.d.ts +64 -0
  103. package/dist/services/linear/plan-builders.d.ts.map +1 -0
  104. package/dist/services/linear/plan-builders.js +237 -0
  105. package/dist/services/linear/plan-builders.js.map +1 -0
  106. package/dist/services/linear/scope-loaders.d.ts +79 -0
  107. package/dist/services/linear/scope-loaders.d.ts.map +1 -0
  108. package/dist/services/linear/scope-loaders.js +227 -0
  109. package/dist/services/linear/scope-loaders.js.map +1 -0
  110. package/dist/services/linear/strategy-context.d.ts +66 -0
  111. package/dist/services/linear/strategy-context.d.ts.map +1 -0
  112. package/dist/services/linear/strategy-context.js +121 -0
  113. package/dist/services/linear/strategy-context.js.map +1 -0
  114. package/dist/services/linear-mapping-service.d.ts +11 -0
  115. package/dist/services/linear-mapping-service.d.ts.map +1 -0
  116. package/dist/services/linear-mapping-service.js +220 -0
  117. package/dist/services/linear-mapping-service.js.map +1 -0
  118. package/dist/services/linear-pull-service.d.ts +137 -0
  119. package/dist/services/linear-pull-service.d.ts.map +1 -0
  120. package/dist/services/linear-pull-service.js +720 -0
  121. package/dist/services/linear-pull-service.js.map +1 -0
  122. package/dist/services/linear-push-service.d.ts +86 -0
  123. package/dist/services/linear-push-service.d.ts.map +1 -0
  124. package/dist/services/linear-push-service.js +956 -0
  125. package/dist/services/linear-push-service.js.map +1 -0
  126. package/dist/services/linear-service.d.ts +122 -0
  127. package/dist/services/linear-service.d.ts.map +1 -0
  128. package/dist/services/linear-service.js +361 -0
  129. package/dist/services/linear-service.js.map +1 -0
  130. package/dist/services/prompt-service.d.ts +37 -0
  131. package/dist/services/prompt-service.d.ts.map +1 -1
  132. package/dist/services/prompt-service.js +111 -0
  133. package/dist/services/prompt-service.js.map +1 -1
  134. package/dist/services/revise-apply-service.d.ts +55 -0
  135. package/dist/services/revise-apply-service.d.ts.map +1 -0
  136. package/dist/services/revise-apply-service.js +255 -0
  137. package/dist/services/revise-apply-service.js.map +1 -0
  138. package/dist/services/revise-cache-service.d.ts +46 -0
  139. package/dist/services/revise-cache-service.d.ts.map +1 -0
  140. package/dist/services/revise-cache-service.js +88 -0
  141. package/dist/services/revise-cache-service.js.map +1 -0
  142. package/dist/services/revise-plan-service.d.ts +38 -0
  143. package/dist/services/revise-plan-service.d.ts.map +1 -0
  144. package/dist/services/revise-plan-service.js +151 -0
  145. package/dist/services/revise-plan-service.js.map +1 -0
  146. package/dist/services/revise-service.d.ts +115 -0
  147. package/dist/services/revise-service.d.ts.map +1 -0
  148. package/dist/services/revise-service.js +294 -0
  149. package/dist/services/revise-service.js.map +1 -0
  150. package/dist/services/template-sections.d.ts +28 -0
  151. package/dist/services/template-sections.d.ts.map +1 -0
  152. package/dist/services/template-sections.js +55 -0
  153. package/dist/services/template-sections.js.map +1 -0
  154. package/dist/templates/backlog/backlog-item.md.hbs +3 -0
  155. package/dist/templates/quick/quick-task.md.hbs +6 -0
  156. package/dist/utils/diff.d.ts +47 -0
  157. package/dist/utils/diff.d.ts.map +1 -0
  158. package/dist/utils/diff.js +278 -0
  159. package/dist/utils/diff.js.map +1 -0
  160. package/dist/utils/markdown.d.ts +23 -0
  161. package/dist/utils/markdown.d.ts.map +1 -1
  162. package/dist/utils/markdown.js +79 -0
  163. package/dist/utils/markdown.js.map +1 -1
  164. package/package.json +3 -2
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Revise plan parser.
3
+ *
4
+ * A "plan" is a previously-written revise audit log that we replay to disk
5
+ * without any model calls. This service parses a Markdown audit file
6
+ * emitted by `audit-log-service.ts` back into structured `ReviseAuditEntry`
7
+ * records that the CLI's apply-from-audit path can replay.
8
+ *
9
+ * Parsing is deliberately strict about structure but tolerant of
10
+ * whitespace — audit files are written by us and edited by no-one, so we
11
+ * can rely on the heading hierarchy and section markers. If a user has
12
+ * hand-edited the audit, the parser surfaces specific errors so the user
13
+ * can fix or discard the file rather than silently getting bad replays.
14
+ *
15
+ * Future extension: we may emit a sidecar `.plan.json` next to the
16
+ * Markdown audit to carry content hashes and `revisedMarkdown` without
17
+ * parsing overhead. The Markdown path remains supported indefinitely —
18
+ * older audits predate any sidecar format and still need replay support.
19
+ */
20
+ import type { ReviseAuditEntry } from '../models/types.js';
21
+ export interface ReplayablePlan {
22
+ /** Absolute path of the audit file this plan was parsed from. */
23
+ sourcePath: string;
24
+ /** Scope recorded in the audit header (e.g., "EPIC-001"). */
25
+ scope: string;
26
+ /** When the dry-run was started, from the audit header. */
27
+ startedAt?: string;
28
+ /** All entries, preserving original order. Includes skipped/flagged for the summary. */
29
+ entries: ReviseAuditEntry[];
30
+ }
31
+ /**
32
+ * Parse a revise Markdown audit log into its constituent entries.
33
+ * Throws if the file cannot be read, is empty, or has no entries.
34
+ */
35
+ export declare function readPlanFromAudit(auditPath: string): ReplayablePlan;
36
+ /** Entries that can actually be written on replay (have a diff and target path). */
37
+ export declare function filterReplayable(plan: ReplayablePlan): ReviseAuditEntry[];
38
+ //# sourceMappingURL=revise-plan-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revise-plan-service.d.ts","sourceRoot":"","sources":["../../src/services/revise-plan-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EACV,gBAAgB,EAIjB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wFAAwF;IACxF,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAuBnE;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,gBAAgB,EAAE,CAIzE"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Revise plan parser.
3
+ *
4
+ * A "plan" is a previously-written revise audit log that we replay to disk
5
+ * without any model calls. This service parses a Markdown audit file
6
+ * emitted by `audit-log-service.ts` back into structured `ReviseAuditEntry`
7
+ * records that the CLI's apply-from-audit path can replay.
8
+ *
9
+ * Parsing is deliberately strict about structure but tolerant of
10
+ * whitespace — audit files are written by us and edited by no-one, so we
11
+ * can rely on the heading hierarchy and section markers. If a user has
12
+ * hand-edited the audit, the parser surfaces specific errors so the user
13
+ * can fix or discard the file rather than silently getting bad replays.
14
+ *
15
+ * Future extension: we may emit a sidecar `.plan.json` next to the
16
+ * Markdown audit to carry content hashes and `revisedMarkdown` without
17
+ * parsing overhead. The Markdown path remains supported indefinitely —
18
+ * older audits predate any sidecar format and still need replay support.
19
+ */
20
+ import { readFileSync } from 'node:fs';
21
+ /**
22
+ * Parse a revise Markdown audit log into its constituent entries.
23
+ * Throws if the file cannot be read, is empty, or has no entries.
24
+ */
25
+ export function readPlanFromAudit(auditPath) {
26
+ let raw;
27
+ try {
28
+ raw = readFileSync(auditPath, 'utf-8');
29
+ }
30
+ catch (err) {
31
+ throw new Error(`Cannot read audit file at ${auditPath}: ${err instanceof Error ? err.message : String(err)}`);
32
+ }
33
+ if (raw.length === 0) {
34
+ throw new Error(`Audit file ${auditPath} is empty.`);
35
+ }
36
+ const { scope, startedAt } = parseHeader(raw, auditPath);
37
+ const entries = parseEntries(raw, auditPath);
38
+ if (entries.length === 0) {
39
+ throw new Error(`Audit file ${auditPath} contains no entries — nothing to apply. Re-run revise with --dry-run first.`);
40
+ }
41
+ return { sourcePath: auditPath, scope, startedAt, entries };
42
+ }
43
+ /** Entries that can actually be written on replay (have a diff and target path). */
44
+ export function filterReplayable(plan) {
45
+ return plan.entries.filter((e) => e.outcome === 'would-apply' && e.artifactPath && e.diff && e.diff.length > 0);
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // Parsing internals
49
+ // ---------------------------------------------------------------------------
50
+ function parseHeader(raw, auditPath) {
51
+ const title = raw.match(/^#\s+Revise audit\s+—\s+([A-Z]+-\d{3,})\b/m);
52
+ if (!title) {
53
+ throw new Error(`Audit file ${auditPath} does not start with a recognizable header (expected "# Revise audit — <SCOPE>").`);
54
+ }
55
+ const scope = title[1];
56
+ const started = raw.match(/started=([0-9T:\-.Z]+)/);
57
+ return { scope, startedAt: started ? started[1] : undefined };
58
+ }
59
+ /**
60
+ * Split the audit body into entries by the `### [outcome] ArtifactId` heading.
61
+ * Each entry then parses independently; any entry that fails to parse is
62
+ * collected with a reason in its error field rather than aborting the whole
63
+ * parse — the caller filters to `would-apply` for replay, so skipped/malformed
64
+ * entries simply won't be replayed.
65
+ */
66
+ function parseEntries(raw, auditPath) {
67
+ // The `## Entries` / `## Summary` headings bookend the body; we scan between them.
68
+ const entriesStart = raw.indexOf('\n## Entries');
69
+ const summaryStart = raw.indexOf('\n## Summary');
70
+ const body = entriesStart >= 0
71
+ ? raw.slice(entriesStart, summaryStart >= 0 ? summaryStart : raw.length)
72
+ : raw;
73
+ // Split on the entry heading. Each chunk (except the first, which is the
74
+ // `## Entries` preamble) corresponds to one entry.
75
+ const chunks = body.split(/\n(?=### \[)/);
76
+ const entries = [];
77
+ for (const chunk of chunks) {
78
+ const trimmed = chunk.trim();
79
+ if (!trimmed.startsWith('### ['))
80
+ continue;
81
+ const parsed = parseEntry(trimmed, auditPath);
82
+ if (parsed)
83
+ entries.push(parsed);
84
+ }
85
+ return entries;
86
+ }
87
+ function parseEntry(chunk, _auditPath) {
88
+ const heading = chunk.match(/^### \[([^\]]+)\]\s+(\S+)/);
89
+ if (!heading)
90
+ return null;
91
+ const outcome = heading[1];
92
+ const artifactId = heading[2];
93
+ const artifactPathMatch = chunk.match(/\n>\s+(\/[^\s\n]+\.md)/);
94
+ const artifactPath = artifactPathMatch ? artifactPathMatch[1] : undefined;
95
+ const timestampMatch = chunk.match(/timestamp=([0-9T:\-.Z]+)/);
96
+ const timestamp = timestampMatch ? timestampMatch[1] : new Date().toISOString();
97
+ const rationaleMatch = chunk.match(/\*\*Rationale:\*\*\s+([\s\S]*?)(?=\n\n\*\*|\n\n```|$)/);
98
+ const rationale = rationaleMatch ? rationaleMatch[1].trim() : '';
99
+ const evidence = parseEvidenceList(chunk);
100
+ const ambiguous = parseAmbiguousList(chunk);
101
+ const diff = parseDiffBlock(chunk);
102
+ return {
103
+ artifactId,
104
+ artifactPath,
105
+ outcome,
106
+ rationale,
107
+ evidence,
108
+ ambiguous,
109
+ diff,
110
+ timestamp,
111
+ // Older audits don't carry these; the replay flow is responsible for
112
+ // computing / verifying them against live content when present.
113
+ };
114
+ }
115
+ function parseEvidenceList(chunk) {
116
+ // Match from `**Evidence:**` heading to the next `**Xxx:**` / `## ` /
117
+ // fenced-diff / end.
118
+ const section = chunk.match(/\*\*Evidence:\*\*\s*\n([\s\S]*?)(?=\n\n\*\*[A-Z]|\n\n```|\n## |$)/);
119
+ if (!section)
120
+ return [];
121
+ const out = [];
122
+ const lineRe = /^-\s+\[([a-z_]+)\]\s+`([^`]+)`(?:\s+—\s+"([\s\S]*?)"\s*$)?/gm;
123
+ let m = lineRe.exec(section[1]);
124
+ while (m !== null) {
125
+ out.push({
126
+ type: m[1],
127
+ ref: m[2],
128
+ quote: m[3],
129
+ });
130
+ m = lineRe.exec(section[1]);
131
+ }
132
+ return out;
133
+ }
134
+ function parseAmbiguousList(chunk) {
135
+ const section = chunk.match(/\*\*Ambiguous[^*]*\*\*\s*\n([\s\S]*?)(?=\n\n\*\*[A-Z]|\n\n```|\n## |$)/);
136
+ if (!section)
137
+ return [];
138
+ const out = [];
139
+ const lineRe = /^-\s+§([^:]+):\s+(.+)$/gm;
140
+ let m = lineRe.exec(section[1]);
141
+ while (m !== null) {
142
+ out.push({ section: m[1].trim(), reason: m[2].trim() });
143
+ m = lineRe.exec(section[1]);
144
+ }
145
+ return out;
146
+ }
147
+ function parseDiffBlock(chunk) {
148
+ const m = chunk.match(/\*\*Diff:\*\*\s*\n```diff\n([\s\S]*?)\n```/);
149
+ return m ? m[1] : undefined;
150
+ }
151
+ //# sourceMappingURL=revise-plan-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revise-plan-service.js","sourceRoot":"","sources":["../../src/services/revise-plan-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * `planr revise` — core service.
3
+ *
4
+ * Exposes composable primitives. `reviseArtifact` produces dry-run decisions;
5
+ * `applyDecision` writes them to disk. The verifier context is exposed so
6
+ * callers can run `verifyDecision` against the same inputs the agent saw.
7
+ */
8
+ import { type RevisePromptArtifact, type ReviseWritableScope } from '../ai/prompts/prompt-builder.js';
9
+ import { type AIProvider, type AIUsage } from '../ai/types.js';
10
+ import type { ArtifactType, OpenPlanrConfig, ReviseAuditEntry, ReviseDecision } from '../models/types.js';
11
+ import type { AuditLogWriter } from './audit-log-service.js';
12
+ import type { EvidenceVerifierContext } from './evidence-verifier.js';
13
+ export interface ReviseArtifactOptions {
14
+ /** Must be `true` in this release; reserved for future write path. */
15
+ dryRun: true;
16
+ /** Which parts of the artifact the agent may modify. Default: 'all'. */
17
+ writableScope?: ReviseWritableScope;
18
+ /** Skip codebase context assembly (fast mode). Default: include code. */
19
+ noCodeContext?: boolean;
20
+ /** Skip immediate-sibling context gathering (fast mode / first-pass). Default: include. */
21
+ noSiblingContext?: boolean;
22
+ /** Maximum number of sibling artifacts to inject (budget guard). Default: 8. */
23
+ maxSiblings?: number;
24
+ }
25
+ export interface ReviseArtifactContextStats {
26
+ parentsLoaded: number;
27
+ siblingsLoaded: number;
28
+ codebaseContextIncluded: boolean;
29
+ sourcesLoaded: number;
30
+ }
31
+ export interface ReviseArtifactResult {
32
+ decision: ReviseDecision;
33
+ usage: AIUsage;
34
+ contextStats: ReviseArtifactContextStats;
35
+ /** Filesystem path the decision's revisedMarkdown would be written to. */
36
+ artifactPath: string;
37
+ /** Pre-revise raw content, used for diff rendering and auditing. */
38
+ originalContent: string;
39
+ /** Context the caller should pass to `verifyDecision`. */
40
+ verifierContext: EvidenceVerifierContext;
41
+ }
42
+ /**
43
+ * Error thrown when an artifact id cannot be resolved to an artifact type or
44
+ * the artifact file does not exist on disk.
45
+ */
46
+ export declare class ReviseArtifactNotFoundError extends Error {
47
+ readonly artifactId: string;
48
+ constructor(artifactId: string, message: string);
49
+ }
50
+ /**
51
+ * Revise a single artifact (dry-run).
52
+ *
53
+ * Does NOT write any files. The returned decision is the agent output after
54
+ * schema validation; evidence verification, diff preview, and write live in
55
+ * the CLI / apply path. Cascade, siblings, and declared sources are future
56
+ * extensions.
57
+ */
58
+ export declare function reviseArtifact(projectDir: string, config: OpenPlanrConfig, provider: AIProvider, artifactId: string, options: ReviseArtifactOptions): Promise<ReviseArtifactResult>;
59
+ export interface ApplyDecisionOptions {
60
+ artifactPath: string;
61
+ originalContent: string;
62
+ decision: ReviseDecision;
63
+ /**
64
+ * Directory where sidecar backups are written. Typically
65
+ * `.planr/reports/revise-<scope>-<date>/backup/` — set by the CLI.
66
+ */
67
+ backupDir: string;
68
+ /** Audit writer that will persist an entry describing the outcome. */
69
+ audit: AuditLogWriter;
70
+ /** When true, produces an audit entry but does not write the artifact. */
71
+ dryRun: boolean;
72
+ /** Cascade level tag for audit log grouping; omit for single-artifact runs. */
73
+ cascadeLevel?: ReviseAuditEntry['cascadeLevel'];
74
+ }
75
+ export interface ApplyDecisionResult {
76
+ outcome: ReviseAuditEntry['outcome'];
77
+ wrote: boolean;
78
+ diff: string;
79
+ }
80
+ /**
81
+ * Apply a (verified) decision: write the artifact atomically when
82
+ * `action === 'revise'` and `dryRun` is false, emit an audit entry
83
+ * describing the outcome either way.
84
+ *
85
+ * Caller is expected to have already run `verifyDecision` — `applyDecision`
86
+ * trusts that whatever decision arrives is allowed to be written.
87
+ */
88
+ export declare function applyDecision(options: ApplyDecisionOptions): Promise<ApplyDecisionResult>;
89
+ /**
90
+ * `true` when the agent's `revisedMarkdown` is byte-identical to `original`,
91
+ * or differs only in trailing whitespace/newlines (LLM markdown serializers
92
+ * routinely drop or add one trailing newline without changing semantics).
93
+ *
94
+ * Exported so the `--apply-from <audit>` replay path and the interactive
95
+ * UI can share the same unchanged-detection rule.
96
+ */
97
+ export declare function isEffectivelyUnchanged(original: string, revised: string | undefined): boolean;
98
+ /**
99
+ * Load immediate-sibling artifacts (same artifact type, same parent) as
100
+ * prompt entries. Lazy-reads each sibling's body only if it's going to be
101
+ * included — the listing check is cheap, the reads are budgeted by
102
+ * `maxSiblings` (default 8) so the prompt stays within token budget even on
103
+ * epics with many features.
104
+ *
105
+ * For epic scope, there are no siblings (epics live at the top). For types
106
+ * without a parent-id relationship (quick, backlog, etc.), returns empty.
107
+ */
108
+ export declare function loadSiblingPromptArtifacts(projectDir: string, config: OpenPlanrConfig, type: ArtifactType, id: string, maxSiblings: number): Promise<RevisePromptArtifact[]>;
109
+ /**
110
+ * Resolve the parent chain for an artifact and return it as the ordered
111
+ * `RevisePromptArtifact[]` the prompt builder expects (epic → feature →
112
+ * story). Empty array for top-level artifacts (epic scope).
113
+ */
114
+ export declare function loadParentPromptArtifacts(projectDir: string, config: OpenPlanrConfig, type: ArtifactType, id: string): Promise<RevisePromptArtifact[]>;
115
+ //# sourceMappingURL=revise-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revise-service.d.ts","sourceRoot":"","sources":["../../src/services/revise-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,294 @@
1
+ /**
2
+ * `planr revise` — core service.
3
+ *
4
+ * Exposes composable primitives. `reviseArtifact` produces dry-run decisions;
5
+ * `applyDecision` writes them to disk. The verifier context is exposed so
6
+ * callers can run `verifyDecision` against the same inputs the agent saw.
7
+ */
8
+ import path from 'node:path';
9
+ import { buildCodebaseContext, extractKeywords, formatCodebaseContext, } from '../ai/codebase/context-builder.js';
10
+ import { buildRevisePrompt, } from '../ai/prompts/prompt-builder.js';
11
+ import { aiReviseDecisionSchema } from '../ai/schemas/ai-response-schemas.js';
12
+ import { TOKEN_BUDGETS } from '../ai/types.js';
13
+ import { logger } from '../utils/logger.js';
14
+ import { generateJSON } from './ai-service.js';
15
+ import { findArtifactTypeById, getArtifactDir, getParentChain, listArtifacts, readArtifact, readArtifactRaw, resolveArtifactFilename, } from './artifact-service.js';
16
+ import { atomicWriteFile } from './atomic-write-service.js';
17
+ import { renderDiff } from './diff-service.js';
18
+ import { getCanonicalSections } from './template-sections.js';
19
+ /**
20
+ * Error thrown when an artifact id cannot be resolved to an artifact type or
21
+ * the artifact file does not exist on disk.
22
+ */
23
+ export class ReviseArtifactNotFoundError extends Error {
24
+ artifactId;
25
+ constructor(artifactId, message) {
26
+ super(message);
27
+ this.name = 'ReviseArtifactNotFoundError';
28
+ this.artifactId = artifactId;
29
+ }
30
+ }
31
+ /**
32
+ * Revise a single artifact (dry-run).
33
+ *
34
+ * Does NOT write any files. The returned decision is the agent output after
35
+ * schema validation; evidence verification, diff preview, and write live in
36
+ * the CLI / apply path. Cascade, siblings, and declared sources are future
37
+ * extensions.
38
+ */
39
+ export async function reviseArtifact(projectDir, config, provider, artifactId, options) {
40
+ const artifactType = findArtifactTypeById(artifactId);
41
+ if (!artifactType) {
42
+ throw new ReviseArtifactNotFoundError(artifactId, `Cannot determine artifact type from ID: ${artifactId}. Expected format: EPIC-001, FEAT-001, US-001, TASK-001.`);
43
+ }
44
+ const artifactRaw = await readArtifactRaw(projectDir, config, artifactType, artifactId);
45
+ if (artifactRaw === null) {
46
+ throw new ReviseArtifactNotFoundError(artifactId, `Artifact ${artifactId} not found under ${artifactType}/ directory.`);
47
+ }
48
+ const parents = await loadParentPromptArtifacts(projectDir, config, artifactType, artifactId);
49
+ const siblings = options.noSiblingContext
50
+ ? []
51
+ : await loadSiblingPromptArtifacts(projectDir, config, artifactType, artifactId, options.maxSiblings ?? 8);
52
+ let codebaseContextFormatted;
53
+ let codebaseCtx;
54
+ if (!options.noCodeContext) {
55
+ const keywords = extractKeywords(artifactRaw);
56
+ codebaseCtx = await buildCodebaseContext(projectDir, keywords);
57
+ codebaseContextFormatted = formatCodebaseContext(codebaseCtx);
58
+ }
59
+ const messages = buildRevisePrompt({
60
+ artifact: { id: artifactId, type: artifactType, content: artifactRaw },
61
+ parents,
62
+ siblings,
63
+ codebaseContextFormatted,
64
+ sources: [], // Declared-sources loader lands with the revise.yaml config work
65
+ writableScope: options.writableScope ?? 'all',
66
+ canonicalSections: getCanonicalSections(artifactType),
67
+ });
68
+ logger.debug(`reviseArtifact: calling AI for ${artifactId}`);
69
+ const result = await generateJSON(provider, messages, aiReviseDecisionSchema, {
70
+ maxTokens: TOKEN_BUDGETS.revise,
71
+ });
72
+ // resolveArtifactFilename strips the `.md` extension (other callers want the
73
+ // slug form). Revise must write to the full `.md` file, so append the
74
+ // extension explicitly. Defensive fallback if a future version ever returns
75
+ // a filename with the extension intact.
76
+ const filenameNoExt = await resolveArtifactFilename(projectDir, config, artifactType, artifactId);
77
+ const artifactDir = path.join(projectDir, getArtifactDir(config, artifactType));
78
+ const fullFilename = filenameNoExt.endsWith('.md') ? filenameNoExt : `${filenameNoExt}.md`;
79
+ const artifactPath = path.join(artifactDir, fullFilename);
80
+ const verifierContext = {
81
+ projectDir,
82
+ config,
83
+ artifactDir: path.dirname(artifactPath),
84
+ codebaseContextFormatted,
85
+ knownSourceRefs: [], // sources loader (future extension) will populate
86
+ knownPatternRuleIds: codebaseCtx ? codebaseCtx.patternRules.map((r) => r.name) : [],
87
+ };
88
+ return {
89
+ decision: result.result,
90
+ usage: result.usage ?? { inputTokens: 0, outputTokens: 0 },
91
+ contextStats: {
92
+ parentsLoaded: parents.length,
93
+ siblingsLoaded: siblings.length,
94
+ codebaseContextIncluded: !!codebaseContextFormatted,
95
+ sourcesLoaded: 0,
96
+ },
97
+ artifactPath,
98
+ originalContent: artifactRaw,
99
+ verifierContext,
100
+ };
101
+ }
102
+ /**
103
+ * Apply a (verified) decision: write the artifact atomically when
104
+ * `action === 'revise'` and `dryRun` is false, emit an audit entry
105
+ * describing the outcome either way.
106
+ *
107
+ * Caller is expected to have already run `verifyDecision` — `applyDecision`
108
+ * trusts that whatever decision arrives is allowed to be written.
109
+ */
110
+ export async function applyDecision(options) {
111
+ const { decision, audit, dryRun, originalContent, artifactPath, backupDir } = options;
112
+ const timestamp = new Date().toISOString();
113
+ if (decision.action === 'skip') {
114
+ audit.appendEntry({
115
+ artifactId: decision.artifactId,
116
+ artifactPath,
117
+ outcome: 'skipped-by-agent',
118
+ rationale: decision.rationale,
119
+ evidence: decision.evidence,
120
+ ambiguous: decision.ambiguous,
121
+ cascadeLevel: options.cascadeLevel,
122
+ timestamp,
123
+ });
124
+ return { outcome: 'skipped-by-agent', wrote: false, diff: '' };
125
+ }
126
+ if (decision.action === 'flag') {
127
+ // When a revise → flag demotion happened upstream, `revisedMarkdown`
128
+ // still holds the agent's proposed rewrite. Include the would-have-been
129
+ // diff in the audit entry so users can see what was rejected and decide
130
+ // whether to hand-apply it.
131
+ const proposedDiff = decision.revisedMarkdown
132
+ ? renderDiff(originalContent, decision.revisedMarkdown, {
133
+ color: false,
134
+ oldLabel: `${decision.artifactId} (before)`,
135
+ newLabel: `${decision.artifactId} (proposed — REJECTED by verifier)`,
136
+ })
137
+ : undefined;
138
+ audit.appendEntry({
139
+ artifactId: decision.artifactId,
140
+ artifactPath,
141
+ outcome: 'flagged',
142
+ rationale: decision.rationale,
143
+ evidence: decision.evidence,
144
+ ambiguous: decision.ambiguous,
145
+ cascadeLevel: options.cascadeLevel,
146
+ ...(proposedDiff ? { diff: proposedDiff } : {}),
147
+ timestamp,
148
+ });
149
+ return { outcome: 'flagged', wrote: false, diff: proposedDiff ?? '' };
150
+ }
151
+ // action === 'revise'
152
+ const diff = decision.revisedMarkdown
153
+ ? renderDiff(originalContent, decision.revisedMarkdown, {
154
+ color: false,
155
+ oldLabel: `${decision.artifactId} (before)`,
156
+ newLabel: `${decision.artifactId} (proposed)`,
157
+ })
158
+ : '';
159
+ // Short-circuit: if the agent returned content that is effectively
160
+ // identical to the original (byte-exact, or only trailing-whitespace
161
+ // differences from markdown serializer normalization), skip the write
162
+ // and report `unchanged-by-agent`. Prevents the "Proposed diff: <empty>
163
+ // → applied" UX bug where a trivial newline-strip got reported as a
164
+ // successful revise even though the agent explicitly said the artifact
165
+ // was already well-structured.
166
+ if (isEffectivelyUnchanged(originalContent, decision.revisedMarkdown)) {
167
+ audit.appendEntry({
168
+ artifactId: decision.artifactId,
169
+ artifactPath,
170
+ outcome: 'unchanged-by-agent',
171
+ rationale: decision.rationale,
172
+ evidence: decision.evidence,
173
+ ambiguous: decision.ambiguous,
174
+ cascadeLevel: options.cascadeLevel,
175
+ timestamp,
176
+ });
177
+ return { outcome: 'unchanged-by-agent', wrote: false, diff: '' };
178
+ }
179
+ if (dryRun) {
180
+ audit.appendEntry({
181
+ artifactId: decision.artifactId,
182
+ artifactPath,
183
+ outcome: 'would-apply',
184
+ rationale: decision.rationale,
185
+ evidence: decision.evidence,
186
+ ambiguous: decision.ambiguous,
187
+ cascadeLevel: options.cascadeLevel,
188
+ diff,
189
+ timestamp,
190
+ });
191
+ return { outcome: 'would-apply', wrote: false, diff };
192
+ }
193
+ // Real apply: atomic write + sidecar backup.
194
+ const backupPath = path.join(backupDir, `${decision.artifactId}.md.bak`);
195
+ await atomicWriteFile(artifactPath, decision.revisedMarkdown ?? '', { backupPath });
196
+ audit.appendEntry({
197
+ artifactId: decision.artifactId,
198
+ artifactPath,
199
+ outcome: 'applied',
200
+ rationale: decision.rationale,
201
+ evidence: decision.evidence,
202
+ ambiguous: decision.ambiguous,
203
+ cascadeLevel: options.cascadeLevel,
204
+ diff,
205
+ timestamp,
206
+ });
207
+ return { outcome: 'applied', wrote: true, diff };
208
+ }
209
+ /**
210
+ * `true` when the agent's `revisedMarkdown` is byte-identical to `original`,
211
+ * or differs only in trailing whitespace/newlines (LLM markdown serializers
212
+ * routinely drop or add one trailing newline without changing semantics).
213
+ *
214
+ * Exported so the `--apply-from <audit>` replay path and the interactive
215
+ * UI can share the same unchanged-detection rule.
216
+ */
217
+ export function isEffectivelyUnchanged(original, revised) {
218
+ if (revised === undefined)
219
+ return true;
220
+ if (revised === original)
221
+ return true;
222
+ return revised.replace(/\s+$/, '') === original.replace(/\s+$/, '');
223
+ }
224
+ /**
225
+ * Load immediate-sibling artifacts (same artifact type, same parent) as
226
+ * prompt entries. Lazy-reads each sibling's body only if it's going to be
227
+ * included — the listing check is cheap, the reads are budgeted by
228
+ * `maxSiblings` (default 8) so the prompt stays within token budget even on
229
+ * epics with many features.
230
+ *
231
+ * For epic scope, there are no siblings (epics live at the top). For types
232
+ * without a parent-id relationship (quick, backlog, etc.), returns empty.
233
+ */
234
+ export async function loadSiblingPromptArtifacts(projectDir, config, type, id, maxSiblings) {
235
+ const parentFieldByType = {
236
+ feature: 'epicId',
237
+ story: 'featureId',
238
+ task: 'storyId',
239
+ };
240
+ const parentField = parentFieldByType[type];
241
+ if (!parentField)
242
+ return [];
243
+ const self = await readArtifact(projectDir, config, type, id);
244
+ if (!self)
245
+ return [];
246
+ const parentId = self.data[parentField];
247
+ if (!parentId)
248
+ return [];
249
+ const siblings = [];
250
+ const listing = await listArtifacts(projectDir, config, type);
251
+ for (const entry of listing) {
252
+ if (siblings.length >= maxSiblings)
253
+ break;
254
+ if (entry.id === id)
255
+ continue;
256
+ const sib = await readArtifact(projectDir, config, type, entry.id);
257
+ if (sib && sib.data[parentField] === parentId) {
258
+ siblings.push({ id: entry.id, type, content: sib.content });
259
+ }
260
+ }
261
+ return siblings;
262
+ }
263
+ /**
264
+ * Resolve the parent chain for an artifact and return it as the ordered
265
+ * `RevisePromptArtifact[]` the prompt builder expects (epic → feature →
266
+ * story). Empty array for top-level artifacts (epic scope).
267
+ */
268
+ export async function loadParentPromptArtifacts(projectDir, config, type, id) {
269
+ const chain = await getParentChain(projectDir, config, type, id);
270
+ const parents = [];
271
+ if (chain.epic) {
272
+ parents.push({
273
+ id: String(chain.epic.data.id),
274
+ type: 'epic',
275
+ content: chain.epic.content,
276
+ });
277
+ }
278
+ if (chain.feature) {
279
+ parents.push({
280
+ id: String(chain.feature.data.id),
281
+ type: 'feature',
282
+ content: chain.feature.content,
283
+ });
284
+ }
285
+ if (chain.story) {
286
+ parents.push({
287
+ id: String(chain.story.data.id),
288
+ type: 'story',
289
+ content: chain.story.content,
290
+ });
291
+ }
292
+ return parents;
293
+ }
294
+ //# 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.
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