Skip to content

Commit dc00430

Browse files
authored
refactor: rename adapter workflow helpers (#223)
Refs #209
1 parent 92f5ff8 commit dc00430

7 files changed

Lines changed: 39 additions & 28 deletions

File tree

docs/product-name-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ git grep -n -i -E 'kapi|ilchul' -- ':!package-lock.json' ':!node_modules'
3838
- Store abstractions now use semantic `WorkflowStore` / `FileWorkflowStore` names across application ports, adapters, and runtime probe scripts; compatibility imports have been migrated and the temporary store alias has been removed.
3939
- Service source surfaces now use semantic `WorkflowService` in the implementation, local factory, presentation layer, runtime probe scripts, and active tests; the temporary `KapiService` export has been removed after migrating the large remaining test clusters.
4040
- Presentation registration/UI action helpers now use semantic workflow names (`registerWorkflowExtension`, `registerWorkflowCommandsAndTools`, `registerWorkflowTools`, `WorkflowUiActionContext`, `installWorkflowAutocomplete`, `toggleWorkflowWidgetExpanded`) while leaving public `kapi_*` tool names and `/kapi-*` contracts intact.
41+
- Adapter/domain private helper names now use semantic state/workflow naming for symlink guards, managed autoresearch symlinks, worker branch validation, and prompt rule rendering while preserving user-facing Kapi contract text.
4142

4243
## Residual scan after service filename rename
4344

src/adapters/autoresearch-bridge.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,16 +145,16 @@ export class FileAutoresearchBridgePreparer {
145145
const activeFiles = new Set(bridgeFiles.map((file) => file.rootName));
146146
for (const file of AUTORESEARCH_ARTIFACT_NAMES) {
147147
if (activeFiles.has(file)) continue;
148-
await this.removeKapiOwnedSymlink(workspace, file);
148+
await this.removeManagedSymlink(workspace, file);
149149
}
150150
}
151151

152-
private async removeKapiOwnedSymlink(workspace: string, file: AutoresearchArtifactName): Promise<void> {
152+
private async removeManagedSymlink(workspace: string, file: AutoresearchArtifactName): Promise<void> {
153153
const linkPath = path.join(workspace, file);
154154
const stat = await this.lstatIfExists(linkPath);
155155
if (!stat?.isSymbolicLink()) return;
156156
const currentTarget = await fs.readlink(linkPath);
157-
if (this.isKapiAutoresearchTarget(workspace, path.resolve(workspace, currentTarget))) await fs.unlink(linkPath);
157+
if (this.isManagedAutoresearchTarget(workspace, path.resolve(workspace, currentTarget))) await fs.unlink(linkPath);
158158
}
159159

160160
private async ensureSymlink(input: { workspace: string; artifactRoot: string; file: BridgeArtifact }): Promise<void> {
@@ -176,15 +176,15 @@ export class FileAutoresearchBridgePreparer {
176176
const resolvedCurrent = path.resolve(input.workspace, currentTarget);
177177
const resolvedTarget = path.resolve(targetPath);
178178
if (resolvedCurrent === resolvedTarget) return;
179-
if (this.isKapiAutoresearchTarget(input.workspace, resolvedCurrent)) {
179+
if (this.isManagedAutoresearchTarget(input.workspace, resolvedCurrent)) {
180180
await fs.unlink(linkPath);
181181
await fs.symlink(relativeTarget, linkPath);
182182
return;
183183
}
184184
throw new Error(`Cannot prepare Kapi autoresearch bridge: ${input.file.rootName} points to ${currentTarget}, not ${relativeTarget}.`);
185185
}
186186

187-
private isKapiAutoresearchTarget(workspace: string, targetPath: string): boolean {
187+
private isManagedAutoresearchTarget(workspace: string, targetPath: string): boolean {
188188
const relative = path.relative(workspace, targetPath).replace(/\\/g, "/");
189189
return relative.startsWith(".ilchul/workflows/autoresearch/");
190190
}

src/adapters/file-store.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ interface ActiveIndex {
1313
export class FileWorkflowStore implements WorkflowStore {
1414
async saveWorkflow(state: WorkflowState): Promise<void> {
1515
const file = path.join(state.artifactRoot, "state.json");
16-
await this.prepareWritableKapiFile(state.workspace, file);
16+
await this.prepareWritableStateFile(state.workspace, file);
1717
await fs.writeFile(file, `${JSON.stringify(state, null, 2)}\n`, "utf8");
1818
}
1919

2020
async loadWorkflow(workspace: string, workflowId: WorkflowState["workflowId"], slug: string): Promise<WorkflowState | undefined> {
2121
const file = this.statePath(workspace, workflowId, slug);
22-
await this.assertKapiAncestorsAreNotSymlinks(workspace, file);
22+
await this.assertStateAncestorsAreNotSymlinks(workspace, file);
2323
return this.readJson<WorkflowState>(file);
2424
}
2525

2626
async loadActive(workspace: string): Promise<WorkflowState | undefined> {
27-
await this.assertKapiAncestorsAreNotSymlinks(workspace, this.activePath(workspace));
27+
await this.assertStateAncestorsAreNotSymlinks(workspace, this.activePath(workspace));
2828
const active = await this.readJsonOrClearActivePointer<ActiveIndex>(workspace, this.activePath(workspace));
2929
if (!active) return undefined;
3030
return this.resolveActivePointer(workspace, active);
@@ -38,7 +38,7 @@ export class FileWorkflowStore implements WorkflowStore {
3838
updatedAt: state.updatedAt,
3939
};
4040
const file = this.activePath(state.workspace);
41-
await this.prepareWritableKapiFile(state.workspace, file);
41+
await this.prepareWritableStateFile(state.workspace, file);
4242
await fs.writeFile(file, `${JSON.stringify(active, null, 2)}\n`, "utf8");
4343
}
4444

@@ -51,13 +51,13 @@ export class FileWorkflowStore implements WorkflowStore {
5151
}
5252

5353
async ensureArtifacts(state: WorkflowState): Promise<void> {
54-
await this.assertKapiAncestorsAreNotSymlinks(state.workspace, path.join(state.artifactRoot, "state.json"));
54+
await this.assertStateAncestorsAreNotSymlinks(state.workspace, path.join(state.artifactRoot, "state.json"));
5555
await fs.mkdir(state.artifactRoot, { recursive: true });
5656
await Promise.all(
5757
state.artifacts.map(async (artifact) => {
5858
this.assertArtifactPathInsideRoot(state, artifact);
5959
const file = artifact.path;
60-
await this.assertKapiAncestorsAreNotSymlinks(state.workspace, file);
60+
await this.assertStateAncestorsAreNotSymlinks(state.workspace, file);
6161
if (this.isDirectoryArtifact(state, artifact.name)) {
6262
await this.ensureDirectoryArtifact(state, artifact.name, file);
6363
return;
@@ -79,20 +79,20 @@ export class FileWorkflowStore implements WorkflowStore {
7979

8080
async readArtifact(state: WorkflowState, artifactName: string): Promise<string | undefined> {
8181
const artifact = this.resolveArtifact(state, artifactName);
82-
await this.assertKapiAncestorsAreNotSymlinks(state.workspace, artifact.path);
82+
await this.assertStateAncestorsAreNotSymlinks(state.workspace, artifact.path);
8383
return this.readFileIfExists(artifact.path);
8484
}
8585

8686
async artifactExists(state: WorkflowState, artifactName: string): Promise<boolean> {
8787
const artifact = this.resolveArtifact(state, artifactName);
88-
await this.assertKapiAncestorsAreNotSymlinks(state.workspace, artifact.path);
88+
await this.assertStateAncestorsAreNotSymlinks(state.workspace, artifact.path);
8989
if (this.isDirectoryArtifact(state, artifact.name)) return this.directoryArtifactExists(artifact.name, artifact.path);
9090
return this.regularFileExists(artifact.path);
9191
}
9292

9393
async artifactMetadata(state: WorkflowState, artifactName: string): Promise<{ exists: boolean; isFile: boolean; isSymlink: boolean; executable: boolean }> {
9494
const artifact = this.resolveArtifact(state, artifactName);
95-
await this.assertKapiAncestorsAreNotSymlinks(state.workspace, artifact.path);
95+
await this.assertStateAncestorsAreNotSymlinks(state.workspace, artifact.path);
9696
const stat = await this.lstatIfExists(artifact.path);
9797
if (!stat) return { exists: false, isFile: false, isSymlink: false, executable: false };
9898
const isSymlink = stat.isSymbolicLink();
@@ -113,7 +113,7 @@ export class FileWorkflowStore implements WorkflowStore {
113113
await this.assertArtifactFileIsNotSymlink(artifactPath);
114114
if (artifactName !== "specs") return;
115115
const readmePath = path.join(artifactPath, "README.md");
116-
await this.assertKapiAncestorsAreNotSymlinks(state.workspace, readmePath);
116+
await this.assertStateAncestorsAreNotSymlinks(state.workspace, readmePath);
117117
if (!(await this.fileExists(readmePath))) {
118118
await fs.writeFile(readmePath, this.initialSpecsReadmeContent(state), "utf8");
119119
}
@@ -154,7 +154,7 @@ export class FileWorkflowStore implements WorkflowStore {
154154

155155
private async writableArtifactPath(state: WorkflowState, artifactName: string): Promise<string> {
156156
const artifact = this.resolveArtifact(state, artifactName);
157-
await this.prepareWritableKapiFile(state.workspace, artifact.path);
157+
await this.prepareWritableStateFile(state.workspace, artifact.path);
158158
return artifact.path;
159159
}
160160

@@ -193,7 +193,7 @@ export class FileWorkflowStore implements WorkflowStore {
193193

194194
private async listWorkflowRoot(workspace: string): Promise<WorkflowState[]> {
195195
const root = path.join(workspace, ".ilchul", "workflows");
196-
await this.assertKapiAncestorsAreNotSymlinks(workspace, path.join(root, ".list"));
196+
await this.assertStateAncestorsAreNotSymlinks(workspace, path.join(root, ".list"));
197197
const states: WorkflowState[] = [];
198198
try {
199199
for (const workflowDir of await fs.readdir(root, { withFileTypes: true })) {
@@ -227,7 +227,7 @@ export class FileWorkflowStore implements WorkflowStore {
227227
return this.clearActivePointer(workspace);
228228
}
229229

230-
await this.assertKapiAncestorsAreNotSymlinks(workspace, active.statePath);
230+
await this.assertStateAncestorsAreNotSymlinks(workspace, active.statePath);
231231
const state = await this.readJsonOrClearActivePointer<WorkflowState>(workspace, active.statePath);
232232
if (!state) return this.clearActivePointer(workspace);
233233
if (!this.isStateConsistentWithWorkspace(workspace, state)) return this.clearActivePointer(workspace);
@@ -270,8 +270,8 @@ export class FileWorkflowStore implements WorkflowStore {
270270
return fs.readFile(file, "utf8");
271271
}
272272

273-
private async prepareWritableKapiFile(workspace: string, file: string): Promise<void> {
274-
await this.assertKapiAncestorsAreNotSymlinks(workspace, file);
273+
private async prepareWritableStateFile(workspace: string, file: string): Promise<void> {
274+
await this.assertStateAncestorsAreNotSymlinks(workspace, file);
275275
await fs.mkdir(path.dirname(file), { recursive: true });
276276
await this.assertArtifactFileIsNotSymlink(file);
277277
}
@@ -281,7 +281,7 @@ export class FileWorkflowStore implements WorkflowStore {
281281
if (stat) this.assertNotSymlink(file, stat);
282282
}
283283

284-
private async assertKapiAncestorsAreNotSymlinks(workspace: string, file: string): Promise<void> {
284+
private async assertStateAncestorsAreNotSymlinks(workspace: string, file: string): Promise<void> {
285285
const workspaceRoot = path.resolve(workspace);
286286
const resolvedFile = path.resolve(file);
287287
const relative = path.relative(workspaceRoot, resolvedFile);

src/adapters/worker-substrate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function defaultWorkerBranchName(request: WorkerPrepareInput, id: string): strin
99
return `${type}/${mode}-${request.workflowSlug}-${id.slice(-8)}`;
1010
}
1111

12-
function assertKapiOwnedBranchName(branch: string): void {
12+
function assertWorkflowOwnedBranchName(branch: string): void {
1313
if (/^(feat|fix|perf|refactor|test|docs|chore|build|ci)\/[a-z0-9][a-z0-9-]*$/.test(branch)) return;
1414
throw new Error(`Kapi worktree branch must use a Conventional Commit prefix and kebab-case slug: ${branch}`);
1515
}
@@ -135,7 +135,7 @@ export async function prepareWorkerWorkspace(
135135
workerWorkspace = request.worktreePath ?? path.join(worktreeRoot, id);
136136
await assertWorktreePathInsideBoundary(worktreeRoot, workerWorkspace);
137137
const branch = request.branchName ?? defaultWorkerBranchName(request, id);
138-
assertKapiOwnedBranchName(branch);
138+
assertWorkflowOwnedBranchName(branch);
139139
assertBranchInsideBoundary(branch, request.branchPrefix);
140140
await assertCleanBaseCheckout(runner, gitRoot.output);
141141
await assertTargetBranchAvailable(runner, gitRoot.output, request.targetBranch);

src/domain/run-contract-prompt-renderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function renderRunContractPrompt(options: RenderRunContractPromptOptions)
5050
if (layout === "workflow" && options.workerCapabilitySnapshot !== undefined) sections.push(`Worker capability snapshot:\n${options.workerCapabilitySnapshot}`);
5151

5252
const rules = layout === "runtime" ? runtimeRuleLines(options.additionalRuleLines) : workflowRuleLines(options.additionalRuleLines);
53-
sections.push(formatKapiRules(rules, { artifactGuidance: layout === "runtime" ? artifactGuidance : [], resumeNotes: options.resumeNotes }));
53+
sections.push(formatWorkflowRules(rules, { artifactGuidance: layout === "runtime" ? artifactGuidance : [], resumeNotes: options.resumeNotes }));
5454

5555
if (options.summaryState) sections.push(summarizeWorkflow(options.summaryState));
5656
return sections.join("\n\n");
@@ -88,7 +88,7 @@ function formatSharedLifecycle(options: RenderRunContractPromptOptions, layout:
8888
].join("\n");
8989
}
9090

91-
function formatKapiRules(options: readonly string[], extra: { artifactGuidance: readonly string[]; resumeNotes?: readonly string[] }): string {
91+
function formatWorkflowRules(options: readonly string[], extra: { artifactGuidance: readonly string[]; resumeNotes?: readonly string[] }): string {
9292
const lines = ["Kapi rules:", ...options.map((rule) => `- ${rule}`)];
9393
if (extra.artifactGuidance.length) lines.push("Artifact guidance:", ...extra.artifactGuidance.map((item) => `- ${item}`));
9494
if (extra.resumeNotes?.length) lines.push("", "Resume notes:", ...extra.resumeNotes.map((note) => `- ${note}`));

src/domain/workflow-validation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function isWorktreePathInsideBoundary(root: string, value: string): boolean {
5757
return Boolean(boundary) && candidate.startsWith(`${boundary}/`);
5858
}
5959

60-
function hasValidKapiBranchPrefix(value: string): boolean {
60+
function hasValidWorkflowBranchPrefix(value: string): boolean {
6161
return /^(feat|fix|perf|refactor|test|docs|chore|build|ci)\/[a-z0-9][a-z0-9-]*$/.test(value);
6262
}
6363

@@ -336,7 +336,7 @@ export function validateWorkflowState(state: WorkflowState): WorkflowValidationR
336336
if (!collapsePathSegments(state.worktreeBoundary.root)) {
337337
issues.push({ severity: "error", code: "invalid_worktree_boundary_root", message: `Kapi worktree boundary root must be non-empty, got ${state.worktreeBoundary.root}.` });
338338
}
339-
if (!hasValidKapiBranchPrefix(state.worktreeBoundary.branchPrefix)) {
339+
if (!hasValidWorkflowBranchPrefix(state.worktreeBoundary.branchPrefix)) {
340340
issues.push({ severity: "error", code: "invalid_worktree_branch_prefix", message: `Kapi worktree branchPrefix must use a conventional prefix and kebab-case slug: ${state.worktreeBoundary.branchPrefix}.` });
341341
}
342342
}
@@ -390,7 +390,7 @@ export function validateWorkflowState(state: WorkflowState): WorkflowValidationR
390390
code: "worker_isolated_workspace_missing_branch",
391391
message: `Kapi isolated workspace worker ${worker.id} must record the created branchName handle.`,
392392
});
393-
} else if (!hasValidKapiBranchPrefix(worker.handles.branchName)) {
393+
} else if (!hasValidWorkflowBranchPrefix(worker.handles.branchName)) {
394394
issues.push({
395395
severity: "error",
396396
code: "worker_isolated_workspace_invalid_branch",

test/architecture.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ test("presentation helper exports avoid product-prefixed generic names", async (
6161
assert.match(presentation, /formatWorkflowError/);
6262
});
6363

64+
test("internal adapter and domain helpers avoid product-prefixed generic names", async () => {
65+
const source = [await readTsFiles(path.join(process.cwd(), "src", "adapters")), await readTsFiles(path.join(process.cwd(), "src", "domain"))].join("\n");
66+
assert.doesNotMatch(source, /prepareWritableKapiFile|assertKapiAncestorsAreNotSymlinks|removeKapiOwnedSymlink|isKapiAutoresearchTarget|assertKapiOwnedBranchName|hasValidKapiBranchPrefix|formatKapiRules/);
67+
assert.match(source, /prepareWritableStateFile/);
68+
assert.match(source, /assertStateAncestorsAreNotSymlinks/);
69+
assert.match(source, /assertWorkflowOwnedBranchName/);
70+
assert.match(source, /hasValidWorkflowBranchPrefix/);
71+
assert.match(source, /formatWorkflowRules/);
72+
});
73+
6474
test("domain layer stays independent from Pi, filesystem, tmux, git commands, and process adapters", async () => {
6575
const domain = await readTsFiles(path.join(process.cwd(), "src", "domain"));
6676
assert.doesNotMatch(domain, /@mariozechner/);

0 commit comments

Comments
 (0)