@astrosheep/keiyaku 0.1.86 → 1.0.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 (157) hide show
  1. package/README.md +15 -23
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/effective-policy.js +32 -0
  4. package/build/agents/harness/event-channel.js +178 -0
  5. package/build/agents/harness/execution-handle.js +117 -0
  6. package/build/agents/harness/index.js +622 -0
  7. package/build/agents/harness/pump.js +152 -0
  8. package/build/agents/harness/runtime.js +68 -0
  9. package/build/agents/index.js +104 -212
  10. package/build/agents/opencode-sdk.js +12 -18
  11. package/build/agents/providers/claude-agent-sdk.js +529 -0
  12. package/build/agents/providers/codex-app-server.js +656 -0
  13. package/build/agents/providers/codex-sdk.js +193 -0
  14. package/build/agents/providers/opencode-sdk.js +257 -0
  15. package/build/agents/providers/pi.js +345 -0
  16. package/build/agents/selector.js +15 -6
  17. package/build/agents/session.js +29 -0
  18. package/build/agents/types.js +5 -11
  19. package/build/cli/commands/akuma.js +79 -0
  20. package/build/cli/completion.js +71 -0
  21. package/build/cli/draft-artifact.js +50 -0
  22. package/build/cli/flags.js +96 -0
  23. package/build/cli/help.js +108 -0
  24. package/build/cli/index.js +206 -874
  25. package/build/cli/parse.js +280 -0
  26. package/build/cli/render/arc.js +63 -0
  27. package/build/{responses/tool-errors.js → cli/render/errors.js} +15 -16
  28. package/build/cli/render/format.js +199 -0
  29. package/build/cli/render/misc.js +40 -0
  30. package/build/cli/render/petition.js +32 -0
  31. package/build/{responses → cli/render}/response-style.js +4 -4
  32. package/build/cli/render/shared.js +122 -0
  33. package/build/cli/render/status.js +69 -0
  34. package/build/cli/render/summon.js +17 -0
  35. package/build/cli/render/tell.js +11 -0
  36. package/build/cli/skills-install.js +237 -0
  37. package/build/cli/subagent-guard.js +6 -3
  38. package/build/config/akuma-loader.js +268 -0
  39. package/build/config/env-keys.js +2 -10
  40. package/build/config/{schema.js → env.js} +120 -23
  41. package/build/config/settings.js +137 -260
  42. package/build/core/amend.js +120 -0
  43. package/build/core/arc.js +225 -0
  44. package/build/core/atomic-publish.js +64 -0
  45. package/build/core/bind.js +281 -0
  46. package/build/core/claim.js +360 -0
  47. package/build/core/command-io.js +174 -0
  48. package/build/core/context.js +108 -0
  49. package/build/core/contract.js +14 -0
  50. package/build/core/draft.js +82 -0
  51. package/build/core/entry.js +203 -0
  52. package/build/core/forfeit.js +60 -0
  53. package/build/core/hints.js +108 -0
  54. package/build/core/ids.js +70 -0
  55. package/build/core/ledger.js +117 -0
  56. package/build/core/log.js +24 -0
  57. package/build/{protocol → core}/markdown/parser.js +68 -0
  58. package/build/{protocol → core}/markdown/sections.js +2 -28
  59. package/build/core/markdown/titles.js +9 -0
  60. package/build/{tools/petition/claim-gates.js → core/petition-claim-gates.js} +43 -29
  61. package/build/core/petition-claim.js +116 -0
  62. package/build/core/petition-forfeit.js +39 -0
  63. package/build/{tools/round/head-guard.js → core/petition-head-guard.js} +2 -2
  64. package/build/core/petition-run.js +81 -0
  65. package/build/core/petition.js +96 -0
  66. package/build/core/places.js +82 -0
  67. package/build/core/projection-core.js +541 -0
  68. package/build/core/projection-mint.js +79 -0
  69. package/build/core/projection-status.js +179 -0
  70. package/build/core/projection-wake.js +252 -0
  71. package/build/core/queue.js +73 -0
  72. package/build/core/ref-log.js +94 -0
  73. package/build/core/registry.js +96 -0
  74. package/build/core/render.js +326 -0
  75. package/build/core/renew.js +195 -0
  76. package/build/core/scope.js +163 -0
  77. package/build/core/seal.js +195 -0
  78. package/build/core/status.js +476 -0
  79. package/build/{tools/summon/persist.js → core/summon-persist.js} +10 -9
  80. package/build/core/summon.js +377 -0
  81. package/build/{protocol/response-history.js → core/transcripts.js} +83 -39
  82. package/build/core/verdict.js +96 -0
  83. package/build/core/worktree-path.js +134 -0
  84. package/build/flow-error.js +80 -4
  85. package/build/generated/version.js +1 -1
  86. package/build/git/branches.js +8 -9
  87. package/build/git/core.js +1 -2
  88. package/build/git/diff/pathspec.js +2 -3
  89. package/build/git/diff/preview.js +7 -2
  90. package/build/git/diff/read.js +4 -3
  91. package/build/git/staging.js +1 -1
  92. package/build/index.js +12 -6
  93. package/build/keiyaku.js +17 -13
  94. package/build/telemetry/debug-log.js +1 -1
  95. package/package.json +15 -8
  96. package/skills/keiyaku/SKILL.md +17 -13
  97. package/build/agents/claude-agent-sdk-exec.js +0 -248
  98. package/build/agents/codex-cli-exec.js +0 -235
  99. package/build/agents/codex-sdk-exec.js +0 -216
  100. package/build/agents/gemini-cli-core-exec.js +0 -499
  101. package/build/agents/opencode-exec.js +0 -297
  102. package/build/agents/process-runner.js +0 -239
  103. package/build/agents/round-runner.js +0 -52
  104. package/build/config/apply-argument-descriptions.js +0 -16
  105. package/build/config/architect-hints.js +0 -371
  106. package/build/config/base-rules.js +0 -23
  107. package/build/config/config.js +0 -50
  108. package/build/config/dotenv.js +0 -61
  109. package/build/config/keiyaku-home.js +0 -9
  110. package/build/config/path-utils.js +0 -11
  111. package/build/config/render-template.js +0 -8
  112. package/build/git/diff/filter.js +0 -67
  113. package/build/git/keiyaku-state.js +0 -58
  114. package/build/git/snapshot.js +0 -41
  115. package/build/git/stash.js +0 -62
  116. package/build/protocol/bind-draft.js +0 -195
  117. package/build/protocol/draft-artifacts.js +0 -99
  118. package/build/protocol/file-guards.js +0 -24
  119. package/build/protocol/keiyaku-draft.js +0 -168
  120. package/build/protocol/ledger.js +0 -374
  121. package/build/protocol/markdown/index.js +0 -14
  122. package/build/protocol/markdown/normalization.js +0 -71
  123. package/build/protocol/markdown/render.js +0 -68
  124. package/build/protocol/oath.js +0 -36
  125. package/build/protocol/render-keiyaku.js +0 -37
  126. package/build/protocol/review.js +0 -1
  127. package/build/protocol/status-previews.js +0 -68
  128. package/build/responses/responses.js +0 -530
  129. package/build/tools/amend/index.js +0 -18
  130. package/build/tools/amend/run.js +0 -44
  131. package/build/tools/bind/index.js +0 -44
  132. package/build/tools/bind/run.js +0 -237
  133. package/build/tools/contracts.js +0 -1
  134. package/build/tools/create-handler.js +0 -31
  135. package/build/tools/help.js +0 -9
  136. package/build/tools/petition/claim.js +0 -174
  137. package/build/tools/petition/forfeit.js +0 -100
  138. package/build/tools/petition/index.js +0 -83
  139. package/build/tools/petition/run.js +0 -66
  140. package/build/tools/round/guidance.js +0 -7
  141. package/build/tools/round/prompt.js +0 -70
  142. package/build/tools/round/report.js +0 -153
  143. package/build/tools/round/worktree.js +0 -55
  144. package/build/tools/round-close/index.js +0 -19
  145. package/build/tools/round-close/run.js +0 -98
  146. package/build/tools/round-open/index.js +0 -20
  147. package/build/tools/round-open/run.js +0 -77
  148. package/build/tools/schema.js +0 -68
  149. package/build/tools/status/index.js +0 -16
  150. package/build/tools/status/read.js +0 -152
  151. package/build/tools/summon/index.js +0 -27
  152. package/build/tools/summon/run.js +0 -79
  153. /package/build/{config/toml.js → agents/harness/events.js} +0 -0
  154. /package/build/{git/diff/contracts.js → cli/render/types.js} +0 -0
  155. /package/build/{protocol/bind-input.js → cli/types.js} +0 -0
  156. /package/build/{protocol → core}/markdown/lex.js +0 -0
  157. /package/build/{protocol → core}/markdown/types.js +0 -0
@@ -1,374 +0,0 @@
1
- import { createGit, wrapGitError } from "../git/core.js";
2
- import { toStatusPreview } from "./status-previews.js";
3
- const ROUND_OPEN_ROOT_HEADING = "## Round Open";
4
- const ROUND_CLOSE_ROOT_HEADING = "## Round Close";
5
- const LEDGER_SECTION_PREFIX = "### ";
6
- const LEDGER_COMMIT_SUBJECT_RE = /^keiyaku\((.+)\): round (open|close) (\d+) - (.+)$/i;
7
- const LIST_ITEM_PREFIX_RE = /^[-*+]\s+/;
8
- function normalizeTitle(value) {
9
- return value.trim().replace(/\r?\n+/g, " ");
10
- }
11
- function splitBodyLines(body) {
12
- return body.replace(/\r\n/g, "\n").split("\n");
13
- }
14
- function readSection(lines, startIndex) {
15
- const collected = [];
16
- let index = startIndex;
17
- while (index < lines.length) {
18
- const line = lines[index] ?? "";
19
- const trimmed = line.trim();
20
- if (trimmed === "## Brief" || trimmed === "## Report" || trimmed.startsWith(LEDGER_SECTION_PREFIX)) {
21
- break;
22
- }
23
- collected.push(line);
24
- index += 1;
25
- }
26
- return { body: collected.join("\n").trim(), nextIndex: index };
27
- }
28
- function readListSection(lines, startIndex) {
29
- const collected = [];
30
- let index = startIndex;
31
- while (index < lines.length) {
32
- const line = lines[index] ?? "";
33
- const trimmed = line.trim();
34
- if (trimmed === "## Report" || trimmed.startsWith(LEDGER_SECTION_PREFIX)) {
35
- break;
36
- }
37
- if (trimmed.length > 0) {
38
- collected.push(trimmed.replace(LIST_ITEM_PREFIX_RE, "").trim());
39
- }
40
- index += 1;
41
- }
42
- return {
43
- items: collected.filter((item) => item.length > 0),
44
- nextIndex: index,
45
- };
46
- }
47
- function makeHeaderReader(lines, startIndex) {
48
- let index = startIndex;
49
- return {
50
- getIndex() {
51
- return index;
52
- },
53
- setIndex(nextIndex) {
54
- index = nextIndex;
55
- },
56
- readSectionHeader(expected) {
57
- while (index < lines.length && lines[index]?.trim().length === 0) {
58
- index += 1;
59
- }
60
- if (lines[index]?.trim() !== expected) {
61
- return false;
62
- }
63
- index += 1;
64
- return true;
65
- },
66
- };
67
- }
68
- function parseRoundOpenSections(message) {
69
- const lines = splitBodyLines(message.trimEnd());
70
- let index = 0;
71
- while (index < lines.length && lines[index]?.trim().length === 0) {
72
- index += 1;
73
- }
74
- if (lines[index]?.trim() !== ROUND_OPEN_ROOT_HEADING) {
75
- return null;
76
- }
77
- index += 1;
78
- const reader = makeHeaderReader(lines, index);
79
- if (!reader.readSectionHeader("### Round"))
80
- return null;
81
- const roundSection = readSection(lines, reader.getIndex());
82
- reader.setIndex(roundSection.nextIndex);
83
- const round = Number.parseInt(roundSection.body.trim(), 10);
84
- if (!Number.isFinite(round))
85
- return null;
86
- if (!reader.readSectionHeader("### Title"))
87
- return null;
88
- const titleSection = readSection(lines, reader.getIndex());
89
- reader.setIndex(titleSection.nextIndex);
90
- const title = titleSection.body.trim();
91
- if (!title)
92
- return null;
93
- if (!reader.readSectionHeader("### Objective"))
94
- return null;
95
- const objectiveSection = readSection(lines, reader.getIndex());
96
- reader.setIndex(objectiveSection.nextIndex);
97
- const objective = objectiveSection.body.trim();
98
- if (!objective)
99
- return null;
100
- index = reader.getIndex();
101
- while (index < lines.length && lines[index]?.trim().length === 0) {
102
- index += 1;
103
- }
104
- if (lines[index]?.trim() !== "## Brief") {
105
- return null;
106
- }
107
- index += 1;
108
- const brief = lines.slice(index).join("\n").trim();
109
- if (!brief)
110
- return null;
111
- return {
112
- round,
113
- title,
114
- objective,
115
- brief,
116
- };
117
- }
118
- function parseRoundCloseSections(message) {
119
- const lines = splitBodyLines(message.trimEnd());
120
- let index = 0;
121
- while (index < lines.length && lines[index]?.trim().length === 0) {
122
- index += 1;
123
- }
124
- if (lines[index]?.trim() !== ROUND_CLOSE_ROOT_HEADING) {
125
- return null;
126
- }
127
- index += 1;
128
- const reader = makeHeaderReader(lines, index);
129
- if (!reader.readSectionHeader("### Round"))
130
- return null;
131
- const roundSection = readSection(lines, reader.getIndex());
132
- reader.setIndex(roundSection.nextIndex);
133
- const round = Number.parseInt(roundSection.body.trim(), 10);
134
- if (!Number.isFinite(round))
135
- return null;
136
- if (!reader.readSectionHeader("### Title"))
137
- return null;
138
- const titleSection = readSection(lines, reader.getIndex());
139
- reader.setIndex(titleSection.nextIndex);
140
- const title = titleSection.body.trim();
141
- if (!title)
142
- return null;
143
- if (!reader.readSectionHeader("### Objective"))
144
- return null;
145
- const objectiveSection = readSection(lines, reader.getIndex());
146
- reader.setIndex(objectiveSection.nextIndex);
147
- const objective = objectiveSection.body.trim();
148
- if (!objective)
149
- return null;
150
- if (!reader.readSectionHeader("### Commits"))
151
- return null;
152
- const commitsSection = readListSection(lines, reader.getIndex());
153
- reader.setIndex(commitsSection.nextIndex);
154
- if (commitsSection.items.length === 0)
155
- return null;
156
- index = reader.getIndex();
157
- while (index < lines.length && lines[index]?.trim().length === 0) {
158
- index += 1;
159
- }
160
- if (lines[index]?.trim() !== "## Report") {
161
- return null;
162
- }
163
- index += 1;
164
- const report = lines.slice(index).join("\n").trim();
165
- if (!report)
166
- return null;
167
- return {
168
- round,
169
- title,
170
- objective,
171
- commits: commitsSection.items,
172
- report,
173
- };
174
- }
175
- export function formatRoundOpenLedgerCommitMessage(input) {
176
- const title = normalizeTitle(input.title);
177
- const objective = input.objective.trim();
178
- const brief = input.brief.trimEnd();
179
- return [
180
- `keiyaku(${input.branchToken}): round open ${input.round} - ${title}`,
181
- "",
182
- ROUND_OPEN_ROOT_HEADING,
183
- "### Round",
184
- String(input.round),
185
- "",
186
- "### Title",
187
- title,
188
- "",
189
- "### Objective",
190
- objective,
191
- "",
192
- "## Brief",
193
- brief,
194
- "",
195
- ].join("\n");
196
- }
197
- export function formatRoundCloseLedgerCommitMessage(input) {
198
- const title = normalizeTitle(input.title);
199
- const objective = input.objective.trim();
200
- const commits = input.commits.map((commit) => commit.trim()).filter((commit) => commit.length > 0);
201
- const report = input.report.trimEnd();
202
- return [
203
- `keiyaku(${input.branchToken}): round close ${input.round} - ${title}`,
204
- "",
205
- ROUND_CLOSE_ROOT_HEADING,
206
- "### Round",
207
- String(input.round),
208
- "",
209
- "### Title",
210
- title,
211
- "",
212
- "### Objective",
213
- objective,
214
- "",
215
- "### Commits",
216
- ...commits.map((commit) => `- ${commit}`),
217
- "",
218
- "## Report",
219
- report,
220
- "",
221
- ].join("\n");
222
- }
223
- export function parseRoundLedgerCommit(commit, message) {
224
- const subject = message.split(/\r?\n/, 1)[0]?.trim() ?? "";
225
- const subjectMatch = LEDGER_COMMIT_SUBJECT_RE.exec(subject);
226
- if (!subjectMatch)
227
- return null;
228
- const kind = subjectMatch[2];
229
- const subjectRound = Number.parseInt(subjectMatch[3] ?? "", 10);
230
- const subjectTitle = subjectMatch[4] ?? "";
231
- const bodyStart = message.indexOf("\n");
232
- const body = bodyStart === -1 ? "" : message.slice(bodyStart + 1);
233
- const parsed = kind === "open" ? parseRoundOpenSections(body) : parseRoundCloseSections(body);
234
- if (!parsed)
235
- return null;
236
- if (parsed.round !== subjectRound)
237
- return null;
238
- if (normalizeTitle(parsed.title) !== normalizeTitle(subjectTitle))
239
- return null;
240
- return {
241
- ...parsed,
242
- kind,
243
- commit,
244
- branchToken: subjectMatch[1] ?? "",
245
- };
246
- }
247
- export async function scanRoundLedger(cwd) {
248
- const git = createGit(cwd);
249
- try {
250
- const output = await git.raw(["log", "--reverse", "--format=%H%x00%B%x1e", "HEAD"]);
251
- return output
252
- .split("\x1e")
253
- .map((record) => record.trim())
254
- .filter((record) => record.length > 0)
255
- .flatMap((record) => {
256
- const separatorIndex = record.indexOf("\u0000");
257
- if (separatorIndex === -1) {
258
- return [];
259
- }
260
- const commit = record.slice(0, separatorIndex).trim();
261
- const message = record.slice(separatorIndex + 1);
262
- const parsed = parseRoundLedgerCommit(commit, message);
263
- return parsed ? [parsed] : [];
264
- });
265
- }
266
- catch (error) {
267
- throw wrapGitError("log round ledger", error, cwd);
268
- }
269
- }
270
- export function buildRoundLedgerRounds(entries) {
271
- const rounds = new Map();
272
- for (const entry of entries) {
273
- const current = rounds.get(entry.round) ?? {
274
- round: entry.round,
275
- branchToken: entry.branchToken,
276
- title: entry.title,
277
- };
278
- current.branchToken = current.open?.branchToken ?? current.close?.branchToken ?? entry.branchToken;
279
- current.title = current.open?.title ?? current.close?.title ?? entry.title;
280
- if (entry.kind === "open") {
281
- current.open = entry;
282
- current.branchToken = entry.branchToken;
283
- current.title = entry.title;
284
- }
285
- else {
286
- current.close = entry;
287
- if (!current.open) {
288
- current.branchToken = entry.branchToken;
289
- current.title = entry.title;
290
- }
291
- }
292
- rounds.set(entry.round, current);
293
- }
294
- return [...rounds.values()].sort((left, right) => left.round - right.round);
295
- }
296
- export function findOpenRound(entries) {
297
- return buildRoundLedgerRounds(entries)
298
- .filter((round) => round.open && !round.close)
299
- .at(-1) ?? null;
300
- }
301
- export function findLatestClosedRound(entries) {
302
- return buildRoundLedgerRounds(entries)
303
- .filter((round) => round.close)
304
- .at(-1) ?? null;
305
- }
306
- export async function getNextRoundNumber(cwd) {
307
- const entries = await scanRoundLedger(cwd);
308
- const maxRound = entries.reduce((max, entry) => Math.max(max, entry.round), 0);
309
- return maxRound + 1;
310
- }
311
- export function getMaxRound(entries) {
312
- return entries.reduce((maxRound, entry) => Math.max(maxRound, entry.round), 0);
313
- }
314
- export function renderRoundLedgerRound(round) {
315
- const open = round.open;
316
- const close = round.close;
317
- const objective = open?.objective ?? close?.objective ?? "";
318
- const lines = [
319
- `## Round ${round.round} - ${round.title}`,
320
- "",
321
- "### Status",
322
- close ? "Closed" : "Open",
323
- "",
324
- "### Branch",
325
- round.branchToken,
326
- "",
327
- ];
328
- if (open) {
329
- lines.push("### Open Ledger Commit", open.commit, "", "### Objective", objective, "", "### Brief", open.brief.trimEnd(), "");
330
- }
331
- else if (objective) {
332
- lines.push("### Objective", objective, "");
333
- }
334
- if (close) {
335
- lines.push("### Close Ledger Commit", close.commit, "", "### Commits", ...close.commits.map((commit) => `- ${commit}`), "", "### Report", close.report.trimEnd(), "");
336
- }
337
- return lines.join("\n");
338
- }
339
- export function renderRoundLedgerHistory(entries) {
340
- if (entries.length === 0) {
341
- return "No round ledger entries recorded.";
342
- }
343
- return buildRoundLedgerRounds(entries).map((round) => renderRoundLedgerRound(round)).join("\n");
344
- }
345
- export function renderRoundLedgerPreview(round) {
346
- if (!round)
347
- return undefined;
348
- const open = round.open;
349
- const close = round.close;
350
- const lines = [
351
- `Round ${round.round} - ${round.title}`,
352
- `Status: ${close ? "Closed" : "Open"}`,
353
- ];
354
- const objective = open?.objective ?? close?.objective;
355
- if (objective) {
356
- lines.push(`Objective: ${objective}`);
357
- }
358
- if (open) {
359
- const briefPreview = toStatusPreview(open.brief);
360
- if (briefPreview) {
361
- lines.push(`Brief: ${briefPreview}`);
362
- }
363
- lines.push(`Open Ledger Commit: ${open.commit}`);
364
- }
365
- if (close) {
366
- lines.push(`Commits: ${close.commits.join(", ")}`);
367
- lines.push(`Close Ledger Commit: ${close.commit}`);
368
- const reportPreview = toStatusPreview(close.report);
369
- if (reportPreview) {
370
- lines.push(`Report: ${reportPreview}`);
371
- }
372
- }
373
- return lines.join("\n");
374
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * Responsibility boundary:
3
- * - This module is a markdown-like parser/renderer utility layer only.
4
- * - It handles syntax-level concerns (tokens, AST, section extraction, rendering).
5
- * - It MUST NOT encode keiyaku workflow/domain semantics such as required fields
6
- * (goal/context/rules/criteria), reserved business section names, or
7
- * bind/round policy decisions.
8
- * - Domain-specific draft/schema logic belongs in tool/protocol modules.
9
- */
10
- export { KeiyakuParseError, } from "./types.js";
11
- export { parseToAST } from "./parser.js";
12
- export { computeHeadingDelta, demoteMarkdownHeadings, renderMarkdownSections } from "./normalization.js";
13
- export { extractListItems, renderNodeContent, renderSectionContent } from "./render.js";
14
- export { hasTopLevelHeaders, parseMarkdownListSection, parseMarkdownSections, parseMarkdownStructure } from "./sections.js";
@@ -1,71 +0,0 @@
1
- import { parseToAST } from "./parser.js";
2
- import { renderNodeContent } from "./render.js";
3
- export function computeHeadingDelta(shallowest, minLevel) {
4
- if (shallowest === null || shallowest >= minLevel) {
5
- return 0;
6
- }
7
- return minLevel - shallowest;
8
- }
9
- export function demoteMarkdownHeadings(text, minLevel = 3) {
10
- const ast = parseToAST(text, { allowSections: false });
11
- // Preserve relative heading hierarchy by shifting all headings together.
12
- // This avoids collapsing something like `# A` and `### B` into the same level.
13
- let shallowest = null;
14
- {
15
- const stack = [ast];
16
- while (stack.length > 0) {
17
- const node = stack.pop();
18
- if (!node)
19
- continue;
20
- if (node.type === "heading") {
21
- shallowest = shallowest === null ? node.level : Math.min(shallowest, node.level);
22
- }
23
- if (node.type === "code_block" || node.type === "blockquote" || node.type === "text")
24
- continue;
25
- if (node.type === "document" || node.type === "section" || node.type === "list_item") {
26
- stack.push(...node.children);
27
- }
28
- else if (node.type === "list") {
29
- stack.push(...node.items);
30
- }
31
- }
32
- }
33
- const delta = computeHeadingDelta(shallowest, minLevel);
34
- if (delta === 0) {
35
- // Return a normalized render (trimEnd) to avoid leaking extra trailing newlines.
36
- return renderNodeContent(ast).trimEnd();
37
- }
38
- const shift = (node) => {
39
- switch (node.type) {
40
- case "heading": {
41
- const nextLevel = Math.min(6, node.level + delta);
42
- return nextLevel === node.level ? node : { ...node, level: nextLevel };
43
- }
44
- case "code_block":
45
- case "blockquote":
46
- case "text":
47
- return node;
48
- case "document":
49
- case "section":
50
- return { ...node, children: node.children.map((child) => shift(child)) };
51
- case "list":
52
- return { ...node, items: node.items.map((item) => shift(item)) };
53
- case "list_item":
54
- return { ...node, children: node.children.map((child) => shift(child)) };
55
- }
56
- };
57
- return renderNodeContent(shift(ast)).trimEnd();
58
- }
59
- export function renderMarkdownSections(items) {
60
- return items
61
- .map((item) => {
62
- // Keiyaku uses H2 as contract section delimiters. Keep payload headings at H3+.
63
- const normalized = demoteMarkdownHeadings(item, 3);
64
- const trimmed = normalized.trimStart();
65
- if (/^[-*+]\s+/.test(trimmed) || /^\d+[.)]\s+/.test(trimmed)) {
66
- return normalized;
67
- }
68
- return `- ${normalized}`;
69
- })
70
- .join("\n\n");
71
- }
@@ -1,68 +0,0 @@
1
- function renderListItemFirstLine(item) {
2
- const content = renderNodeContent(item);
3
- if (!content) {
4
- return `${" ".repeat(item.indent)}${item.marker}`;
5
- }
6
- const [firstLine, ...rest] = content.split("\n");
7
- const prefix = `${" ".repeat(item.indent)}${item.marker}${firstLine.length > 0 ? " " : ""}${firstLine}`;
8
- if (rest.length === 0) {
9
- return prefix;
10
- }
11
- return [prefix, ...rest].join("\n");
12
- }
13
- export function renderBlock(node) {
14
- switch (node.type) {
15
- case "section": {
16
- const header = `${"#".repeat(node.level)} ${node.title}`;
17
- const body = renderNodeContent(node);
18
- return body ? `${header}\n${body}` : header;
19
- }
20
- case "list":
21
- return node.items.map((item) => renderListItemFirstLine(item)).join("\n");
22
- case "code_block":
23
- return node.lines.join("\n");
24
- case "blockquote":
25
- return node.lines.map((line) => `${node.marker}${line}`).join("\n");
26
- case "text":
27
- return node.value;
28
- case "heading":
29
- return `${"#".repeat(node.level)} ${node.text}`;
30
- }
31
- }
32
- export function renderNodeContent(node) {
33
- switch (node.type) {
34
- case "document":
35
- return node.children.map((child) => renderBlock(child)).join("\n");
36
- case "section":
37
- return node.children.map((child) => renderBlock(child)).join("\n");
38
- case "list":
39
- return renderBlock(node);
40
- case "list_item":
41
- return node.children.map((child) => renderBlock(child)).join("\n");
42
- case "code_block":
43
- return node.lines.join("\n");
44
- case "blockquote":
45
- return node.lines.map((line) => `${node.marker}${line}`).join("\n");
46
- case "text":
47
- return node.value;
48
- case "heading":
49
- return `${"#".repeat(node.level)} ${node.text}`;
50
- }
51
- }
52
- export function renderSectionContent(sectionNode) {
53
- return sectionNode.children.map((child) => renderBlock(child)).join("\n");
54
- }
55
- export function extractListItems(sectionNode) {
56
- const items = [];
57
- for (const child of sectionNode.children) {
58
- if (child.type !== "list")
59
- continue;
60
- for (const item of child.items) {
61
- const rendered = renderNodeContent(item).trimEnd();
62
- if (rendered.trim().length > 0) {
63
- items.push(rendered);
64
- }
65
- }
66
- }
67
- return items;
68
- }
@@ -1,36 +0,0 @@
1
- import { getConfig } from "../config/config.js";
2
- const OATH_NAME_PLACEHOLDER = "[your name]";
3
- export function resolveOath() {
4
- return getConfig().workflow.closeOath;
5
- }
6
- export function oathMatches(inputOath, expectedOath) {
7
- const actual = inputOath?.trim();
8
- if (!actual)
9
- return false;
10
- const expected = expectedOath.trim();
11
- if (!expected.includes(OATH_NAME_PLACEHOLDER)) {
12
- return actual === expected;
13
- }
14
- const parts = expected.split(OATH_NAME_PLACEHOLDER);
15
- let cursor = 0;
16
- for (let idx = 0; idx < parts.length; idx += 1) {
17
- const part = parts[idx] ?? "";
18
- if (!actual.startsWith(part, cursor)) {
19
- return false;
20
- }
21
- cursor += part.length;
22
- if (idx === parts.length - 1)
23
- break;
24
- const nextPart = parts[idx + 1] ?? "";
25
- const nextPartIndex = nextPart ? actual.indexOf(nextPart, cursor) : actual.length;
26
- if (nextPartIndex < 0) {
27
- return false;
28
- }
29
- const replacement = actual.slice(cursor, nextPartIndex);
30
- if (replacement.trim().length === 0) {
31
- return false;
32
- }
33
- cursor = nextPartIndex;
34
- }
35
- return cursor === actual.length;
36
- }
@@ -1,37 +0,0 @@
1
- import { BASE_RULES_FILE } from "../keiyaku.js";
2
- import { demoteMarkdownHeadings } from "./markdown/normalization.js";
3
- export const AMENDMENT_ENTRY_TITLE_PREFIX = "Amendment";
4
- const AMENDMENT_BODY_HEADING_LEVEL = 4;
5
- export function renderKeiyaku(title, goal, context, baseRules, taskRules, taskCriteria) {
6
- const normalizedContext = demoteMarkdownHeadings(context, 3);
7
- const normalizedGoal = demoteMarkdownHeadings(goal, 3);
8
- const normalizedBaseRules = demoteMarkdownHeadings(baseRules, 4).trim();
9
- const normalizedTaskRules = demoteMarkdownHeadings(taskRules, 4).trim();
10
- const normalizedTaskCriteria = demoteMarkdownHeadings(taskCriteria, 4).trim();
11
- let content = `# ${title}\n\n## Goal\n${normalizedGoal}\n\n## Context\n${normalizedContext}`;
12
- content += "\n\n## Rules";
13
- content += `\n\n### Task Rules\n${normalizedTaskRules}`;
14
- if (normalizedBaseRules.length > 0) {
15
- content += `\n\n### Project Rules\nLoaded from: \`${BASE_RULES_FILE}\`\n\n${normalizedBaseRules}`;
16
- }
17
- content += "\n\n## Acceptance Criteria";
18
- content += `\n\n### Task Criteria\n${normalizedTaskCriteria}`;
19
- return content;
20
- }
21
- function renderAmendmentEntry(number, amendment) {
22
- const normalizedAmendment = demoteMarkdownHeadings(amendment, AMENDMENT_BODY_HEADING_LEVEL).trim();
23
- return [`## ${AMENDMENT_ENTRY_TITLE_PREFIX} ${number}`, "", normalizedAmendment].join("\n");
24
- }
25
- function countAmendmentEntries(content) {
26
- const matches = content.match(new RegExp(`^## ${AMENDMENT_ENTRY_TITLE_PREFIX} \\d+\\s*$`, "gm"));
27
- return matches?.length ?? 0;
28
- }
29
- export function appendKeiyakuAmendment(content, input) {
30
- const normalizedContent = content.trimEnd();
31
- const amendmentNumber = countAmendmentEntries(normalizedContent) + 1;
32
- const entry = renderAmendmentEntry(amendmentNumber, input.amendment);
33
- return {
34
- content: `${normalizedContent}\n\n${entry}\n`,
35
- amendmentNumber,
36
- };
37
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,68 +0,0 @@
1
- import { parseToAST, renderNodeContent } from "./markdown/index.js";
2
- const PREVIEW_SECTION_TITLES = ["goal", "context"];
3
- const PREVIEW_SECTION_TITLE_SET = new Set(PREVIEW_SECTION_TITLES);
4
- const STATUS_PREVIEW_MAX_LINES = 2;
5
- const STATUS_PREVIEW_MAX_LINE_CHARS = 180;
6
- function normalizeHeading(value) {
7
- return value.trim().toLowerCase();
8
- }
9
- function renderNodesForPreview(nodes) {
10
- if (nodes.length === 0)
11
- return "";
12
- return nodes
13
- .map((node) => {
14
- if (node.type === "blockquote") {
15
- return node.value.trim();
16
- }
17
- return renderNodeContent(node).trim();
18
- })
19
- .filter((text) => text.length > 0)
20
- .join("\n")
21
- .trim();
22
- }
23
- export function extractDocumentPreviewBySections(content, fallbackToDocument) {
24
- const ast = parseToAST(content, { allowSections: true });
25
- const sectionBodies = new Map();
26
- for (const node of ast.children) {
27
- if (node.type !== "section" || node.level !== 2)
28
- continue;
29
- const normalizedTitle = normalizeHeading(node.title);
30
- if (!PREVIEW_SECTION_TITLE_SET.has(normalizedTitle))
31
- continue;
32
- if (sectionBodies.has(normalizedTitle))
33
- continue;
34
- const body = renderNodesForPreview(node.children);
35
- if (body.length === 0)
36
- continue;
37
- sectionBodies.set(normalizedTitle, body);
38
- }
39
- const preferredBody = PREVIEW_SECTION_TITLES
40
- .map((title) => sectionBodies.get(title))
41
- .filter((value) => Boolean(value))
42
- .join("\n")
43
- .trim();
44
- if (preferredBody.length > 0) {
45
- return preferredBody;
46
- }
47
- if (!fallbackToDocument)
48
- return undefined;
49
- const fullDocument = renderNodeContent(ast).trim();
50
- return fullDocument.length > 0 ? fullDocument : undefined;
51
- }
52
- export function toStatusPreview(text) {
53
- if (!text)
54
- return undefined;
55
- const lines = text
56
- .split(/\r?\n/)
57
- .map((line) => line.trim())
58
- .filter((line) => line.length > 0);
59
- if (lines.length === 0)
60
- return undefined;
61
- const previewLines = lines
62
- .slice(0, STATUS_PREVIEW_MAX_LINES)
63
- .map((line) => (line.length > STATUS_PREVIEW_MAX_LINE_CHARS ? `${line.slice(0, STATUS_PREVIEW_MAX_LINE_CHARS - 1)}…` : line));
64
- if (lines.length > STATUS_PREVIEW_MAX_LINES) {
65
- previewLines.push("…");
66
- }
67
- return previewLines.join("\n");
68
- }