@awak-app/simy-cli 0.3.3 → 0.4.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.
- package/README.md +55 -0
- package/package.json +4 -2
- package/src/agent.js +18 -1
- package/src/cli-contract.js +1 -1
- package/src/execution-guardrail.js +2 -2
- package/src/index.js +14 -0
- package/src/local-task.js +51 -8
- package/src/orchestrator/audit.js +42 -1
- package/src/orchestrator/loop.js +29 -3
- package/src/runner.js +44 -0
- package/src/sqm/bundle-store.js +249 -0
- package/src/sqm/canonical.js +45 -0
- package/src/sqm/checkers.js +299 -0
- package/src/sqm/command.js +149 -0
- package/src/sqm/evidence-client.js +12 -0
- package/src/sqm/index.js +141 -0
- package/src/sqm/proof.js +154 -0
- package/src/sqm/repository.js +163 -0
- package/src/sqm/session.js +58 -0
- package/src/sqm/validation.js +305 -0
package/README.md
CHANGED
|
@@ -203,6 +203,61 @@ limit. SIMY Web can edit that suggestion and call
|
|
|
203
203
|
`provider_token_budget`. The CLI persists the higher limit and resumes the same
|
|
204
204
|
run. SIMY token consumption remains a separate platform billing record.
|
|
205
205
|
|
|
206
|
+
## SQM local checks
|
|
207
|
+
|
|
208
|
+
SQM deliberately has two identity-bound stages:
|
|
209
|
+
|
|
210
|
+
1. `simy sqm min-check` quickly inspects committed changes, the working tree,
|
|
211
|
+
untracked files, and deletions. It runs only deterministic low-cost rules,
|
|
212
|
+
reports preliminary findings, and writes the applicable state, transition,
|
|
213
|
+
stress, and scenario plan. A passing min-check is not a complete validation.
|
|
214
|
+
2. `simy sqm check` verifies that repository, base, HEAD, working-tree digest,
|
|
215
|
+
and signed bundle still match the min-check. It then evaluates applicable
|
|
216
|
+
state transitions, invariants, stress/strength expectations, and scenarios,
|
|
217
|
+
writes `proof.json`, and signs that proof with the local device identity.
|
|
218
|
+
|
|
219
|
+
Normal runs do not require a separate sync command: the CLI
|
|
220
|
+
uses a verified local cache and revalidates it with TTL and ETag semantics.
|
|
221
|
+
The Agentic Loop runs min-check after each code modification and the full check
|
|
222
|
+
before PR readiness. Findings and the redacted signed-proof summary are recorded
|
|
223
|
+
in its audit; source text is never uploaded.
|
|
224
|
+
|
|
225
|
+
Configure the bundle service with `SIMY_SQM_BUNDLE_URL`. Authentication uses
|
|
226
|
+
the current SIMY session in the coding loop, or `SIMY_SQM_TOKEN`/
|
|
227
|
+
`SIMY_ACCESS_TOKEN` for the diagnostic command. On the first authenticated
|
|
228
|
+
HTTPS request, the CLI receives the bundle verification key from the SIMY
|
|
229
|
+
gateway, verifies the signed bundle, and pins that key to the endpoint,
|
|
230
|
+
account, organization, and repository cache identity for offline use. Explicit
|
|
231
|
+
trust anchors can instead be configured as a JSON key-ID map in
|
|
232
|
+
`SIMY_SQM_PUBLIC_KEYS_JSON`, or as one `SIMY_SQM_KEY_ID` plus
|
|
233
|
+
`SIMY_SQM_PUBLIC_KEY`. Public keys may be PEM, base64 SPKI, or a base64 raw
|
|
234
|
+
32-byte Ed25519 key. Optional settings are:
|
|
235
|
+
|
|
236
|
+
- `SIMY_SQM_CACHE_DIR`: verified bundle cache location.
|
|
237
|
+
- `SIMY_SQM_CACHE_TTL_MS`: revalidation TTL, defaulting to five minutes.
|
|
238
|
+
- `SIMY_SQM_ACCOUNT_ID` and `SIMY_SQM_ORGANIZATION_ID`: tenant identity for
|
|
239
|
+
diagnostic checks. Coding-loop checks receive tenant identity from the
|
|
240
|
+
authenticated session. Cache entries are isolated by endpoint, account,
|
|
241
|
+
organization, and repository, and the organization is verified against the
|
|
242
|
+
signed bundle. `SIMY_SQM_ORGANIZATION_ID` is required for standalone
|
|
243
|
+
diagnostic checks; without a tenant identity SQM fails closed in shadow mode.
|
|
244
|
+
|
|
245
|
+
Use either phase with `--offline` to prohibit network access and `--refresh` to force
|
|
246
|
+
ETag revalidation, `--base <branch>` to select a base branch, and `--json` for
|
|
247
|
+
machine-readable results. Full check additionally accepts `--min-result`,
|
|
248
|
+
`--proof`, `--signed-evidence`, and `--no-upload`. The min result and full proof
|
|
249
|
+
share one pinned signed bundle and one exact repository identity, so a code
|
|
250
|
+
change cannot silently reuse an old preflight. If Cloud is unavailable, the CLI clearly warns and
|
|
251
|
+
uses the last unexpired signature-verified bundle only after a network failure
|
|
252
|
+
or Cloud 5xx response. Authentication, authorization, compatibility,
|
|
253
|
+
signature, schema, tenant, and expiry failures never execute stale knowledge.
|
|
254
|
+
With no verified cache it skips SQM
|
|
255
|
+
in shadow mode. Bundles contain only the fixed declarative checker vocabulary;
|
|
256
|
+
the CLI never executes shell, JavaScript, Python, WASM, or another remotely
|
|
257
|
+
supplied implementation. A module's matcher array has explicit **AND semantics**:
|
|
258
|
+
every matcher must match before any check in that module runs; it is not an OR
|
|
259
|
+
list.
|
|
260
|
+
|
|
206
261
|
## Interactive console
|
|
207
262
|
|
|
208
263
|
Running `simy` in a terminal opens the Agentic Loop chat. It discovers the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awak-app/simy-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"start": "node ./src/index.js",
|
|
15
15
|
"test": "node --test",
|
|
16
16
|
"test:e2e:console": "node ./scripts/console-e2e.js",
|
|
17
|
+
"test:e2e:sqm-complete-loop": "node ./scripts/sqm-complete-loop-e2e.js",
|
|
17
18
|
"test:e2e:console:narrow": "CLI_E2E_COLUMNS=78 CLI_E2E_ROWS=40 node ./scripts/console-e2e.js",
|
|
18
19
|
"test:e2e:console:no-color": "CLI_E2E_NO_COLOR=1 node ./scripts/console-e2e.js",
|
|
19
20
|
"test:e2e:console:recovery": "node ./scripts/console-recovery-e2e.js",
|
|
@@ -35,7 +36,8 @@
|
|
|
35
36
|
"test:e2e:console:real-provider": "node ./scripts/real-provider-console-e2e.js",
|
|
36
37
|
"test:e2e:console:fullstack": "node ./scripts/fullstack-cli-smoke.js",
|
|
37
38
|
"check": "npm run check:auto-update && node --check ./src/desktop-executor.js && node --check ./src/local-task.js && node --check ./scripts/desktop-executor-e2e.js && node --check ./scripts/pre-guardrail-e2e.js && node --check ./scripts/fixtures/fake-desktop-provider.js && npm run check:syntax && npm run check:real-provider-syntax && npm test && npm run check:package",
|
|
38
|
-
"check:auto-update": "node --check ./src/auto-update.js && node --check ./scripts/auto-update-e2e.js",
|
|
39
|
+
"check:auto-update": "node --check ./src/auto-update.js && node --check ./scripts/auto-update-e2e.js && npm run check:sqm",
|
|
40
|
+
"check:sqm": "node --check ./src/sqm/canonical.js && node --check ./src/sqm/validation.js && node --check ./src/sqm/repository.js && node --check ./src/sqm/checkers.js && node --check ./src/sqm/bundle-store.js && node --check ./src/sqm/proof.js && node --check ./src/sqm/evidence-client.js && node --check ./src/sqm/session.js && node --check ./src/sqm/index.js && node --check ./src/sqm/command.js && node --check ./scripts/sqm-complete-loop-e2e.js",
|
|
39
41
|
"check:real-provider-syntax": "node --check ./scripts/real-codex-probe.js && node --check ./scripts/real-claude-probe.js && node --check ./scripts/real-provider-console-fixture.js && node --check ./scripts/real-provider-console-e2e.js",
|
|
40
42
|
"check:syntax": "node --check ./src/index.js && node --check ./src/agent.js && node --check ./src/browser.js && node --check ./src/web-api.js && node --check ./src/durable-local-task-worker.js && node --check ./src/durable-local-task-steps-contract.js && node --check ./src/bounded-local-task-subtasks-contract.js && node --check ./src/bounded-local-task-subtask-pool.js && node --check ./src/local-task-artifact-contract.js && node --check ./src/execution-capability-contract.js && node --check ./src/shutdown.js && node --check ./src/run-registry.js && node --check ./src/direct-executor.js && node --check ./src/execution-guardrail.js && node --check ./src/backend-executable.js && node --check ./src/provider-stream.js && node --check ./src/console/index.js && node --check ./src/console/app.js && node --check ./src/console/commands.js && node --check ./src/local-attachments.js && node --check ./src/repository-inventory.js && node --check ./src/session-store.js && node --check ./src/web-origin.js && node --check ./src/workspace-context.js && node --check ./src/orchestrator/index.js && node --check ./src/orchestrator/shared.js && node --check ./src/orchestrator/recovery.js && node --check ./src/orchestrator/risk.js && node --check ./src/orchestrator/contract.js && node --check ./src/orchestrator/completion-contract.js && node --check ./src/orchestrator/instruction.js && node --check ./src/orchestrator/problem-solving.js && node --check ./src/orchestrator/budget.js && node --check ./src/orchestrator/execution-io.js && node --check ./src/orchestrator/presentation.js && node --check ./src/orchestrator/result.js && node --check ./src/orchestrator/retry.js && node --check ./src/orchestrator/evidence.js && node --check ./src/orchestrator/independent-audit.js && node --check ./src/orchestrator/audit.js && node --check ./src/orchestrator/loop.js && node --check ./src/runner.js && node --check ./scripts/fixtures/completion-contract-fixture.js && node --check ./scripts/fixtures/fake-coding-backend.js && node --check ./scripts/e2e-terminal-evidence.js && node --check ./scripts/audit-gate-e2e.js && node --check ./scripts/completion-contract-e2e.js && node --check ./scripts/retry-circuit-e2e.js && node --check ./scripts/follow-up-charter-e2e.js && node --check ./scripts/task-routing-e2e.js && node --check ./scripts/executor-version-preflight-e2e.js && node --check ./scripts/blocked-recovery-e2e.js && node --check ./scripts/process-localization-e2e.js && node --check ./scripts/hypothesis-retries-e2e.js && node --check ./scripts/budget-enforcement-e2e.js && node --check ./scripts/local-task-default-workspace-e2e.js && node --check ./scripts/local-task-idempotency-e2e.js && node --check ./scripts/console-e2e-fixture.js && node --check ./scripts/console-e2e.js && node --check ./scripts/console-recovery-e2e-fixture.js && node --check ./scripts/console-recovery-e2e.js && node --check ./scripts/real-provider-console-fixture.js && node --check ./scripts/real-provider-console-e2e.js && node --check ./scripts/fullstack-cli-smoke.js && node --check ./scripts/check-package-contents.js",
|
|
41
43
|
"check:package": "node ./scripts/check-package-contents.js"
|
package/src/agent.js
CHANGED
|
@@ -557,6 +557,16 @@ export async function startAgent({
|
|
|
557
557
|
device_id: typeof body.device_id === "string" ? body.device_id : null,
|
|
558
558
|
api_origin: apiOrigin,
|
|
559
559
|
api_base_url: apiBaseUrl,
|
|
560
|
+
account_id: typeof body.account_id === "string"
|
|
561
|
+
? body.account_id
|
|
562
|
+
: typeof body.auth_user_id === "string"
|
|
563
|
+
? body.auth_user_id
|
|
564
|
+
: null,
|
|
565
|
+
organization_id: typeof body.organization_id === "string"
|
|
566
|
+
? body.organization_id
|
|
567
|
+
: typeof body.org_id === "string"
|
|
568
|
+
? body.org_id
|
|
569
|
+
: null,
|
|
560
570
|
expires_at: typeof body.expires_at === "string" ? body.expires_at : expiresAtFromNow(),
|
|
561
571
|
};
|
|
562
572
|
await writeSession(apiOrigin, session, sessionRoot);
|
|
@@ -679,6 +689,13 @@ export async function startAgent({
|
|
|
679
689
|
json(res, 400, { error: "instruction is required" });
|
|
680
690
|
return;
|
|
681
691
|
}
|
|
692
|
+
const authorizationInstruction = String(
|
|
693
|
+
body.authorization_instruction || instruction,
|
|
694
|
+
).trim();
|
|
695
|
+
if (!authorizationInstruction) {
|
|
696
|
+
json(res, 400, { error: "authorization_instruction is required" });
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
682
699
|
if (body.backend !== "codex" && body.backend !== "claude") {
|
|
683
700
|
json(res, 400, { error: "backend must be codex or claude" });
|
|
684
701
|
return;
|
|
@@ -686,7 +703,7 @@ export async function startAgent({
|
|
|
686
703
|
const idempotencyKey = String(
|
|
687
704
|
req.headers["idempotency-key"] || body.client_request_id || "",
|
|
688
705
|
).trim();
|
|
689
|
-
const guardrail = classifyExecutionRequirement(
|
|
706
|
+
const guardrail = classifyExecutionRequirement(authorizationInstruction);
|
|
690
707
|
if (
|
|
691
708
|
guardrail.decision !== "direct_executor" ||
|
|
692
709
|
body.guardrail?.policy_version !== EXECUTION_GUARDRAIL_POLICY_VERSION ||
|
package/src/cli-contract.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./durable-local-task-steps-contract.js";
|
|
14
14
|
import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
|
|
15
15
|
|
|
16
|
-
export const CLI_VERSION = "0.
|
|
16
|
+
export const CLI_VERSION = "0.4.0";
|
|
17
17
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
18
18
|
|
|
19
19
|
export function withCliContract(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const EXECUTION_GUARDRAIL_POLICY_VERSION = "2026-07-
|
|
1
|
+
export const EXECUTION_GUARDRAIL_POLICY_VERSION = "2026-07-29.1";
|
|
2
2
|
|
|
3
3
|
const OBSERVE_PATTERN =
|
|
4
4
|
/\b(monitor|watch|observe|track|status|progress|keep an eye on)\b|監視|見守|進捗|状況を?(?:見|確認)|监控|監控|观察|觀察|进度|進度|状态|狀態/i;
|
|
@@ -98,7 +98,7 @@ export function classifyExecutionRequirement(sourceMessage) {
|
|
|
98
98
|
reason_code: "observe_existing_provider_session",
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
-
if (changeRequested && knowledgeWork && !codingTarget) {
|
|
101
|
+
if (changeRequested && (knowledgeWork || hasObject) && !codingTarget) {
|
|
102
102
|
return guardrail("direct_executor", {
|
|
103
103
|
change_requested: true,
|
|
104
104
|
repository_required: false,
|
package/src/index.js
CHANGED
|
@@ -21,11 +21,25 @@ import { DEFAULT_WEB_ORIGIN, resolveWebOrigin } from "./web-origin.js";
|
|
|
21
21
|
const argv = process.argv.slice(2);
|
|
22
22
|
const args = new Set(argv);
|
|
23
23
|
|
|
24
|
+
if (argv[0] === "sqm") {
|
|
25
|
+
const packageInfo = await readCliPackageInfo();
|
|
26
|
+
try {
|
|
27
|
+
const { runSqmCommand } = await import("./sqm/command.js");
|
|
28
|
+
process.exitCode = await runSqmCommand(argv.slice(1), { cliVersion: packageInfo.version });
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
31
|
+
process.exitCode = 2;
|
|
32
|
+
}
|
|
33
|
+
process.exit();
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
if (args.has("--help") || args.has("-h")) {
|
|
25
37
|
console.log(`Usage:
|
|
26
38
|
simy Start the local SIMY agent
|
|
27
39
|
simy --daemon Start it in the background
|
|
28
40
|
simy --no-tui Start the foreground HTTP agent without the interactive console
|
|
41
|
+
simy sqm min-check Run the fast deterministic SQM diff preflight
|
|
42
|
+
simy sqm check Run full scenarios, write proof.json, and sign the run
|
|
29
43
|
|
|
30
44
|
Options:
|
|
31
45
|
--daemon, --deamon Run detached in the background
|
package/src/local-task.js
CHANGED
|
@@ -3,15 +3,17 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
3
3
|
import { EventEmitter, once } from "node:events";
|
|
4
4
|
import {
|
|
5
5
|
mkdir,
|
|
6
|
+
mkdtemp,
|
|
6
7
|
lstat,
|
|
7
8
|
readFile,
|
|
8
9
|
readdir,
|
|
9
10
|
realpath,
|
|
10
11
|
rename,
|
|
12
|
+
rm,
|
|
11
13
|
stat,
|
|
12
14
|
writeFile,
|
|
13
15
|
} from "node:fs/promises";
|
|
14
|
-
import { homedir } from "node:os";
|
|
16
|
+
import { homedir, tmpdir } from "node:os";
|
|
15
17
|
import path from "node:path";
|
|
16
18
|
import { promisify } from "node:util";
|
|
17
19
|
|
|
@@ -426,17 +428,38 @@ export async function startLocalTask(
|
|
|
426
428
|
// synchronous spawn boundary so a stopped task can never start Provider work.
|
|
427
429
|
if (task.stop_requested || task.status !== "running") return task;
|
|
428
430
|
|
|
429
|
-
|
|
431
|
+
const runtimeTempBase = process.platform === "darwin" ? "/tmp" : tmpdir();
|
|
432
|
+
const runtimeTempRoot = await mkdtemp(
|
|
433
|
+
path.join(runtimeTempBase, `simy-local-task-${task.id}-`),
|
|
434
|
+
);
|
|
435
|
+
allowCodexRuntimeTemp(command, runtimeTempRoot);
|
|
436
|
+
|
|
437
|
+
return new Promise((resolve, reject) => {
|
|
430
438
|
let settled = false;
|
|
431
439
|
let timeout = null;
|
|
432
440
|
const rawStdout = [];
|
|
433
441
|
const tokenRecords = [];
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
442
|
+
let child;
|
|
443
|
+
try {
|
|
444
|
+
child = command.spawn(command.bin, command.args, {
|
|
445
|
+
cwd: task.workspace_path,
|
|
446
|
+
env: {
|
|
447
|
+
...process.env,
|
|
448
|
+
TMPDIR: runtimeTempRoot,
|
|
449
|
+
TMP: runtimeTempRoot,
|
|
450
|
+
TEMP: runtimeTempRoot,
|
|
451
|
+
...command.env,
|
|
452
|
+
},
|
|
453
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
454
|
+
detached: process.platform !== "win32",
|
|
455
|
+
});
|
|
456
|
+
} catch (error) {
|
|
457
|
+
void rm(runtimeTempRoot, { recursive: true, force: true }).then(
|
|
458
|
+
() => reject(error),
|
|
459
|
+
() => reject(error),
|
|
460
|
+
);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
440
463
|
task.child = child;
|
|
441
464
|
|
|
442
465
|
const onLine = (line) => {
|
|
@@ -523,6 +546,7 @@ export async function startLocalTask(
|
|
|
523
546
|
let artifacts = [];
|
|
524
547
|
let scenarioAcceptance = null;
|
|
525
548
|
try {
|
|
549
|
+
await rm(runtimeTempRoot, { recursive: true, force: true });
|
|
526
550
|
artifacts = task.outputs_root
|
|
527
551
|
? await collectLocalTaskArtifacts(task.outputs_root, {
|
|
528
552
|
localTaskId: task.id,
|
|
@@ -1085,6 +1109,25 @@ function providerTokenCount(record) {
|
|
|
1085
1109
|
return Number(record?.total_tokens || 0);
|
|
1086
1110
|
}
|
|
1087
1111
|
|
|
1112
|
+
function allowCodexRuntimeTemp(command, runtimeTempRoot) {
|
|
1113
|
+
if (
|
|
1114
|
+
command.backend !== "codex" ||
|
|
1115
|
+
command.execution_kind !== "direct" ||
|
|
1116
|
+
command.verification === "explicit_command_override" ||
|
|
1117
|
+
command.args[0] !== "exec" ||
|
|
1118
|
+
command.args.length < 2
|
|
1119
|
+
) {
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const instruction = command.args.at(-1);
|
|
1123
|
+
command.args = [
|
|
1124
|
+
...command.args.slice(0, -1),
|
|
1125
|
+
"--add-dir",
|
|
1126
|
+
runtimeTempRoot,
|
|
1127
|
+
instruction,
|
|
1128
|
+
];
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1088
1131
|
function providerLabel(backend) {
|
|
1089
1132
|
return backend === "claude" ? "Claude Code" : "Codex";
|
|
1090
1133
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
normalizeVisualReview,
|
|
14
14
|
} from "./completion-contract.js";
|
|
15
15
|
|
|
16
|
-
export async function auditAttempt(charter, attempt, { previousAttempt = null } = {}) {
|
|
16
|
+
export async function auditAttempt(charter, attempt, { previousAttempt = null, sqm = null } = {}) {
|
|
17
17
|
const checks = [];
|
|
18
18
|
const addCheck = (
|
|
19
19
|
id,
|
|
@@ -219,6 +219,7 @@ export async function auditAttempt(charter, attempt, { previousAttempt = null }
|
|
|
219
219
|
? "Implementation evidence passed local verification."
|
|
220
220
|
: "Implementation evidence failed local verification.",
|
|
221
221
|
checks,
|
|
222
|
+
sqm: normalizeSqmAudit(sqm),
|
|
222
223
|
};
|
|
223
224
|
|
|
224
225
|
return {
|
|
@@ -257,6 +258,46 @@ export async function auditAttempt(charter, attempt, { previousAttempt = null }
|
|
|
257
258
|
release_gate: null,
|
|
258
259
|
work_log_signals: [],
|
|
259
260
|
work_log_interventions: [],
|
|
261
|
+
sqm: normalizeSqmAudit(sqm),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function normalizeSqmAudit(sqm) {
|
|
266
|
+
if (!sqm) return null;
|
|
267
|
+
return {
|
|
268
|
+
status: sqm.status || "unavailable",
|
|
269
|
+
mode: "shadow",
|
|
270
|
+
bundle: sqm.bundle || null,
|
|
271
|
+
warnings: Array.isArray(sqm.warnings) ? sqm.warnings : [],
|
|
272
|
+
findings: (Array.isArray(sqm.findings) ? sqm.findings : []).map((finding) => ({
|
|
273
|
+
finding_id: finding.finding_id,
|
|
274
|
+
module_id: finding.module_id,
|
|
275
|
+
module_version: finding.module_version,
|
|
276
|
+
rule_id: finding.rule_id,
|
|
277
|
+
severity: finding.severity,
|
|
278
|
+
file: finding.file || null,
|
|
279
|
+
line: finding.line || null,
|
|
280
|
+
side: finding.side || null,
|
|
281
|
+
evidence_hash: finding.evidence_hash,
|
|
282
|
+
bundle_id: finding.bundle_id,
|
|
283
|
+
bundle_version: finding.bundle_version,
|
|
284
|
+
bundle_digest: finding.bundle_digest,
|
|
285
|
+
})),
|
|
286
|
+
min_check: sqm.min_check ? {
|
|
287
|
+
result_digest: sqm.min_check.result_digest || null,
|
|
288
|
+
status: sqm.min_check.status || null,
|
|
289
|
+
executed_at: sqm.min_check.executed_at || null,
|
|
290
|
+
} : null,
|
|
291
|
+
proof: sqm.proof ? {
|
|
292
|
+
proof_id: sqm.proof.proof_id || null,
|
|
293
|
+
digest: sqm.proof.integrity?.digest || null,
|
|
294
|
+
outcome: sqm.proof.outcome?.status || null,
|
|
295
|
+
} : null,
|
|
296
|
+
signed_evidence: sqm.signed_evidence ? {
|
|
297
|
+
evidence_id: sqm.signed_evidence.evidence_id || null,
|
|
298
|
+
key_id: sqm.signed_evidence.signer?.key_id || null,
|
|
299
|
+
algorithm: sqm.signed_evidence.integrity?.algorithm || null,
|
|
300
|
+
} : null,
|
|
260
301
|
};
|
|
261
302
|
}
|
|
262
303
|
|
package/src/orchestrator/loop.js
CHANGED
|
@@ -14,6 +14,7 @@ export async function runCodingLoop({
|
|
|
14
14
|
executeAttempt,
|
|
15
15
|
executeIndependentAudit,
|
|
16
16
|
collectEvidence,
|
|
17
|
+
checkSqm = null,
|
|
17
18
|
onUpdate,
|
|
18
19
|
humanGuidance = "",
|
|
19
20
|
resume = false,
|
|
@@ -127,6 +128,7 @@ export async function runCodingLoop({
|
|
|
127
128
|
});
|
|
128
129
|
await publish(snapshot, "collecting_evidence", onUpdate);
|
|
129
130
|
attempt.observed_evidence = await safeEvidence(collectEvidence, attempt);
|
|
131
|
+
attempt.sqm = await safeSqm(checkSqm, attempt);
|
|
130
132
|
if (shouldStop()) return stopCodingLoop(snapshot, onUpdate);
|
|
131
133
|
|
|
132
134
|
appendEvent(snapshot, "auditing", `Verifying local evidence for attempt ${attemptNumber}.`, {
|
|
@@ -140,7 +142,10 @@ export async function runCodingLoop({
|
|
|
140
142
|
attempt.observed_evidence?.github?.available === true,
|
|
141
143
|
});
|
|
142
144
|
await publish(snapshot, "auditing", onUpdate);
|
|
143
|
-
attempt.audit = await auditAttempt(snapshot.charter, attempt, {
|
|
145
|
+
attempt.audit = await auditAttempt(snapshot.charter, attempt, {
|
|
146
|
+
previousAttempt,
|
|
147
|
+
sqm: attempt.sqm,
|
|
148
|
+
});
|
|
144
149
|
if (shouldStop()) return stopCodingLoop(snapshot, onUpdate);
|
|
145
150
|
attempt.implementation_gate = attempt.audit.implementation_gate;
|
|
146
151
|
snapshot.attempts.push(attempt);
|
|
@@ -212,8 +217,12 @@ export async function runCodingLoop({
|
|
|
212
217
|
|
|
213
218
|
// Re-collect after the read-only auditor to catch any mutated HEAD or working tree.
|
|
214
219
|
attempt.observed_evidence = await safeEvidence(collectEvidence, attempt);
|
|
220
|
+
attempt.sqm = await safeSqm(checkSqm, attempt, attempt.sqm);
|
|
215
221
|
if (shouldStop()) return stopCodingLoop(snapshot, onUpdate);
|
|
216
|
-
attempt.audit = await auditAttempt(snapshot.charter, attempt, {
|
|
222
|
+
attempt.audit = await auditAttempt(snapshot.charter, attempt, {
|
|
223
|
+
previousAttempt,
|
|
224
|
+
sqm: attempt.sqm,
|
|
225
|
+
});
|
|
217
226
|
if (shouldStop()) return stopCodingLoop(snapshot, onUpdate);
|
|
218
227
|
attempt.implementation_gate = attempt.audit.implementation_gate;
|
|
219
228
|
|
|
@@ -267,7 +276,7 @@ async function stopCodingLoop(snapshot, onUpdate) {
|
|
|
267
276
|
return snapshot;
|
|
268
277
|
}
|
|
269
278
|
|
|
270
|
-
export async function recheckPrReadiness({ snapshot, collectEvidence, onUpdate }) {
|
|
279
|
+
export async function recheckPrReadiness({ snapshot, collectEvidence, checkSqm = null, onUpdate }) {
|
|
271
280
|
const attempt = snapshot.attempts.at(-1);
|
|
272
281
|
if (!attempt || !attempt.audit?.passed || !attempt.independent_audit?.passed) return snapshot;
|
|
273
282
|
appendEvent(snapshot, "checking_pr", "Refreshing GitHub review and CI evidence.", {
|
|
@@ -275,8 +284,10 @@ export async function recheckPrReadiness({ snapshot, collectEvidence, onUpdate }
|
|
|
275
284
|
});
|
|
276
285
|
await publish(snapshot, "checking_pr", onUpdate);
|
|
277
286
|
attempt.observed_evidence = await safeEvidence(collectEvidence, attempt);
|
|
287
|
+
attempt.sqm = await safeSqm(checkSqm, attempt, attempt.sqm, "full");
|
|
278
288
|
attempt.audit = await auditAttempt(snapshot.charter, attempt, {
|
|
279
289
|
previousAttempt: snapshot.attempts.at(-2) ?? null,
|
|
290
|
+
sqm: attempt.sqm,
|
|
280
291
|
});
|
|
281
292
|
snapshot.implementation_gate_passed = attempt.audit.passed;
|
|
282
293
|
attempt.pr_readiness = evaluatePrReadiness(snapshot.charter, attempt);
|
|
@@ -352,6 +363,21 @@ async function safeEvidence(collector, attempt) {
|
|
|
352
363
|
}
|
|
353
364
|
}
|
|
354
365
|
|
|
366
|
+
async function safeSqm(checker, attempt, previous = null, phase = "min") {
|
|
367
|
+
if (!checker) return previous;
|
|
368
|
+
try {
|
|
369
|
+
return await checker(attempt, { phase });
|
|
370
|
+
} catch (error) {
|
|
371
|
+
return {
|
|
372
|
+
status: "unavailable",
|
|
373
|
+
mode: "shadow",
|
|
374
|
+
bundle: null,
|
|
375
|
+
findings: [],
|
|
376
|
+
warnings: [error instanceof Error ? error.message : "SQM shadow check failed."],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
355
381
|
function unavailableEvidence(error) {
|
|
356
382
|
return {
|
|
357
383
|
collected_at: new Date().toISOString(),
|
package/src/runner.js
CHANGED
|
@@ -40,6 +40,8 @@ import {
|
|
|
40
40
|
import { LocalRunRegistry } from "./run-registry.js";
|
|
41
41
|
import { webApiHeaders, webApiUrl } from "./web-api.js";
|
|
42
42
|
import { normalizeGitHubRemote } from "./workspace-context.js";
|
|
43
|
+
import { CLI_VERSION } from "./cli-contract.js";
|
|
44
|
+
import { createSqmChecker } from "./sqm/index.js";
|
|
43
45
|
|
|
44
46
|
const execFileAsync = promisify(execFile);
|
|
45
47
|
const MAX_LOCAL_LOG_LINES = 1_000;
|
|
@@ -543,6 +545,20 @@ async function runLocalCodingRun(
|
|
|
543
545
|
attempt,
|
|
544
546
|
repositoryPath,
|
|
545
547
|
}));
|
|
548
|
+
const sqmChecker =
|
|
549
|
+
run.sqmChecker ||
|
|
550
|
+
createSqmChecker({
|
|
551
|
+
cwd: repositoryPath,
|
|
552
|
+
baseBranch: run.snapshot.charter.base_branch,
|
|
553
|
+
cliVersion: CLI_VERSION,
|
|
554
|
+
token: run.session?.token,
|
|
555
|
+
endpoint: sqmBundleEndpoint(run.session),
|
|
556
|
+
evidenceEndpoint: sqmEvidenceEndpoint(run.session),
|
|
557
|
+
accountId: run.session?.account_id || run.session?.auth_user_id || run.session?.device_id,
|
|
558
|
+
organizationId: run.session?.organization_id || run.session?.org_id,
|
|
559
|
+
deviceId: run.session?.device_id,
|
|
560
|
+
});
|
|
561
|
+
run.sqmChecker = sqmChecker;
|
|
546
562
|
|
|
547
563
|
try {
|
|
548
564
|
await runCodingLoop({
|
|
@@ -550,6 +566,7 @@ async function runLocalCodingRun(
|
|
|
550
566
|
executeAttempt: executor,
|
|
551
567
|
executeIndependentAudit: independentAuditor,
|
|
552
568
|
collectEvidence: evidenceCollector,
|
|
569
|
+
checkSqm: sqmChecker,
|
|
553
570
|
humanGuidance,
|
|
554
571
|
resume,
|
|
555
572
|
shouldStop: () => run.stopRequested,
|
|
@@ -601,9 +618,24 @@ export async function recheckLocalCodingRun(run, { collectEvidence } = {}) {
|
|
|
601
618
|
attempt,
|
|
602
619
|
repositoryPath,
|
|
603
620
|
}));
|
|
621
|
+
const sqmChecker =
|
|
622
|
+
run.sqmChecker ||
|
|
623
|
+
createSqmChecker({
|
|
624
|
+
cwd: repositoryPath,
|
|
625
|
+
baseBranch: run.snapshot.charter.base_branch,
|
|
626
|
+
cliVersion: CLI_VERSION,
|
|
627
|
+
token: run.session?.token,
|
|
628
|
+
endpoint: sqmBundleEndpoint(run.session),
|
|
629
|
+
evidenceEndpoint: sqmEvidenceEndpoint(run.session),
|
|
630
|
+
accountId: run.session?.account_id || run.session?.auth_user_id || run.session?.device_id,
|
|
631
|
+
organizationId: run.session?.organization_id || run.session?.org_id,
|
|
632
|
+
deviceId: run.session?.device_id,
|
|
633
|
+
});
|
|
634
|
+
run.sqmChecker = sqmChecker;
|
|
604
635
|
await recheckPrReadiness({
|
|
605
636
|
snapshot: run.snapshot,
|
|
606
637
|
collectEvidence: evidenceCollector,
|
|
638
|
+
checkSqm: sqmChecker,
|
|
607
639
|
onUpdate: async (snapshot) => {
|
|
608
640
|
run.snapshot = snapshot;
|
|
609
641
|
await updateRun(run, snapshot.state);
|
|
@@ -612,6 +644,18 @@ export async function recheckLocalCodingRun(run, { collectEvidence } = {}) {
|
|
|
612
644
|
return run.snapshot;
|
|
613
645
|
}
|
|
614
646
|
|
|
647
|
+
function sqmBundleEndpoint(session) {
|
|
648
|
+
if (process.env.SIMY_SQM_BUNDLE_URL?.trim()) return process.env.SIMY_SQM_BUNDLE_URL.trim();
|
|
649
|
+
if (!session?.api_base_url) return undefined;
|
|
650
|
+
return new URL("sqm/knowledge-bundle", session.api_base_url).href;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function sqmEvidenceEndpoint(session) {
|
|
654
|
+
if (process.env.SIMY_SQM_EVIDENCE_URL?.trim()) return process.env.SIMY_SQM_EVIDENCE_URL.trim();
|
|
655
|
+
if (!session?.api_base_url) return undefined;
|
|
656
|
+
return new URL("sqm/run-evidence", session.api_base_url).href;
|
|
657
|
+
}
|
|
658
|
+
|
|
615
659
|
async function executeProcessAttempt({
|
|
616
660
|
backend,
|
|
617
661
|
instruction,
|