@arcfoundry/core 0.2.0 → 0.2.1
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.
- package/dist/agency-execution/final-gates/availability.d.ts +2 -0
- package/dist/agency-execution/final-gates/availability.js +4 -0
- package/dist/agency-execution/final-gates/package-manager.d.ts +2 -0
- package/dist/agency-execution/final-gates/package-manager.js +38 -0
- package/dist/agency-execution/final-gates/project-command-metadata.d.ts +6 -0
- package/dist/agency-execution/final-gates/project-command-metadata.js +24 -0
- package/dist/agency-execution/final-gates/script-gates.d.ts +2 -0
- package/dist/agency-execution/final-gates/script-gates.js +20 -0
- package/dist/agency-execution/final-gates/steps.d.ts +9 -0
- package/dist/agency-execution/final-gates/steps.js +24 -0
- package/dist/agency-execution/final-gates.d.ts +7 -2
- package/dist/agency-execution/final-gates.js +24 -17
- package/dist/commands/availability.d.ts +1 -0
- package/dist/commands/availability.js +27 -0
- package/dist/commands/evidence-runner.js +17 -0
- package/dist/opencode-runtime/sdk-stage/output-retry.d.ts +12 -0
- package/dist/opencode-runtime/sdk-stage/output-retry.js +20 -0
- package/dist/opencode-runtime/sdk-stage/session-flow.js +12 -1
- package/dist/opencode-runtime/stage-output-retry.d.ts +4 -0
- package/dist/opencode-runtime/stage-output-retry.js +15 -0
- package/package.json +2 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const LOCKFILE_PACKAGE_MANAGERS = [
|
|
4
|
+
["package-lock.json", "npm"],
|
|
5
|
+
["npm-shrinkwrap.json", "npm"],
|
|
6
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
7
|
+
["yarn.lock", "yarn"],
|
|
8
|
+
["bun.lock", "bun"],
|
|
9
|
+
["bun.lockb", "bun"],
|
|
10
|
+
];
|
|
11
|
+
export async function detectPackageManager(repoRoot, packageManagerSpec) {
|
|
12
|
+
const declared = parsePackageManager(packageManagerSpec);
|
|
13
|
+
if (declared)
|
|
14
|
+
return declared;
|
|
15
|
+
for (const [lockfile, packageManager] of LOCKFILE_PACKAGE_MANAGERS) {
|
|
16
|
+
if (await fileExists(path.join(repoRoot, lockfile)))
|
|
17
|
+
return packageManager;
|
|
18
|
+
}
|
|
19
|
+
return "npm";
|
|
20
|
+
}
|
|
21
|
+
function parsePackageManager(packageManagerSpec) {
|
|
22
|
+
const packageManager = packageManagerSpec?.split("@")[0];
|
|
23
|
+
return packageManager === "npm" ||
|
|
24
|
+
packageManager === "pnpm" ||
|
|
25
|
+
packageManager === "yarn" ||
|
|
26
|
+
packageManager === "bun"
|
|
27
|
+
? packageManager
|
|
28
|
+
: null;
|
|
29
|
+
}
|
|
30
|
+
async function fileExists(filePath) {
|
|
31
|
+
try {
|
|
32
|
+
await access(filePath);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type PackageManager } from "./package-manager.js";
|
|
2
|
+
export type ProjectCommandMetadata = {
|
|
3
|
+
readonly packageManager: PackageManager;
|
|
4
|
+
readonly scripts: ReadonlySet<string>;
|
|
5
|
+
};
|
|
6
|
+
export declare function projectCommandMetadata(repoRoot: string): Promise<ProjectCommandMetadata>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { detectPackageManager } from "./package-manager.js";
|
|
4
|
+
export async function projectCommandMetadata(repoRoot) {
|
|
5
|
+
const packageJson = await readPackageJson(repoRoot);
|
|
6
|
+
return {
|
|
7
|
+
packageManager: await detectPackageManager(repoRoot, packageJson.packageManager),
|
|
8
|
+
scripts: new Set(Object.keys(packageJson.scripts ?? {})),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
async function readPackageJson(repoRoot) {
|
|
12
|
+
try {
|
|
13
|
+
const raw = await readFile(path.join(repoRoot, "package.json"), "utf8");
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (isMissingFileError(error))
|
|
18
|
+
return {};
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isMissingFileError(error) {
|
|
23
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const FALLBACK_TEST_GATES = [
|
|
2
|
+
["test", "test:unit"],
|
|
3
|
+
["test", "test:integration"],
|
|
4
|
+
["test", "test:e2e"],
|
|
5
|
+
["browser-validation", "test:browser"],
|
|
6
|
+
];
|
|
7
|
+
const QUALITY_GATES = [
|
|
8
|
+
["lint", "lint"],
|
|
9
|
+
["format", "format"],
|
|
10
|
+
["format", "format:check"],
|
|
11
|
+
];
|
|
12
|
+
export function scriptGates(scripts) {
|
|
13
|
+
return [
|
|
14
|
+
...(scripts.has("build") ? [["build", "build"]] : []),
|
|
15
|
+
...(scripts.has("test")
|
|
16
|
+
? [["test", "test"]]
|
|
17
|
+
: FALLBACK_TEST_GATES.filter(([, script]) => scripts.has(script))),
|
|
18
|
+
...QUALITY_GATES.filter(([, script]) => scripts.has(script)),
|
|
19
|
+
];
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type CommandStep } from "../../commands/command-sequence.js";
|
|
2
|
+
import { type ProjectCommandMetadata } from "./project-command-metadata.js";
|
|
3
|
+
type FinalVerificationStepsInput = {
|
|
4
|
+
readonly evidenceDir: string;
|
|
5
|
+
readonly metadata: ProjectCommandMetadata;
|
|
6
|
+
readonly repoRoot: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function finalVerificationSteps(input: FinalVerificationStepsInput): CommandStep[];
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { scriptGates } from "./script-gates.js";
|
|
2
|
+
export function finalVerificationSteps(input) {
|
|
3
|
+
return [
|
|
4
|
+
step(input, "install", installArgs(input.metadata.packageManager)),
|
|
5
|
+
...scriptGates(input.metadata.scripts).map(([purpose, script]) => step(input, purpose, runScriptArgs(script))),
|
|
6
|
+
];
|
|
7
|
+
}
|
|
8
|
+
function step(input, purpose, args) {
|
|
9
|
+
return {
|
|
10
|
+
purpose,
|
|
11
|
+
command: input.metadata.packageManager,
|
|
12
|
+
args: [...args],
|
|
13
|
+
cwd: input.repoRoot,
|
|
14
|
+
evidenceDir: input.evidenceDir,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function installArgs(packageManager) {
|
|
18
|
+
if (packageManager === "pnpm")
|
|
19
|
+
return ["install", "--config.confirm-modules-purge=false"];
|
|
20
|
+
return ["install"];
|
|
21
|
+
}
|
|
22
|
+
function runScriptArgs(script) {
|
|
23
|
+
return ["run", script];
|
|
24
|
+
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { type CommandEvidence } from "@arcfoundry/schemas";
|
|
2
2
|
import { type CommandStepRunner } from "../commands/command-sequence.js";
|
|
3
|
+
import { type CommandAvailabilityChecker } from "./final-gates/availability.js";
|
|
4
|
+
export { type CommandAvailabilityChecker } from "./final-gates/availability.js";
|
|
5
|
+
export type FinalVerificationGatePlanOptions = {
|
|
6
|
+
readonly commandAvailable?: CommandAvailabilityChecker;
|
|
7
|
+
};
|
|
3
8
|
export declare class FinalVerificationGatePlan {
|
|
4
9
|
private readonly repoRoot;
|
|
5
10
|
private readonly evidenceDir;
|
|
6
|
-
|
|
11
|
+
private readonly options;
|
|
12
|
+
constructor(repoRoot: string, evidenceDir: string, options?: FinalVerificationGatePlanOptions);
|
|
7
13
|
run(runner?: CommandStepRunner): Promise<CommandEvidence[]>;
|
|
8
|
-
private steps;
|
|
9
14
|
}
|
|
10
15
|
export declare function runFinalVerificationGates(repoRoot: string, evidenceDir: string): Promise<CommandEvidence[]>;
|
|
11
16
|
export declare function commandFailureMessages(commands: CommandEvidence[]): string[];
|
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
import { CommandSequence
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
["lint", "pnpm", ["run", "lint"]],
|
|
6
|
-
["format", "pnpm", ["run", "format"]],
|
|
7
|
-
["format", "pnpm", ["run", "format:check"]],
|
|
8
|
-
];
|
|
1
|
+
import { CommandSequence } from "../commands/command-sequence.js";
|
|
2
|
+
import { isCommandAvailable } from "./final-gates/availability.js";
|
|
3
|
+
import { finalVerificationSteps } from "./final-gates/steps.js";
|
|
4
|
+
import { projectCommandMetadata } from "./final-gates/project-command-metadata.js";
|
|
9
5
|
export class FinalVerificationGatePlan {
|
|
10
6
|
repoRoot;
|
|
11
7
|
evidenceDir;
|
|
12
|
-
|
|
8
|
+
options;
|
|
9
|
+
constructor(repoRoot, evidenceDir, options = {}) {
|
|
13
10
|
this.repoRoot = repoRoot;
|
|
14
11
|
this.evidenceDir = evidenceDir;
|
|
12
|
+
this.options = options;
|
|
15
13
|
}
|
|
16
14
|
async run(runner) {
|
|
17
15
|
const sink = { commands: [] };
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
const metadata = await projectCommandMetadata(this.repoRoot);
|
|
17
|
+
const commandAvailable = this.options.commandAvailable ?? isCommandAvailable;
|
|
18
|
+
if (!(await commandAvailable(metadata.packageManager))) {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
purpose: "install",
|
|
22
|
+
command: `${metadata.packageManager} (unavailable)`,
|
|
23
|
+
exitCode: 127,
|
|
24
|
+
durationMs: 0,
|
|
25
|
+
stdoutRef: null,
|
|
26
|
+
stderrRef: null,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
return new CommandSequence(sink, runner).run(finalVerificationSteps({
|
|
26
31
|
evidenceDir: this.evidenceDir,
|
|
32
|
+
metadata,
|
|
33
|
+
repoRoot: this.repoRoot,
|
|
27
34
|
}));
|
|
28
35
|
}
|
|
29
36
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isCommandExecutableAvailable(command: string, cwd: string, pathValue?: string): Promise<boolean>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export async function isCommandExecutableAvailable(command, cwd, pathValue = process.env.PATH ?? "") {
|
|
5
|
+
if (isPathCommand(command))
|
|
6
|
+
return canExecute(path.resolve(cwd, command));
|
|
7
|
+
return searchPath(command, pathValue);
|
|
8
|
+
}
|
|
9
|
+
function isPathCommand(command) {
|
|
10
|
+
return path.isAbsolute(command) || command.includes(path.sep);
|
|
11
|
+
}
|
|
12
|
+
async function searchPath(command, pathValue) {
|
|
13
|
+
for (const directory of pathValue.split(path.delimiter)) {
|
|
14
|
+
if (directory && (await canExecute(path.join(directory, command))))
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
async function canExecute(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
await access(filePath, constants.X_OK);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
+
import { isCommandExecutableAvailable } from "./availability.js";
|
|
1
2
|
import { writeCommandEvidenceFiles } from "./evidence-files.js";
|
|
2
3
|
import { runCommandProcess } from "./process-runner.js";
|
|
3
4
|
export async function runCommandEvidence(command, args, cwd, evidenceDir, extraEnv = {}, timeoutMs = 180000) {
|
|
4
5
|
const started = Date.now();
|
|
6
|
+
if (!(await isCommandExecutableAvailable(command, cwd, extraEnv.PATH))) {
|
|
7
|
+
const result = {
|
|
8
|
+
code: 127,
|
|
9
|
+
stdout: "",
|
|
10
|
+
stderr: `Command unavailable before execution: ${command}\n`,
|
|
11
|
+
};
|
|
12
|
+
const files = await writeCommandEvidenceFiles(evidenceDir, command, result);
|
|
13
|
+
return {
|
|
14
|
+
purpose: "other",
|
|
15
|
+
command: [command, ...args].join(" "),
|
|
16
|
+
exitCode: result.code,
|
|
17
|
+
durationMs: Date.now() - started,
|
|
18
|
+
stdoutRef: files.stdoutRef,
|
|
19
|
+
stderrRef: files.stderrRef,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
5
22
|
const result = await runCommandProcess({
|
|
6
23
|
command,
|
|
7
24
|
args,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type PromptedOpenCodeSession } from "../session-prompt.js";
|
|
2
|
+
import { type OpenCodeSdkClient } from "../sdk-stage-runtime.js";
|
|
3
|
+
import { type OpenCodeModelSelection } from "../types.js";
|
|
4
|
+
import { type OpenCodeSdkStageOptions } from "./options.js";
|
|
5
|
+
export declare function retryMissingStageOutput(options: {
|
|
6
|
+
client: OpenCodeSdkClient;
|
|
7
|
+
prompted: PromptedOpenCodeSession;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
stageOptions: OpenCodeSdkStageOptions;
|
|
10
|
+
model: OpenCodeModelSelection | null;
|
|
11
|
+
signal: AbortSignal;
|
|
12
|
+
}): Promise<PromptedOpenCodeSession>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { promptOpenCodeSession } from "../session-prompt.js";
|
|
2
|
+
import { parseStageOutput } from "../stage-output.js";
|
|
3
|
+
import { stageOutputRetryPrompt } from "../stage-output-retry.js";
|
|
4
|
+
export async function retryMissingStageOutput(options) {
|
|
5
|
+
const parsed = parseStageOutput(options.prompted.assistantText);
|
|
6
|
+
if (parsed.ok)
|
|
7
|
+
return options.prompted;
|
|
8
|
+
return promptOpenCodeSession({
|
|
9
|
+
client: options.client,
|
|
10
|
+
sessionId: options.sessionId,
|
|
11
|
+
repoRoot: options.stageOptions.repoRoot,
|
|
12
|
+
agent: options.stageOptions.opencodeAgent,
|
|
13
|
+
model: options.model,
|
|
14
|
+
prompt: stageOutputRetryPrompt({
|
|
15
|
+
stage: options.stageOptions.stage,
|
|
16
|
+
parsingError: parsed.error,
|
|
17
|
+
}),
|
|
18
|
+
signal: options.signal,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createOpenCodeSdkSession } from "../sdk-stage-session.js";
|
|
2
2
|
import { promptOpenCodeSession } from "../session-prompt.js";
|
|
3
|
+
import { retryMissingStageOutput } from "./output-retry.js";
|
|
3
4
|
export async function promptOpenCodeSdkStageSession(options) {
|
|
4
5
|
const { session, model } = await createOpenCodeSdkSession({
|
|
5
6
|
client: options.client,
|
|
@@ -21,5 +22,15 @@ export async function promptOpenCodeSdkStageSession(options) {
|
|
|
21
22
|
prompt: options.stageOptions.prompt,
|
|
22
23
|
signal: options.signal,
|
|
23
24
|
});
|
|
24
|
-
return {
|
|
25
|
+
return {
|
|
26
|
+
sessionId: session.id,
|
|
27
|
+
prompted: await retryMissingStageOutput({
|
|
28
|
+
client: options.client,
|
|
29
|
+
prompted,
|
|
30
|
+
sessionId: session.id,
|
|
31
|
+
stageOptions: options.stageOptions,
|
|
32
|
+
model,
|
|
33
|
+
signal: options.signal,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
25
36
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function stageOutputRetryPrompt(input) {
|
|
2
|
+
return [
|
|
3
|
+
"Your previous response completed without a valid Arc Foundry stage output.",
|
|
4
|
+
"",
|
|
5
|
+
`Stage: ${input.stage}`,
|
|
6
|
+
`Parsing error: ${input.parsingError}`,
|
|
7
|
+
"",
|
|
8
|
+
"Finish this stage now.",
|
|
9
|
+
"If the stage work is already complete, do not repeat it. If more work is required, complete only the missing work.",
|
|
10
|
+
"End with exactly one strict JSON object between these markers and no Markdown fences:",
|
|
11
|
+
"AGENCY_STAGE_OUTPUT_JSON_START",
|
|
12
|
+
'{"status":"passed","summary":"Concise stage result.","outputs":{},"blockingIssues":[]}',
|
|
13
|
+
"AGENCY_STAGE_OUTPUT_JSON_END",
|
|
14
|
+
].join("\n");
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcfoundry/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Core workflow, governance, artifact, and runtime primitives for Arc Foundry.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"files": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@opencode-ai/sdk": "^1.15.11",
|
|
22
|
-
"@arcfoundry/schemas": "0.2.
|
|
22
|
+
"@arcfoundry/schemas": "0.2.1"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": "^25.9.0"
|