@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,41 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as os from "os";
3
- import * as path from "path";
4
- import { NOT_GIT_REPOSITORY_PATTERNS, createGit, errorContainsAnyPattern, wrapGitError } from "./core.js";
5
- async function withTempIndex(cwd, run) {
6
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "keiyaku-git-index-"));
7
- const tempIndexPath = path.join(tempDir, "index");
8
- const git = createGit(cwd);
9
- git.env("GIT_INDEX_FILE", tempIndexPath);
10
- try {
11
- return await run(git);
12
- }
13
- finally {
14
- await fs.rm(tempDir, { recursive: true, force: true });
15
- }
16
- }
17
- export async function createWorkspaceSnapshot(cwd) {
18
- try {
19
- return await withTempIndex(cwd, async (git) => {
20
- await git.raw(["add", "-A"]);
21
- const tree = await git.raw(["write-tree"]);
22
- return tree.trim() || undefined;
23
- });
24
- }
25
- catch (error) {
26
- if (errorContainsAnyPattern(error, NOT_GIT_REPOSITORY_PATTERNS)) {
27
- return undefined;
28
- }
29
- throw wrapGitError("create workspace snapshot", error, cwd);
30
- }
31
- }
32
- export async function getChangedFilesBetweenSnapshots(cwd, beforeSha, afterSha) {
33
- const git = createGit(cwd);
34
- try {
35
- const output = await git.raw(["diff", "--name-status", beforeSha, afterSha]);
36
- return output.trim();
37
- }
38
- catch (err) {
39
- throw wrapGitError(`diff --name-status ${beforeSha} ${afterSha}`, err, cwd);
40
- }
41
- }
@@ -1,62 +0,0 @@
1
- import { createGit, wrapGitError } from "./core.js";
2
- function parseStashRefs(raw) {
3
- return raw
4
- .split(/\r?\n/)
5
- .map((line) => line.trim())
6
- .filter((line) => line.length > 0);
7
- }
8
- async function listStashRefs(cwd) {
9
- const git = createGit(cwd);
10
- try {
11
- const output = await git.raw(["stash", "list", "--format=%gd"]);
12
- return parseStashRefs(output);
13
- }
14
- catch (err) {
15
- throw wrapGitError("stash list --format=%gd", err, cwd);
16
- }
17
- }
18
- export async function createSafetyStash(cwd, message) {
19
- const before = await listStashRefs(cwd);
20
- const git = createGit(cwd);
21
- try {
22
- await git.raw(["stash", "push", "--include-untracked", "--message", message]);
23
- }
24
- catch (err) {
25
- throw wrapGitError(`stash push --include-untracked --message ${message}`, err, cwd);
26
- }
27
- const after = await listStashRefs(cwd);
28
- if (after.length === before.length) {
29
- return null;
30
- }
31
- return after[0] ?? null;
32
- }
33
- export async function applyStash(cwd, stashRef) {
34
- const git = createGit(cwd);
35
- try {
36
- await git.raw(["stash", "apply", stashRef]);
37
- }
38
- catch (err) {
39
- throw wrapGitError(`stash apply ${stashRef}`, err, cwd);
40
- }
41
- }
42
- export async function dropStash(cwd, stashRef) {
43
- const git = createGit(cwd);
44
- try {
45
- await git.raw(["stash", "drop", stashRef]);
46
- }
47
- catch (err) {
48
- throw wrapGitError(`stash drop ${stashRef}`, err, cwd);
49
- }
50
- }
51
- export async function discardAllWorkingTreeChanges(cwd) {
52
- await resetHardHead(cwd);
53
- }
54
- export async function resetHardHead(cwd) {
55
- const git = createGit(cwd);
56
- try {
57
- await git.raw(["reset", "--hard", "HEAD"]);
58
- }
59
- catch (err) {
60
- throw wrapGitError("reset --hard HEAD", err, cwd);
61
- }
62
- }
@@ -1,195 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
3
- import { isErrnoException } from "../errno.js";
4
- import { buildBindDraftRecoveryHints, buildBindDraftSaveFailureHint, } from "../config/architect-hints.js";
5
- import { FlowError, isFlowError, requireText, wrapFlowError } from "../flow-error.js";
6
- import { writeDraftArtifact, listDraftArtifacts } from "./draft-artifacts.js";
7
- import { logInfo } from "../telemetry/logger.js";
8
- import { parseAndValidateKeiyakuDraft } from "./keiyaku-draft.js";
9
- const INTERNAL_BIND_DIRTY_ALLOWLIST = [];
10
- const REQUIRED_BIND_FIELDS = ["title", "goal", "context", "criteria"];
11
- function normalizeOptionalText(value) {
12
- if (value === undefined)
13
- return undefined;
14
- const normalized = value.trim();
15
- return normalized.length > 0 ? normalized : undefined;
16
- }
17
- function resolveFromDraftPath(cwd, fromDraft) {
18
- return path.isAbsolute(fromDraft) ? fromDraft : path.join(cwd, fromDraft);
19
- }
20
- export function resolveGitRelativePath(cwd, filePath) {
21
- const relativePath = path.relative(cwd, filePath);
22
- if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
23
- return null;
24
- }
25
- return relativePath.split(path.sep).join("/");
26
- }
27
- function normalizeOptionalMarkdownText(name, value) {
28
- if (value === undefined)
29
- return undefined;
30
- if (typeof value !== "string") {
31
- throw new FlowError("INVALID_KEIYAKU_DRAFT", `invalid keiyaku draft: field '${name}' must be a markdown string`);
32
- }
33
- const normalized = value.trim();
34
- return normalized.length > 0 ? normalized : undefined;
35
- }
36
- function assertRequiredBindFields(values) {
37
- for (const field of REQUIRED_BIND_FIELDS) {
38
- if (!values[field]) {
39
- throw new FlowError("INVALID_KEIYAKU_DRAFT", `invalid keiyaku draft: missing required field '${field}'`);
40
- }
41
- }
42
- }
43
- export async function resolveBindInput(cwd, input) {
44
- const fromDraft = normalizeOptionalText(input.from_draft);
45
- const dirtyAllowlist = [...INTERNAL_BIND_DIRTY_ALLOWLIST];
46
- const provided = {
47
- title: normalizeOptionalText(input.title),
48
- goal: normalizeOptionalText(input.goal),
49
- context: normalizeOptionalText(input.context),
50
- criteria: normalizeOptionalMarkdownText("criteria", input.criteria),
51
- rules: normalizeOptionalMarkdownText("rules", input.rules),
52
- };
53
- if (!fromDraft) {
54
- assertRequiredBindFields({
55
- title: provided.title,
56
- goal: provided.goal,
57
- context: provided.context,
58
- criteria: provided.criteria,
59
- });
60
- return {
61
- title: requireText("title", provided.title),
62
- goal: requireText("goal", provided.goal),
63
- context: requireText("context", provided.context),
64
- criteria: requireText("criteria", provided.criteria),
65
- rules: provided.rules ?? "",
66
- fromDraft: undefined,
67
- fromDraftPath: undefined,
68
- dirtyAllowlist,
69
- };
70
- }
71
- const draftPath = resolveFromDraftPath(cwd, fromDraft);
72
- let content;
73
- try {
74
- content = await fs.readFile(draftPath, "utf-8");
75
- }
76
- catch (error) {
77
- if (isErrnoException(error) && error.code === "ENOENT") {
78
- throw new FlowError("FROM_DRAFT_NOT_FOUND", `from_draft path does not exist: ${fromDraft}`);
79
- }
80
- throw error;
81
- }
82
- const draft = parseAndValidateKeiyakuDraft(content);
83
- const title = provided.title ?? draft.title;
84
- const goal = provided.goal ?? draft.goal;
85
- const context = provided.context ?? draft.context;
86
- const criteria = provided.criteria ?? draft.criteria;
87
- const rules = provided.rules ?? draft.rules ?? "";
88
- assertRequiredBindFields({
89
- title,
90
- goal,
91
- context,
92
- criteria,
93
- });
94
- return {
95
- title: requireText("title", title),
96
- goal: requireText("goal", goal),
97
- context: requireText("context", context),
98
- criteria: requireText("criteria", criteria),
99
- rules,
100
- fromDraft,
101
- fromDraftPath: draftPath,
102
- dirtyAllowlist,
103
- };
104
- }
105
- export async function readRollbackDraftSnapshot(cwd, candidatePath) {
106
- const relativePath = resolveGitRelativePath(cwd, candidatePath);
107
- if (!relativePath)
108
- return null;
109
- const absolutePath = path.join(cwd, relativePath);
110
- try {
111
- const stat = await fs.stat(absolutePath);
112
- if (!stat.isFile())
113
- return null;
114
- const content = await fs.readFile(absolutePath);
115
- return { relativePath, content };
116
- }
117
- catch (error) {
118
- if (isErrnoException(error) && error.code === "ENOENT") {
119
- return null;
120
- }
121
- throw error;
122
- }
123
- }
124
- export async function captureRollbackDraftSnapshots(cwd, fromDraftPath) {
125
- const canonicalDraftPaths = await listDraftArtifacts(cwd);
126
- const candidatePaths = canonicalDraftPaths.map((relativePath) => path.join(cwd, relativePath));
127
- if (fromDraftPath)
128
- candidatePaths.push(fromDraftPath);
129
- const snapshots = await Promise.all(candidatePaths.map((candidatePath) => readRollbackDraftSnapshot(cwd, candidatePath)));
130
- const uniqueSnapshots = new Map();
131
- for (const snapshot of snapshots) {
132
- if (!snapshot)
133
- continue;
134
- uniqueSnapshots.set(snapshot.relativePath, snapshot);
135
- }
136
- return [...uniqueSnapshots.values()];
137
- }
138
- export async function restoreRollbackDraftSnapshots(cwd, snapshots) {
139
- for (const snapshot of snapshots) {
140
- const absolutePath = path.join(cwd, snapshot.relativePath);
141
- await fs.mkdir(path.dirname(absolutePath), { recursive: true });
142
- await fs.writeFile(absolutePath, snapshot.content);
143
- }
144
- }
145
- export async function handleBindFailure(cwd, error, keiyakuContent, title, fromDraft) {
146
- let savedDraft = false;
147
- let retryHints = [];
148
- let draftSaveFailed;
149
- if (keiyakuContent && !fromDraft) {
150
- try {
151
- const draftPath = await writeDraftArtifact({
152
- cwd,
153
- title,
154
- content: keiyakuContent,
155
- });
156
- retryHints = buildBindDraftRecoveryHints(draftPath);
157
- logInfo(`[keiyaku] Bind failed. Draft saved to ${draftPath}.`, { cwd, section: "script", progressOnly: true });
158
- savedDraft = true;
159
- }
160
- catch (draftSaveError) {
161
- const message = draftSaveError instanceof Error ? draftSaveError.message : String(draftSaveError);
162
- draftSaveFailed = message;
163
- logInfo(`[keiyaku] Failed to save draft file: ${message}`, { cwd, section: "script", progressOnly: true });
164
- }
165
- }
166
- if (error instanceof Error && error.name === "AbortError") {
167
- throw wrapFlowError("bind keiyaku", error);
168
- }
169
- if (savedDraft) {
170
- if (isFlowError(error)) {
171
- throw wrapFlowError("bind keiyaku", new FlowError(error.code, error.message, {
172
- cause: error,
173
- hints: retryHints,
174
- }));
175
- }
176
- if (error instanceof Error) {
177
- throw wrapFlowError("bind keiyaku", new FlowError("INTERNAL_STATE", error.message, { cause: error, hints: retryHints }));
178
- }
179
- throw wrapFlowError("bind keiyaku", new FlowError("INTERNAL_STATE", String(error), { hints: retryHints }));
180
- }
181
- if (draftSaveFailed) {
182
- const draftSaveHint = buildBindDraftSaveFailureHint(draftSaveFailed);
183
- if (isFlowError(error)) {
184
- throw wrapFlowError("bind keiyaku", new FlowError(error.code, error.message, {
185
- cause: error,
186
- hints: [draftSaveHint],
187
- }));
188
- }
189
- if (error instanceof Error) {
190
- throw wrapFlowError("bind keiyaku", new FlowError("INTERNAL_STATE", error.message, { cause: error, hints: [draftSaveHint] }));
191
- }
192
- throw wrapFlowError("bind keiyaku", new FlowError("INTERNAL_STATE", String(error), { hints: [draftSaveHint] }));
193
- }
194
- throw wrapFlowError("bind keiyaku", error);
195
- }
@@ -1,99 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
3
- import { DRAFT_ARTIFACT_NOTICE, KEIYAKU_DRAFT_DIR } from "../keiyaku.js";
4
- import { isErrnoException } from "../errno.js";
5
- const DRAFT_SLUG_FALLBACK = "keiyaku-draft";
6
- function normalizeDraftRelativePath(relativePath) {
7
- return relativePath.split(path.sep).join("/");
8
- }
9
- function formatDraftTimestamp(now) {
10
- const year = String(now.getFullYear());
11
- const month = String(now.getMonth() + 1).padStart(2, "0");
12
- const day = String(now.getDate()).padStart(2, "0");
13
- const hour = String(now.getHours()).padStart(2, "0");
14
- const minute = String(now.getMinutes()).padStart(2, "0");
15
- const second = String(now.getSeconds()).padStart(2, "0");
16
- return `${year}${month}${day}-${hour}${minute}${second}`;
17
- }
18
- function toDraftSlug(title) {
19
- const normalized = title
20
- .trim()
21
- .toLowerCase()
22
- .replace(/[^\p{Script=Han}a-z0-9]+/gu, "-")
23
- .replace(/-+/g, "-")
24
- .replace(/^-+/, "")
25
- .replace(/-+$/, "");
26
- return normalized || DRAFT_SLUG_FALLBACK;
27
- }
28
- export function isDraftArtifactPath(filePath) {
29
- const normalized = normalizeDraftRelativePath(filePath).replace(/^\.\/+/, "");
30
- const draftPrefix = `${normalizeDraftRelativePath(KEIYAKU_DRAFT_DIR)}/`;
31
- return normalized === normalizeDraftRelativePath(KEIYAKU_DRAFT_DIR) || normalized.startsWith(draftPrefix);
32
- }
33
- export function buildDraftArtifactRelativePath(title, now = new Date()) {
34
- const filename = `${formatDraftTimestamp(now)}_${toDraftSlug(title)}.md`;
35
- return normalizeDraftRelativePath(path.join(KEIYAKU_DRAFT_DIR, filename));
36
- }
37
- function buildDraftArtifactMarkdown(content, now) {
38
- return [
39
- "---",
40
- "generated_by: keiyaku",
41
- "type: draft",
42
- `time: '${now.toISOString()}'`,
43
- `notice: '${DRAFT_ARTIFACT_NOTICE}'`,
44
- "---",
45
- "",
46
- content,
47
- ].join("\n");
48
- }
49
- export async function writeDraftArtifact(input) {
50
- const now = input.now ?? new Date();
51
- const relativePath = buildDraftArtifactRelativePath(input.title, now);
52
- const absolutePath = path.join(input.cwd, relativePath);
53
- await fs.mkdir(path.dirname(absolutePath), { recursive: true });
54
- await fs.writeFile(absolutePath, buildDraftArtifactMarkdown(input.content, now), "utf-8");
55
- return relativePath;
56
- }
57
- export async function listDraftArtifacts(cwd) {
58
- const draftDirPath = path.join(cwd, KEIYAKU_DRAFT_DIR);
59
- try {
60
- const entries = await fs.readdir(draftDirPath, { withFileTypes: true });
61
- return entries
62
- .filter((entry) => entry.isFile())
63
- .map((entry) => normalizeDraftRelativePath(path.join(KEIYAKU_DRAFT_DIR, entry.name)))
64
- .sort((a, b) => b.localeCompare(a));
65
- }
66
- catch (error) {
67
- if (isErrnoException(error) && error.code === "ENOENT") {
68
- return [];
69
- }
70
- throw error;
71
- }
72
- }
73
- export async function readLatestDraftArtifact(cwd) {
74
- const [latestPath] = await listDraftArtifacts(cwd);
75
- if (!latestPath)
76
- return null;
77
- const content = await fs.readFile(path.join(cwd, latestPath), "utf-8");
78
- return { path: latestPath, content };
79
- }
80
- export async function readDraftSnapshot(cwd) {
81
- const draftPaths = await listDraftArtifacts(cwd);
82
- if (draftPaths.length === 0)
83
- return null;
84
- const files = await Promise.all(draftPaths.map(async (draftPath) => ({
85
- path: draftPath,
86
- content: await fs.readFile(path.join(cwd, draftPath)),
87
- })));
88
- return {
89
- path: draftPaths[0],
90
- files,
91
- };
92
- }
93
- export async function restoreDraftSnapshot(cwd, snapshot) {
94
- for (const file of snapshot.files) {
95
- const absolutePath = path.join(cwd, file.path);
96
- await fs.mkdir(path.dirname(absolutePath), { recursive: true });
97
- await fs.writeFile(absolutePath, file.content);
98
- }
99
- }
@@ -1,24 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
3
- import { KEIYAKU_FILE } from "../keiyaku.js";
4
- import { isErrnoException } from "../errno.js";
5
- import { FlowError } from "../flow-error.js";
6
- async function isMissingFile(filePath) {
7
- try {
8
- await fs.access(filePath);
9
- return false;
10
- }
11
- catch (error) {
12
- if (isErrnoException(error) && error.code === "ENOENT") {
13
- return true;
14
- }
15
- throw error;
16
- }
17
- }
18
- export async function assertKeiyakuProtocolFiles(cwd) {
19
- const keiyakuPath = path.join(cwd, KEIYAKU_FILE);
20
- const hasKeiyaku = !(await isMissingFile(keiyakuPath));
21
- if (!hasKeiyaku) {
22
- throw new FlowError("MISSING_PROTOCOL_FILES", `missing protocol file for current keiyaku (${KEIYAKU_FILE})`);
23
- }
24
- }
@@ -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
- }