@astrosheep/keiyaku 0.1.87 → 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 (158) hide show
  1. package/README.md +12 -22
  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 -213
  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 +204 -912
  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 +0 -10
  40. package/build/config/{schema.js → env.js} +117 -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 -1
  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 +16 -9
  96. package/skills/keiyaku/SKILL.md +16 -13
  97. package/build/agents/claude-agent-sdk-exec.js +0 -352
  98. package/build/agents/codex-cli-exec.js +0 -237
  99. package/build/agents/codex-sdk-exec.js +0 -291
  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/progress-reporter.js +0 -137
  104. package/build/agents/round-runner.js +0 -53
  105. package/build/config/apply-argument-descriptions.js +0 -16
  106. package/build/config/architect-hints.js +0 -374
  107. package/build/config/base-rules.js +0 -23
  108. package/build/config/config.js +0 -50
  109. package/build/config/dotenv.js +0 -61
  110. package/build/config/keiyaku-home.js +0 -9
  111. package/build/config/path-utils.js +0 -11
  112. package/build/config/render-template.js +0 -8
  113. package/build/git/diff/filter.js +0 -67
  114. package/build/git/keiyaku-state.js +0 -58
  115. package/build/git/snapshot.js +0 -41
  116. package/build/git/stash.js +0 -62
  117. package/build/protocol/bind-draft.js +0 -195
  118. package/build/protocol/draft-artifacts.js +0 -99
  119. package/build/protocol/file-guards.js +0 -24
  120. package/build/protocol/keiyaku-draft.js +0 -168
  121. package/build/protocol/ledger.js +0 -374
  122. package/build/protocol/markdown/index.js +0 -14
  123. package/build/protocol/markdown/normalization.js +0 -71
  124. package/build/protocol/markdown/render.js +0 -68
  125. package/build/protocol/oath.js +0 -36
  126. package/build/protocol/render-keiyaku.js +0 -37
  127. package/build/protocol/review.js +0 -1
  128. package/build/protocol/status-previews.js +0 -68
  129. package/build/responses/responses.js +0 -515
  130. package/build/tools/amend/index.js +0 -18
  131. package/build/tools/amend/run.js +0 -44
  132. package/build/tools/bind/index.js +0 -44
  133. package/build/tools/bind/run.js +0 -237
  134. package/build/tools/contracts.js +0 -1
  135. package/build/tools/create-handler.js +0 -31
  136. package/build/tools/help.js +0 -9
  137. package/build/tools/petition/claim.js +0 -174
  138. package/build/tools/petition/forfeit.js +0 -100
  139. package/build/tools/petition/index.js +0 -83
  140. package/build/tools/petition/run.js +0 -66
  141. package/build/tools/round/guidance.js +0 -7
  142. package/build/tools/round/prompt.js +0 -70
  143. package/build/tools/round/report.js +0 -153
  144. package/build/tools/round/worktree.js +0 -55
  145. package/build/tools/round-close/index.js +0 -19
  146. package/build/tools/round-close/run.js +0 -98
  147. package/build/tools/round-open/index.js +0 -20
  148. package/build/tools/round-open/run.js +0 -77
  149. package/build/tools/schema.js +0 -69
  150. package/build/tools/status/index.js +0 -16
  151. package/build/tools/status/read.js +0 -155
  152. package/build/tools/summon/index.js +0 -27
  153. package/build/tools/summon/run.js +0 -80
  154. /package/build/{config/toml.js → agents/harness/events.js} +0 -0
  155. /package/build/{git/diff/contracts.js → cli/render/types.js} +0 -0
  156. /package/build/{protocol/bind-input.js → cli/types.js} +0 -0
  157. /package/build/{protocol → core}/markdown/lex.js +0 -0
  158. /package/build/{protocol → core}/markdown/types.js +0 -0
@@ -1,168 +0,0 @@
1
- import { FlowError } from "../flow-error.js";
2
- import { KeiyakuParseError, parseToAST, renderSectionContent, } from "./markdown/index.js";
3
- import { computeHeadingDelta } from "./markdown/normalization.js";
4
- const KNOWN_DRAFT_SECTIONS = new Set([
5
- "goal",
6
- "context",
7
- "rules",
8
- "criteria",
9
- "acceptance criteria",
10
- ]);
11
- function normalizeSectionTitle(title) {
12
- return title.trim().toLowerCase().replace(/\s+/g, " ");
13
- }
14
- function parseFenceLine(trimmedLine) {
15
- if (!trimmedLine.startsWith("```"))
16
- return null;
17
- let idx = 0;
18
- while (idx < trimmedLine.length && trimmedLine[idx] === "`")
19
- idx += 1;
20
- if (idx < 3)
21
- return null;
22
- return idx;
23
- }
24
- function normalizeDraftHeadings(content) {
25
- const lines = content.split(/\r?\n/);
26
- if (lines.length > 0) {
27
- lines[0] = lines[0].replace(/^\uFEFF/, "");
28
- }
29
- // Keep structural title/known section headings stable, and shift payload headings together.
30
- const MIN_HEADING_LEVEL = 3;
31
- let sawTitleH1 = false;
32
- let fence = null;
33
- let shallowestNonStructural = null;
34
- for (const line of lines) {
35
- let leadingSpaces = 0;
36
- while (leadingSpaces < line.length && line[leadingSpaces] === " ")
37
- leadingSpaces += 1;
38
- const trimmed = leadingSpaces <= 3 ? line.trimStart() : line;
39
- if (leadingSpaces <= 3) {
40
- const fenceLen = parseFenceLine(trimmed);
41
- if (fenceLen !== null) {
42
- if (!fence)
43
- fence = { length: fenceLen };
44
- else if (fenceLen >= fence.length)
45
- fence = null;
46
- continue;
47
- }
48
- }
49
- if (fence)
50
- continue;
51
- if (leadingSpaces > 3)
52
- continue;
53
- const headerMatch = trimmed.match(/^(#{1,6})[ \t]+(.+?)\s*$/);
54
- if (!headerMatch)
55
- continue;
56
- const level = (headerMatch[1] ?? "").length;
57
- const text = (headerMatch[2] ?? "").trim();
58
- if (level === 1 && !sawTitleH1) {
59
- sawTitleH1 = true;
60
- continue;
61
- }
62
- if (level === 2) {
63
- // Keep ALL H2 section headers structural in from_draft drafts. Unknown headers
64
- // should remain visible to validation instead of being silently demoted.
65
- continue;
66
- }
67
- shallowestNonStructural = shallowestNonStructural === null ? level : Math.min(shallowestNonStructural, level);
68
- }
69
- const delta = computeHeadingDelta(shallowestNonStructural, MIN_HEADING_LEVEL);
70
- if (delta === 0) {
71
- return lines.join("\n");
72
- }
73
- const out = [];
74
- sawTitleH1 = false;
75
- fence = null;
76
- for (const line of lines) {
77
- let leadingSpaces = 0;
78
- while (leadingSpaces < line.length && line[leadingSpaces] === " ")
79
- leadingSpaces += 1;
80
- const trimmed = leadingSpaces <= 3 ? line.trimStart() : line;
81
- const prefix = line.slice(0, leadingSpaces);
82
- if (leadingSpaces <= 3) {
83
- const fenceLen = parseFenceLine(trimmed);
84
- if (fenceLen !== null) {
85
- if (!fence)
86
- fence = { length: fenceLen };
87
- else if (fenceLen >= fence.length)
88
- fence = null;
89
- out.push(line);
90
- continue;
91
- }
92
- }
93
- if (fence) {
94
- out.push(line);
95
- continue;
96
- }
97
- if (leadingSpaces <= 3) {
98
- const headerMatch = trimmed.match(/^(#{1,6})[ \t]+(.+?)\s*$/);
99
- if (headerMatch) {
100
- const hashes = headerMatch[1] ?? "";
101
- const text = (headerMatch[2] ?? "").trim();
102
- const level = hashes.length;
103
- if (level === 1) {
104
- if (!sawTitleH1) {
105
- sawTitleH1 = true;
106
- out.push(line);
107
- continue;
108
- }
109
- out.push(`${prefix}${"#".repeat(Math.min(6, level + delta))} ${text}`);
110
- continue;
111
- }
112
- if (level === 2) {
113
- out.push(line);
114
- continue;
115
- }
116
- out.push(`${prefix}${"#".repeat(Math.min(6, level + delta))} ${text}`);
117
- continue;
118
- }
119
- }
120
- out.push(line);
121
- }
122
- return out.join("\n");
123
- }
124
- export function parseAndValidateKeiyakuDraft(content) {
125
- try {
126
- const normalized = normalizeDraftHeadings(content);
127
- const ast = parseToAST(normalized);
128
- const sections = new Map();
129
- let title;
130
- for (const node of ast.children) {
131
- if (node.type !== "section")
132
- continue;
133
- if (node.level === 1 && !title) {
134
- title = node.title.trim() || undefined;
135
- continue;
136
- }
137
- if (node.level !== 2)
138
- continue;
139
- const normalizedSection = normalizeSectionTitle(node.title);
140
- if (sections.has(normalizedSection)) {
141
- throw new FlowError("INVALID_KEIYAKU_DRAFT", `invalid keiyaku draft: duplicate section header '${normalizedSection}'`);
142
- }
143
- sections.set(normalizedSection, renderSectionContent(node).trim());
144
- }
145
- const unknownSections = [...sections.keys()].filter((section) => !KNOWN_DRAFT_SECTIONS.has(section));
146
- if (unknownSections.length > 0) {
147
- const allowedSections = [...KNOWN_DRAFT_SECTIONS].map((section) => `'${section}'`).join(", ");
148
- throw new FlowError("INVALID_KEIYAKU_DRAFT", `invalid keiyaku draft: unknown section header(s): ${unknownSections.map((section) => `'${section}'`).join(", ")}. Valid section header(s): ${allowedSections}. Fix the header or delete the draft file and retry.`);
149
- }
150
- const goal = sections.get("goal")?.trim();
151
- const context = sections.get("context")?.trim();
152
- const rules = sections.get("rules")?.trim();
153
- const criteria = sections.get("acceptance criteria")?.trim() ?? sections.get("criteria")?.trim();
154
- return {
155
- title: title?.trim() || undefined,
156
- goal: goal || undefined,
157
- context: context || undefined,
158
- rules: rules || undefined,
159
- criteria: criteria || undefined,
160
- };
161
- }
162
- catch (error) {
163
- if (error instanceof KeiyakuParseError) {
164
- throw new FlowError("INVALID_KEIYAKU_DRAFT", `invalid keiyaku draft: ${error.message}`, { cause: error });
165
- }
166
- throw error;
167
- }
168
- }
@@ -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
- }