@awak-app/simy-cli 0.6.17 → 0.6.19
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 +17 -0
- package/package.json +1 -1
- package/src/agent.js +30 -9
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/hooks/delivery-loop.mjs +4 -3
- package/src/codex-plugins/simy/hooks/minimal-sufficient.mjs +15 -0
- package/src/delivery-hooks/supervisor.js +2 -1
- package/src/desktop-access.js +2 -1
- package/src/desktop-host.js +197 -1
- package/src/native-development-report-completion.js +143 -0
- package/src/native-development-reports.js +137 -0
- package/src/orchestrator/checklist-continuation.js +5 -0
- package/src/orchestrator/delivery/WORKFLOW.md +12 -0
- package/src/orchestrator/delivery/references/state-model.md +187 -0
- package/src/orchestrator/independent-audit.js +2 -0
- package/src/orchestrator/instruction.js +2 -0
- package/src/orchestrator/loop.js +119 -34
- package/src/runner.js +31 -2
- package/src/sqm/claude-plugin.js +1 -0
- package/src/windows-bootstrap.js +2 -1
- package/src/windows-inbound.js +9 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
NATIVE_DEVELOPMENT_REPORT_COMPLETIONS_PATH,
|
|
5
|
+
normalizeNativeDevelopmentReportCompletionReceipt,
|
|
6
|
+
} from "./native-development-reports.js";
|
|
7
|
+
|
|
8
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
10
|
+
const SOURCES = new Set(["user", "file_exists", "file_contains", "file_equals"]);
|
|
11
|
+
const MAX_CRITERIA = 50;
|
|
12
|
+
|
|
13
|
+
// This creates a device-authenticated local attestation. It is deliberately a
|
|
14
|
+
// projection of the enrolled record and never asserts server-side verification.
|
|
15
|
+
export function projectNativeDevelopmentReportCompletion({ enrollment, record } = {}) {
|
|
16
|
+
if (!isEnabledEnrollment(enrollment) || !isExactCompletedRecord(record, enrollment)) return null;
|
|
17
|
+
|
|
18
|
+
const run = record.run;
|
|
19
|
+
const managed = run.managed;
|
|
20
|
+
const criteria = managed.acceptanceCriteria;
|
|
21
|
+
const frozenAt = currentFrozenEnrollmentAt(run, criteria);
|
|
22
|
+
if (frozenAt === null || !Array.isArray(criteria) || criteria.length === 0 || criteria.length > MAX_CRITERIA) return null;
|
|
23
|
+
|
|
24
|
+
const projectedCriteria = [];
|
|
25
|
+
for (const criterion of criteria) {
|
|
26
|
+
const projected = projectCriterion(criterion, managed.verified, managed.criterionObservations, frozenAt);
|
|
27
|
+
if (!projected) return null;
|
|
28
|
+
projectedCriteria.push(projected);
|
|
29
|
+
}
|
|
30
|
+
projectedCriteria.sort((left, right) => left.id_hash.localeCompare(right.id_hash));
|
|
31
|
+
if (new Set(projectedCriteria.map((criterion) => criterion.id_hash)).size !== projectedCriteria.length) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const verifiedAtMs = Math.max(...projectedCriteria.map((criterion) => Date.parse(criterion.checked_at)));
|
|
36
|
+
if (!Number.isFinite(verifiedAtMs)) return null;
|
|
37
|
+
const checkpoint = run.checkpoint;
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
provider: enrollment.provider,
|
|
40
|
+
provider_session_id: enrollment.provider_session_id.toLowerCase(),
|
|
41
|
+
enrollment_revision: enrollment.revision,
|
|
42
|
+
task_id: managed.taskId.toLowerCase(),
|
|
43
|
+
managed_revision: run.revision,
|
|
44
|
+
checkpoint_digest: digest(checkpoint),
|
|
45
|
+
acceptance_digest: digest(projectedCriteria),
|
|
46
|
+
verified_at: new Date(verifiedAtMs).toISOString(),
|
|
47
|
+
criteria: projectedCriteria,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function sendNativeDevelopmentReportCompletion({ api, enrollment, record } = {}) {
|
|
52
|
+
if (!api || typeof api.submitCompletion !== "function") {
|
|
53
|
+
throw new TypeError("A native development reports API with submitCompletion is required");
|
|
54
|
+
}
|
|
55
|
+
const completion = projectNativeDevelopmentReportCompletion({ enrollment, record });
|
|
56
|
+
if (!completion) return null;
|
|
57
|
+
const receipt = await api.submitCompletion(completion);
|
|
58
|
+
return normalizeNativeDevelopmentReportCompletionReceipt(receipt, completion);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isEnabledEnrollment(enrollment) {
|
|
62
|
+
return Boolean(
|
|
63
|
+
enrollment &&
|
|
64
|
+
enrollment.enabled === true &&
|
|
65
|
+
enrollment.provider === "codex" &&
|
|
66
|
+
UUID.test(enrollment.provider_session_id || "") &&
|
|
67
|
+
Number.isSafeInteger(enrollment.revision) &&
|
|
68
|
+
enrollment.revision > 0,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isExactCompletedRecord(record, enrollment) {
|
|
73
|
+
const run = record?.run;
|
|
74
|
+
const managed = run?.managed;
|
|
75
|
+
return Boolean(
|
|
76
|
+
record &&
|
|
77
|
+
record.provider === enrollment.provider &&
|
|
78
|
+
typeof record.session === "string" &&
|
|
79
|
+
record.session.toLowerCase() === enrollment.provider_session_id.toLowerCase() &&
|
|
80
|
+
run?.state === "completed" &&
|
|
81
|
+
run.checkpoint?.status === "completed" &&
|
|
82
|
+
run.recoveryCause !== "process_exit" &&
|
|
83
|
+
managed &&
|
|
84
|
+
managed.thread?.provider === record.provider &&
|
|
85
|
+
managed.thread?.id === record.session &&
|
|
86
|
+
UUID.test(managed.taskId || "") &&
|
|
87
|
+
Number.isSafeInteger(run.revision) &&
|
|
88
|
+
run.revision >= 0,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function currentFrozenEnrollmentAt(run, criteria) {
|
|
93
|
+
const enrollment = run.managed?.enrollment;
|
|
94
|
+
const frozenAt = typeof enrollment?.frozenAt === "number" ? enrollment.frozenAt : NaN;
|
|
95
|
+
if (!enrollment || !Number.isFinite(frozenAt) || frozenAt < 0 ||
|
|
96
|
+
enrollment.objective !== run.objective ||
|
|
97
|
+
enrollment.acceptanceDigest !== loopDigest(criteria) ||
|
|
98
|
+
canonicalJson(enrollment.deliveryLevel ?? null) !== canonicalJson(run.managed.deliveryLevel ?? null)) return null;
|
|
99
|
+
return frozenAt;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function projectCriterion(criterion, verified, observations, frozenAt) {
|
|
103
|
+
if (!criterion || typeof criterion !== "object" || typeof criterion.id !== "string" || !criterion.id) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const expectedSource = sourceForCheck(criterion.check?.type);
|
|
107
|
+
const evidence = verified?.[criterion.id];
|
|
108
|
+
const checkedAt = normalizeIso(evidence?.checkedAt);
|
|
109
|
+
if (!expectedSource || !checkedAt || Date.parse(checkedAt) < frozenAt || evidence?.source !== expectedSource) return null;
|
|
110
|
+
|
|
111
|
+
const observation = observations?.[criterion.id];
|
|
112
|
+
if (observation && (observation.passed !== true || observation.criterion_id !== criterion.id)) return null;
|
|
113
|
+
if (expectedSource !== "user" && !SHA256.test(evidence?.sha256 || "")) return null;
|
|
114
|
+
return Object.freeze({
|
|
115
|
+
id_hash: digest(criterion.id),
|
|
116
|
+
source: expectedSource,
|
|
117
|
+
checked_at: checkedAt,
|
|
118
|
+
evidence_digest: digest({ evidence, observation: observation || null }),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sourceForCheck(type) {
|
|
123
|
+
return type === "manual" ? "user" : SOURCES.has(type) ? type : null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function normalizeIso(value) {
|
|
127
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
|
|
128
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function digest(value) {
|
|
132
|
+
return createHash("sha256").update(canonicalJson(value)).digest("hex");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function loopDigest(value) {
|
|
136
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function canonicalJson(value) {
|
|
140
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
141
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
142
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
|
143
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { webApiErrorMessage, webApiHeaders, webApiUrl } from "./web-api.js";
|
|
2
|
+
|
|
3
|
+
export const NATIVE_DEVELOPMENT_REPORTS_PATH = "native-development-reports";
|
|
4
|
+
export const NATIVE_DEVELOPMENT_REPORT_COMPLETIONS_PATH = "native-development-report-completions";
|
|
5
|
+
|
|
6
|
+
const PROVIDERS = new Set(["codex", "claude"]);
|
|
7
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
8
|
+
const CONTROL = /[\x00-\x1f\x7f]/;
|
|
9
|
+
|
|
10
|
+
export class NativeDevelopmentReportsError extends Error {
|
|
11
|
+
constructor(message, { code = "native_development_reports_unavailable", status = null } = {}) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "NativeDevelopmentReportsError";
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.status = status;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function normalizeNativeDevelopmentReportEnrollment(value) {
|
|
20
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidResponse();
|
|
21
|
+
const keys = Object.keys(value).sort();
|
|
22
|
+
const expected = ["enabled", "provider", "provider_session_id", "request_title", "revision"];
|
|
23
|
+
if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) throw invalidResponse();
|
|
24
|
+
if (!PROVIDERS.has(value.provider) || !UUID.test(value.provider_session_id) ||
|
|
25
|
+
typeof value.request_title !== "string" || value.request_title.length > 160 || CONTROL.test(value.request_title) ||
|
|
26
|
+
typeof value.enabled !== "boolean" || !Number.isSafeInteger(value.revision) || value.revision < 0 ||
|
|
27
|
+
(value.enabled && value.revision === 0)) throw invalidResponse();
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
provider: value.provider,
|
|
30
|
+
provider_session_id: value.provider_session_id.toLowerCase(),
|
|
31
|
+
request_title: value.request_title,
|
|
32
|
+
enabled: value.enabled,
|
|
33
|
+
revision: value.revision,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function normalizeNativeDevelopmentReportSave(value) {
|
|
38
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new NativeDevelopmentReportsError("Invalid development report request.", { code: "invalid_native_development_report_request" });
|
|
39
|
+
const keys = Object.keys(value).sort();
|
|
40
|
+
const expected = ["enabled", "expected_revision", "provider", "provider_session_id", "request_title"];
|
|
41
|
+
if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) throw new NativeDevelopmentReportsError("Invalid development report request.", { code: "invalid_native_development_report_request" });
|
|
42
|
+
if (!PROVIDERS.has(value.provider) || !UUID.test(value.provider_session_id) || typeof value.enabled !== "boolean" ||
|
|
43
|
+
!Number.isSafeInteger(value.expected_revision) || value.expected_revision < 0 ||
|
|
44
|
+
typeof value.request_title !== "string") throw new NativeDevelopmentReportsError("Invalid development report request.", { code: "invalid_native_development_report_request" });
|
|
45
|
+
const requestTitle = value.request_title.trim();
|
|
46
|
+
if (requestTitle.length > 160 || CONTROL.test(requestTitle) || (value.enabled && requestTitle.length === 0)) {
|
|
47
|
+
throw new NativeDevelopmentReportsError("Invalid development report title.", { code: "invalid_native_development_report_request" });
|
|
48
|
+
}
|
|
49
|
+
return Object.freeze({
|
|
50
|
+
provider: value.provider,
|
|
51
|
+
provider_session_id: value.provider_session_id.toLowerCase(),
|
|
52
|
+
request_title: requestTitle,
|
|
53
|
+
enabled: value.enabled,
|
|
54
|
+
expected_revision: value.expected_revision,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function normalizeNativeDevelopmentReportCompletionReceipt(value, completion) {
|
|
59
|
+
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
|
60
|
+
Object.keys(value).sort().join(",") !== "enrollment_revision,receipt_id,status,task_id") {
|
|
61
|
+
throw invalidResponse();
|
|
62
|
+
}
|
|
63
|
+
if (!UUID.test(value.receipt_id) || !UUID.test(value.task_id) ||
|
|
64
|
+
!Number.isSafeInteger(value.enrollment_revision) || value.enrollment_revision < 1 ||
|
|
65
|
+
!["pending", "sent", "suppressed"].includes(value.status) ||
|
|
66
|
+
!completion || value.task_id.toLowerCase() !== completion.task_id.toLowerCase() ||
|
|
67
|
+
value.enrollment_revision !== completion.enrollment_revision) {
|
|
68
|
+
throw invalidResponse();
|
|
69
|
+
}
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
receipt_id: value.receipt_id.toLowerCase(),
|
|
72
|
+
task_id: value.task_id.toLowerCase(),
|
|
73
|
+
enrollment_revision: value.enrollment_revision,
|
|
74
|
+
status: value.status,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createNativeDevelopmentReportsApi({ webOrigin, readSession, fetchImpl = globalThis.fetch } = {}) {
|
|
79
|
+
if (typeof readSession !== "function") throw new TypeError("readSession is required");
|
|
80
|
+
if (typeof fetchImpl !== "function") throw new TypeError("fetchImpl is required");
|
|
81
|
+
|
|
82
|
+
const sessionForRequest = async () => {
|
|
83
|
+
const session = await readSession();
|
|
84
|
+
if (!session?.token || !session?.api_base_url) {
|
|
85
|
+
throw new NativeDevelopmentReportsError("SIMY desktop sign-in is required.", { code: "device_session_unavailable" });
|
|
86
|
+
}
|
|
87
|
+
return session;
|
|
88
|
+
};
|
|
89
|
+
const request = async (method, body = undefined, path = NATIVE_DEVELOPMENT_REPORTS_PATH) => {
|
|
90
|
+
const session = await sessionForRequest();
|
|
91
|
+
let response;
|
|
92
|
+
try {
|
|
93
|
+
response = await fetchImpl(webApiUrl(path, {
|
|
94
|
+
webOrigin, apiBaseUrl: session.api_base_url,
|
|
95
|
+
}), {
|
|
96
|
+
method,
|
|
97
|
+
headers: webApiHeaders({ token: session.token }, body === undefined ? {} : { "Content-Type": "application/json" }),
|
|
98
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
99
|
+
signal: AbortSignal.timeout(10_000),
|
|
100
|
+
cache: "no-store",
|
|
101
|
+
});
|
|
102
|
+
} catch {
|
|
103
|
+
throw new NativeDevelopmentReportsError("Development report settings could not be reached.");
|
|
104
|
+
}
|
|
105
|
+
const payload = await response.json().catch(() => null);
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
throw new NativeDevelopmentReportsError(webApiErrorMessage(payload, response.status), {
|
|
108
|
+
code: response.status === 409 ? "conflict" : "native_development_reports_unavailable",
|
|
109
|
+
status: response.status,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return payload;
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
async list() {
|
|
116
|
+
const payload = await request("GET");
|
|
117
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload) ||
|
|
118
|
+
Object.keys(payload).length !== 1 || !Array.isArray(payload.enrollments)) throw invalidResponse();
|
|
119
|
+
return payload.enrollments.map(normalizeNativeDevelopmentReportEnrollment);
|
|
120
|
+
},
|
|
121
|
+
async save(value) {
|
|
122
|
+
const requestValue = normalizeNativeDevelopmentReportSave(value);
|
|
123
|
+
const payload = await request("PUT", requestValue);
|
|
124
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload) ||
|
|
125
|
+
Object.keys(payload).length !== 1) throw invalidResponse();
|
|
126
|
+
return normalizeNativeDevelopmentReportEnrollment(payload.enrollment);
|
|
127
|
+
},
|
|
128
|
+
async submitCompletion(value) {
|
|
129
|
+
const payload = await request("POST", value, NATIVE_DEVELOPMENT_REPORT_COMPLETIONS_PATH);
|
|
130
|
+
return normalizeNativeDevelopmentReportCompletionReceipt(payload, value);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function invalidResponse() {
|
|
136
|
+
return new NativeDevelopmentReportsError("Development report settings returned an invalid response.", { code: "invalid_native_development_reports_response" });
|
|
137
|
+
}
|
|
@@ -5,8 +5,13 @@ export const ACCEPTANCE_EVIDENCE_PATH = fileURLToPath(
|
|
|
5
5
|
new URL("./delivery/references/acceptance-evidence.md", import.meta.url),
|
|
6
6
|
);
|
|
7
7
|
|
|
8
|
+
export const DELIVERY_STATE_MODEL_PATH = fileURLToPath(
|
|
9
|
+
new URL("./delivery/references/state-model.md", import.meta.url),
|
|
10
|
+
);
|
|
11
|
+
|
|
8
12
|
export function buildChecklistContinuationInstruction({ auditor = false } = {}) {
|
|
9
13
|
return section("checklist_continuation_contract", [
|
|
14
|
+
`Read the integrated delivery state model: ${DELIVERY_STATE_MODEL_PATH}. Keep proven delivery milestones, controller execution states, and final dispositions distinct. Follow the repair/reverify and evidence-invalidation branches; a PR readiness state never proves deployment or overall completion. The model documents the existing controller and executor duties, not an automatic scheduler or new transition validator.`,
|
|
10
15
|
`The table is a progress checkpoint, not the deliverable or a stopping condition. Read: ${ACCEPTANCE_EVIDENCE_PATH}`,
|
|
11
16
|
"Maintain the complete 依頼チェックリスト with columns #, ご依頼, 現状, 判定 in the evidence record after chartering, each evaluated attempt, and in the final receipt. User-facing reporting follows workflow_reporting_contract; show the full table only when requested or when it is the most useful requested answer, never as mandatory commentary. Preserve stable criterion IDs, all rows and both applicable evidence layers. ✅ requires current valid passing evidence for every applicable layer; ❌ means an observed failure; 🟡 means pending, missing, stale, blocked or unverifiable evidence. Retain counts and highest proven state in that record.",
|
|
12
17
|
"After observing any non-green row, classify its cause and immediately take the next safe authorized action: implement missing behavior, repair a failure, exercise an untested path, collect missing evidence, or rerun stale verification. Re-observe the same criterion against the current candidate, run the validator, update the table, and repeat until every applicable requested outcome and gate for the authorized endpoint is verified. A next-action list without performing feasible actions is not completion. Never require another continue request merely because a table was shown.",
|
|
@@ -406,6 +406,10 @@ decision when the trade-off materially changes.
|
|
|
406
406
|
|
|
407
407
|
## Keep delivery states distinct
|
|
408
408
|
|
|
409
|
+
Use the [integrated state model](references/state-model.md) for repair, wait,
|
|
410
|
+
resume, evidence invalidation and endpoint boundaries. It distinguishes these
|
|
411
|
+
delivery milestones from actual CLI controller states and final dispositions.
|
|
412
|
+
|
|
409
413
|
Track and report the highest state actually proven:
|
|
410
414
|
|
|
411
415
|
`chartered -> reproduction_verified -> implemented -> locally_verified -> pr_ci_green -> integration_verified -> user_like_local_verified -> merged -> deployed_exact_revision -> health_verified -> live_verified -> cleanup_verified -> done`
|
|
@@ -517,6 +521,14 @@ boundary from a simplified unit fixture.
|
|
|
517
521
|
|
|
518
522
|
### 2. Isolate and implement
|
|
519
523
|
|
|
524
|
+
Choose the smallest change sufficient for the original purpose and acceptance.
|
|
525
|
+
Tie substantive behavior, abstractions, configuration, compatibility, docs and
|
|
526
|
+
tests to a criterion or observed failure. Independent review must identify
|
|
527
|
+
unnecessary scope or complexity with file/line, burden, a smaller alternative,
|
|
528
|
+
preserved criteria and required verification. Do not optimize line counts or
|
|
529
|
+
remove required behavior, authorization, data integrity, concurrency, recovery,
|
|
530
|
+
regression coverage or evidence to shorten the diff.
|
|
531
|
+
|
|
520
532
|
Start from the latest required base in a dedicated worktree. Preserve user
|
|
521
533
|
changes. Build the smallest behaviorally correct patch and add a regression
|
|
522
534
|
that fails against the pre-fix behavior at the escaped boundary. Derive its
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Delivery Loop の統合状態モデル
|
|
2
|
+
|
|
3
|
+
現行CLIの正本。対象は CLI Agentic Loop (`src/orchestrator/loop.js`) と、
|
|
4
|
+
そのexecutorが従うDelivery workflow。ローカルhook型Loopとは区別する。
|
|
5
|
+
図は新しいruntime enumや自動スケジューラを追加しない。
|
|
6
|
+
|
|
7
|
+
## 1. 利用者が判断する全体図
|
|
8
|
+
|
|
9
|
+
目的は、元の依頼を満たした証拠が揃うまで、安全に実装・検証を続けること。
|
|
10
|
+
「今どこまで証明できたか」「次に何をするか」「今回の終了理由」を分けて保持する。
|
|
11
|
+
以下はexecutor/auditorの行動契約であり、各矢印がCLIに自動実装済みという意味ではない。
|
|
12
|
+
|
|
13
|
+
```mermaid
|
|
14
|
+
flowchart TD
|
|
15
|
+
Contract[目的・体験・成功条件・権限・終了地点を固定] --> Baseline[現状と再現を確認・リスク相応のレビュー計画]
|
|
16
|
+
Baseline --> Work[実装・修正・不足証拠の取得]
|
|
17
|
+
Work --> Verify[現在の版で検証・チェックリスト更新]
|
|
18
|
+
Verify --> Review[独立レビュー・元の依頼と証拠を照合]
|
|
19
|
+
Review --> Decision{未達の原因と次の行動}
|
|
20
|
+
Decision -->|安全に修正・再検証できる| Work
|
|
21
|
+
Decision -->|CI等が進行中| Wait[上限のある待機・再確認]
|
|
22
|
+
Wait --> Verify
|
|
23
|
+
Decision -->|本当の権限・入力不足/進捗停止| Hold[未完了を記録・独立して進められる作業は続行]
|
|
24
|
+
Hold -->|不足解消・必要な承認・再開| Verify
|
|
25
|
+
Decision -->|全行と適用ゲートが合格| Endpoint{固定した終了地点}
|
|
26
|
+
Endpoint -->|PRまで| PR[PRの検証結果・残る非対象作業を記録]
|
|
27
|
+
Endpoint -->|配備までの権限あり| Release[マージ・exact revision配備・段階的な公開]
|
|
28
|
+
Release --> Live[health・実利用経路・成果とguardrailを観測]
|
|
29
|
+
Live -->|未達・回帰| Recovery[公開保留/権限内のrollback・修正]
|
|
30
|
+
Recovery --> Work
|
|
31
|
+
Live -->|合格| Cleanup[cleanupと最終証拠を確認・As-Is更新]
|
|
32
|
+
PR --> Final[元の依頼・全行・終了地点を最終照合]
|
|
33
|
+
Cleanup --> Final
|
|
34
|
+
Final -->|現在の証拠で全条件合格| Complete[依頼範囲の完了を報告]
|
|
35
|
+
Final -->|不足・失効| Decision
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- チェックリストの◎相当は現行表記の **✅**。適用する体験・技術の両層に、
|
|
39
|
+
現在の候補と条件に束縛された合格証拠が必要。🟡・❌は完了ではない。
|
|
40
|
+
- 目的・体験ID・受入条件は保持する。全行を消さずに修正→検証→更新を繰り返す。
|
|
41
|
+
個別の軽微な指摘の扱いは実際の監査判定に従い、未達の受入条件を軽微扱いして免除しない。
|
|
42
|
+
- PRのみの依頼はmerge/deployを許可しない。一方、配備までの依頼をPR作成で終えない。
|
|
43
|
+
- 自動待機を装わない。利用できるbounded wait/recheckまたは承認済みmonitorを使い、
|
|
44
|
+
継続不能なら具体的な制約と再開条件を残す。停止指示は尊重する。
|
|
45
|
+
- 修正によってhead/base/設定/依存版/環境/成功条件が変われば、影響する証拠を失効させて
|
|
46
|
+
再検証する。過去の合格履歴は消さず、現在も合格とは扱わない。
|
|
47
|
+
|
|
48
|
+
## 2. 証明済みの到達段階と品質ゲート
|
|
49
|
+
|
|
50
|
+
次の13段階は成果のmilestoneであり、下節のcontroller stateとは別。
|
|
51
|
+
順番だけで昇格せず、適用証拠を確認する。非該当は理由を明示する。
|
|
52
|
+
|
|
53
|
+
```mermaid
|
|
54
|
+
flowchart LR
|
|
55
|
+
chartered --> reproduction_verified --> implemented --> locally_verified
|
|
56
|
+
locally_verified --> pr_ci_green --> integration_verified --> user_like_local_verified
|
|
57
|
+
user_like_local_verified --> merged --> deployed_exact_revision
|
|
58
|
+
deployed_exact_revision --> health_verified --> live_verified --> cleanup_verified --> done
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| 昇格箇所 | 保持すべき品質ゲート |
|
|
62
|
+
|---|---|
|
|
63
|
+
| chartered / reproduction_verified | 元の依頼・完全な利用体験・As-Is差分・成功契約・権限・レビュー計画 |
|
|
64
|
+
| implemented / locally_verified | 修正、体験全体と技術的不変条件、必要な独立レビュー、現行証拠 |
|
|
65
|
+
| PR準備と検証 | PR preparationの8行と4条件付きsafeguard、最新remote base/current head、CI・E2E・skip開示・差分・cleanup。明示opt-in時のSQM |
|
|
66
|
+
| integration_verified / user_like_local_verified | 統合候補と実利用同等の経路。CI greenだけでは代替しない |
|
|
67
|
+
| merged / deployed_exact_revision | 各操作の権限、正確な配備版、rolloutのpromote/hold/rollback条件 |
|
|
68
|
+
| health_verified / live_verified | 稼働確認と利用者の成果を区別し、必要な観測windowとguardrailを検証 |
|
|
69
|
+
| cleanup_verified / done | 適用する後片付け・最終受入条件・receipt。検証済みAs-Isを更新 |
|
|
70
|
+
|
|
71
|
+
この表はworkflow上の要求。各条件の機械検証範囲は既存validatorとcollectorに従い、
|
|
72
|
+
図だけを根拠に検証済みとはしない。詳細は [state and receipts](state-and-receipts.md)、
|
|
73
|
+
[minimum completion](minimum-completion.md)、[acceptance evidence](acceptance-evidence.md)。
|
|
74
|
+
PR範囲の完了を報告しても、未配備のmilestoneを `done` に進めない。
|
|
75
|
+
|
|
76
|
+
## 3. 現行controllerの実際の制御
|
|
77
|
+
|
|
78
|
+
以下は `loop.js` / `audit.js` と保存済みworker結果の復帰経路。`Gate`、`Receipt`、`RecheckGate`、`Failure` は説明用の判定節点。
|
|
79
|
+
新規runの契約不備は `waiting_human`。resume時も目的・minimum契約を再検査する。
|
|
80
|
+
|
|
81
|
+
```mermaid
|
|
82
|
+
flowchart TD
|
|
83
|
+
queued --> Gate{目的・minimum契約の検査}
|
|
84
|
+
Gate -->|不備| waiting_human
|
|
85
|
+
Gate -->|新規| risk_classifying
|
|
86
|
+
risk_classifying -->|charter不備| waiting_human
|
|
87
|
+
risk_classifying -->|有効| chartering
|
|
88
|
+
chartering --> Receipt{保存済みworker結果の検査}
|
|
89
|
+
Gate -->|resume・既存attempt・契約有効| Receipt
|
|
90
|
+
Receipt -->|保存結果なし| dispatching
|
|
91
|
+
Receipt -->|保存結果が不正| waiting_human
|
|
92
|
+
Receipt -->|有効な完了結果・同じattemptを復帰| collecting_evidence
|
|
93
|
+
dispatching --> coding --> collecting_evidence --> auditing
|
|
94
|
+
coding -->|完了結果の保存失敗・復帰対象外で照合待ち| failed
|
|
95
|
+
collecting_evidence & auditing & independent_auditing -->|有効な保存結果あり・controller例外| failed
|
|
96
|
+
collecting_evidence -->|保存結果あり・収集の再試行も失敗| waiting_human
|
|
97
|
+
independent_auditing -->|保存結果あり・証拠再収集の再試行も失敗| waiting_human
|
|
98
|
+
auditing -->|合格| independent_auditing
|
|
99
|
+
auditing -->|不合格| Failure{失敗原因・進捗・再試行判定}
|
|
100
|
+
independent_auditing -->|証拠を再収集し監査・独立レビュー不合格| Failure
|
|
101
|
+
independent_auditing -->|両方合格・PR条件未達| pr_ready_for_review
|
|
102
|
+
independent_auditing -->|両方合格・PR条件全合格| merge_ready
|
|
103
|
+
Failure -->|修正可能| re_instructing
|
|
104
|
+
re_instructing --> dispatching
|
|
105
|
+
Failure -->|同一失敗/成果の進捗なし| blocked
|
|
106
|
+
Failure -->|人の入力が必要/連続して評価不能| waiting_human
|
|
107
|
+
pr_ready_for_review -->|callerまたはdaemonのrecheck| RecheckGate{目的契約とレビューの目的digest}
|
|
108
|
+
merge_ready -->|callerまたはdaemonのrecheck| RecheckGate
|
|
109
|
+
RecheckGate -->|不備| waiting_human
|
|
110
|
+
RecheckGate -->|有効| checking_pr
|
|
111
|
+
checking_pr -->|現在の証拠・レビュー束縛を含むPR条件未達| pr_ready_for_review
|
|
112
|
+
checking_pr -->|現在のPR条件全合格| merge_ready
|
|
113
|
+
blocked -->|原因解消・許可されたresume| Gate
|
|
114
|
+
waiting_human -->|入力・許可されたresume| Gate
|
|
115
|
+
failed -->|復旧対象の完了結果あり・runnerで復元| waiting_human
|
|
116
|
+
waiting_human -->|中断復元・有効な完了結果・daemonの認証と作業権限確認| Gate
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- 実行中のstop確認点で `stopped` に戻る。図は割込みを全辺に描いていない。
|
|
120
|
+
停止を自動解除しない。`local_stop_requested` を保持し、手動停止をdaemonの復帰対象にしない。
|
|
121
|
+
- `handleFailedAudit` は失敗原因を決定論的に分岐する。修正可能な失敗は
|
|
122
|
+
`re_instructing` から次のattemptへ。成果評価不能は成果の停滞と同一視しない。
|
|
123
|
+
- `budget.js` は現在 `local_limits_removed: true`。token/回数は観測・通知の値で、
|
|
124
|
+
`canStartProvider` は消費量で止めず `codingAttemptCeiling` は無限。
|
|
125
|
+
旧版の「3回/予算切れ→終了」を現行の通常経路として復活させない。
|
|
126
|
+
残っているbudget停止用コードは現行の到達可能な通常経路として描いていない。
|
|
127
|
+
外部providerの制約や停止・承認・進捗保護を無視してよいという意味ではない。
|
|
128
|
+
- `runCodingLoop` はPR readinessを返すと終了する。既存attemptがあり、保存済み
|
|
129
|
+
`completed_worker_result` がない非resume呼出しは `checking_pr` で再評価し、
|
|
130
|
+
再実装・新しい独立レビューは行わない。修正や新しいレビューが必要なら、
|
|
131
|
+
適切なresumeで新attemptを実行する。保存済み結果がある場合は下記の復帰経路になる。
|
|
132
|
+
既存の認証済みdaemonは稼働中、作業実行が許可され、他のoperation/childがない
|
|
133
|
+
PR-ready runを定期recheckする(`agent.js` / `runner.js`)。daemon不在・認証切れでは
|
|
134
|
+
この監視を期待できず、recheck自体は修正attemptを開始しない。
|
|
135
|
+
この資料は追加のmonitorや自動resumeを起動しない。
|
|
136
|
+
- 有効な `completed_worker_result` があれば、同じattemptのworkerを再実行せず
|
|
137
|
+
`collecting_evidence` から監査・独立監査をやり直す(`completed_worker_result_recovered`)。
|
|
138
|
+
保存結果は受入証拠ではなく、run・目的digest・attempt等への束縛を再検査する。
|
|
139
|
+
不正な結果は `WORKER_RESULT_UNVERIFIED` で `waiting_human`。
|
|
140
|
+
保存結果がある証拠収集は最大2回再試行し、失敗が続けば結果を保持したまま
|
|
141
|
+
`EVIDENCE_COLLECTION_UNAVAILABLE` で `waiting_human`。再開時も同じattemptから再収集する。
|
|
142
|
+
完了結果の永続化失敗は `worker_result_persistence_failed` をrunnerへ伝え、
|
|
143
|
+
`failed` と照合待ちを記録する。保存に失敗した結果を安全な復帰用receiptと扱わない。
|
|
144
|
+
- 既存daemonは、中断状態(または `controller_result_recovery` 付きの `failed`)を
|
|
145
|
+
復元すると、目的・結果・actor束縛・認証・作業権限を確認し、停止要求や他のoperation/childが
|
|
146
|
+
なければ `recoverCompletedLocalCodingRun` で検証を自動復帰する。
|
|
147
|
+
すべての `waiting_human` を自動解除する機能ではなく、通常のPR recheckとも別経路。
|
|
148
|
+
この既存の復帰制御を説明する資料であり、新しいdaemonやLoopの起動を許可するものではない。
|
|
149
|
+
- recheck前の目的契約不正、または評価済み成果の `outcome_progress.objective_digest` 不一致は
|
|
150
|
+
`waiting_human`。head/base等に対する `evidence_identity_sha256` の失効・不足は
|
|
151
|
+
PR readinessの未達として `pr_ready_for_review` になり、同じ扱いではない。
|
|
152
|
+
- `pr_ready_for_review` はpending状態で、名前だけでは実装の合格さえ保証しない。
|
|
153
|
+
recheckで古い証拠が失効した場合もこの状態になる。checksとpending reasonsを読む。
|
|
154
|
+
- `merge_ready` と `done_gate_passed` は現在のPR readiness判定。
|
|
155
|
+
マージ実行、配備、元の依頼全体の完了を証明しない。
|
|
156
|
+
|
|
157
|
+
## 4. 最終報告と証拠の失効
|
|
158
|
+
|
|
159
|
+
`verified_complete / best_available / blocked / failed / cancelled` はworkflowの
|
|
160
|
+
終了理由。controller enumと混ぜたり、すべてを `done` に変換したりしない。
|
|
161
|
+
`verified_complete` は固定した依頼範囲の全条件が現在の証拠で満たされた場合のみ。
|
|
162
|
+
|
|
163
|
+
| 変化・停止 | 必要な行動 |
|
|
164
|
+
|---|---|
|
|
165
|
+
| head/base/環境/依存版の変化 | 影響する証拠をpendingに戻し、同じ受入IDで再取得・独立照合 |
|
|
166
|
+
| 承認された目的変更 | successor契約として履歴保持。旧目的のレビューを再利用しない |
|
|
167
|
+
| CIや依存処理の実行中 | bounded wait/recheck。重複jobを起動せず、未変化だけでblockedにしない |
|
|
168
|
+
| 人の権限・入力が本当に不足 | affected IDs、根拠、必要入力、owner、再開条件を記録。独立作業は継続 |
|
|
169
|
+
| 同じ失敗・成果停滞 | 原因と別の安全な方針を検討し、controllerの保護を迂回せず再開 |
|
|
170
|
+
| 中止 | チェックポイントと最高到達段階を保持し、未完了として報告 |
|
|
171
|
+
|
|
172
|
+
これは既存制御の説明とexecutor/auditorへの契約であり、追加の遷移強制validatorではない。
|
|
173
|
+
|
|
174
|
+
## 5. 過去版との比較と採用理由
|
|
175
|
+
|
|
176
|
+
| 版 | 採用する点 | 単独では足りない点 |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| [9月8日 Slack仕様](https://specialist-doctors.slack.com/archives/C04URHQBJV7/p1788832579923809) | 13 milestones、単一controller、証拠・終了理由の分離 | 当時の回数・予算停止を現行に適用できない |
|
|
179
|
+
| [9月14日 #79](https://github.com/SpecialistDoctors-Inc/simy-cli/pull/79) | 未達→修正→再検証、確認不能からの復帰、minimumと全体完了の区別 | 体験全体・PR準備・rolloutの詳細が足りない |
|
|
180
|
+
| [9月16日 #97](https://github.com/SpecialistDoctors-Inc/simy-cli/pull/97) | 体験・レビュー計画・PR preparation・段階的配備・As-Is更新 | 主経路中心で、待機・修正・再開や実装との対応が読み取りづらい |
|
|
181
|
+
| 現行CLI 0.6.11 / 基準commit `3b9b453` | 実際の修正ループ、完了済みworker結果からの検証復帰、証拠再取得、進捗保護、PR recheck | workflowの13 milestonesとcontroller stateを一つの状態として読むと誤解する |
|
|
182
|
+
| 本統合版 | 全体判断図+到達段階と品質ゲート+実装制御図を相互参照 | 図や指示による保証範囲を超えた自動制御は追加しない |
|
|
183
|
+
|
|
184
|
+
8月31日・9月2日のSlack ZIP添付は存在と名称のみ確認でき、本文を取得できなかったため、
|
|
185
|
+
内容の優劣を断定していない。上表は読めた一次資料と現行ソースに基づく選定。
|
|
186
|
+
未マージPRの機能は現行として含めない。更新時は `loop.js`、`audit.js`、`budget.js`、
|
|
187
|
+
`outcome-monitor.js`、`checklist-continuation.js`、`coding-run-recovery.js`、`runner.js`、`agent.js` と突合し、3層を一緒に見直す。
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { minimalSufficientChangeInstruction } from "../codex-plugins/simy/hooks/minimal-sufficient.mjs";
|
|
1
2
|
import { buildAcceptedHumanGuidanceInstruction, buildWorkflowReportingInstruction } from "../workflow-reporting.js";
|
|
2
3
|
import { bullets, cleanString, section } from "./shared.js";
|
|
3
4
|
import { buildPrClosureInstruction } from "./pr-closure.js";
|
|
@@ -26,6 +27,7 @@ export function buildIndependentAuditInstruction(charter, attempt) {
|
|
|
26
27
|
|
|
27
28
|
return [
|
|
28
29
|
buildUxSpecificationInstruction({ auditor: true }),
|
|
30
|
+
section("minimal_sufficient_change", minimalSufficientChangeInstruction({ auditor: true })),
|
|
29
31
|
buildDeliveryLevelInstruction({ auditor: true }),
|
|
30
32
|
buildChecklistContinuationInstruction({ auditor: true }),
|
|
31
33
|
buildWorkflowReportingInstruction({ auditor: true }),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { minimalSufficientChangeInstruction } from "../codex-plugins/simy/hooks/minimal-sufficient.mjs";
|
|
1
2
|
import { buildAcceptedHumanGuidanceInstruction, buildWorkflowReportingInstruction } from "../workflow-reporting.js";
|
|
2
3
|
import { minimumCharter } from "./delivery/scripts/check-minimum-completion.mjs";
|
|
3
4
|
import { bullets, looksLikeUiTask, section } from "./shared.js";
|
|
@@ -23,6 +24,7 @@ export function buildCodingInstruction(
|
|
|
23
24
|
const sections = [
|
|
24
25
|
section("role", "You are the local coding executor for a SIMY agentic loop."),
|
|
25
26
|
buildUxSpecificationInstruction(),
|
|
27
|
+
section("minimal_sufficient_change", minimalSufficientChangeInstruction()),
|
|
26
28
|
buildDeliveryLevelInstruction(),
|
|
27
29
|
buildChecklistContinuationInstruction(),
|
|
28
30
|
buildWorkflowReportingInstruction(),
|