@awak-app/simy-cli 0.7.8 → 0.7.10
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 +16 -9
- package/package.json +4 -3
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/.codex-plugin/plugin.json +5 -7
- package/src/codex-plugins/simy/hooks/README.md +3 -1
- package/src/codex-plugins/simy/hooks/delivery-loop.mjs +12 -2
- package/src/codex-plugins/simy/hooks/hypothesis-sheet.mjs +1 -1
- package/src/codex-plugins/simy/hooks/purpose-cycle.mjs +6 -5
- package/src/codex-plugins/simy/hooks/recovery-incident.mjs +191 -0
- package/src/codex-plugins/simy/hooks/scene-reasoning.mjs +22 -0
- package/src/delivery-hooks/command.js +34 -1
- package/src/delivery-hooks/diagnosis-sheet.js +2 -2
- package/src/delivery-hooks/diagnosis.js +5 -3
- package/src/delivery-hooks/local-supervisor.js +7 -1
- package/src/delivery-hooks/recovery.js +33 -0
- package/src/delivery-hooks/supervisor-owner.js +74 -0
- package/src/delivery-hooks/supervisor-service.js +65 -0
- package/src/delivery-hooks/supervisor-watchdog.js +151 -0
- package/src/delivery-hooks/supervisor.js +26 -0
- package/src/direct-executor.js +2 -1
- package/src/orchestrator/instruction.js +4 -4
- package/src/orchestrator/loop.js +1 -1
- package/src/orchestrator/problem-solving.js +2 -2
- package/src/session-supervision/codex-app-server.js +9 -5
- package/src/session-supervision/continuation-analysis.js +3 -2
- package/src/session-supervision/purpose-instruction.js +10 -2
- package/src/session-supervision/purpose-recovery.js +7 -4
- package/src/sqm/claude-plugin.js +6 -0
- package/src/sqm/local-tests.js +1 -0
- package/src/sqm/review-worker.js +3 -0
- package/src/sqm/workflow-onboarding.js +1 -1
- /package/src/{codex-plugins/simy/skills/delivery-loop/SKILL.md → delivery-hooks/claude-workflow.md} +0 -0
|
@@ -617,7 +617,7 @@ async function frozenNativeSandbox(record, root, signal) {
|
|
|
617
617
|
return policy;
|
|
618
618
|
}
|
|
619
619
|
|
|
620
|
-
export async function executeCodexAppServer(record, { webOrigin, openClient = openCodexAppServer, signal, authorizePreparation = async () => {}, beforeDispatch = async () => {}, onActivity = () => {}, onUsage = () => {}, onEvidence = async () => {}, observeExternal = collectPurposeObservations, root, auditOnly = false, opinionOnly = false, peerAction, runPeerReview = runStructuredProviderReview } = {}) {
|
|
620
|
+
export async function executeCodexAppServer(record, { webOrigin, openClient = openCodexAppServer, signal, authorizePreparation = async () => {}, beforeDispatch = async () => {}, onStarted = async () => {}, onActivity = () => {}, onUsage = () => {}, onEvidence = async () => {}, observeExternal = collectPurposeObservations, root, auditOnly = false, opinionOnly = false, peerAction, runPeerReview = runStructuredProviderReview } = {}) {
|
|
621
621
|
const run = record.run, decision = run.managed?.decision;
|
|
622
622
|
if(run.managed?.enrollment?.nativeAuthorityDigest && run.managed.enrollment.nativeAuthorityDigest!==createHash('sha256').update(JSON.stringify([run.managed.providerSandbox,run.managed.providerSandboxSource])).digest('hex'))throw new CodexAppServerError('permission_narrowed','Frozen native enrollment authority changed; do not dispatch with the altered ceiling.');
|
|
623
623
|
if (!decision || !run.managed.autonomyPolicy) throw new CodexAppServerError('decision', 'An assessed action is required before Codex dispatch.');
|
|
@@ -672,7 +672,7 @@ export async function executeCodexAppServer(record, { webOrigin, openClient = op
|
|
|
672
672
|
};
|
|
673
673
|
const beforeEvidence = searchEnabled(run.managed) && !reviewing ? await captureWorkspace() : null;
|
|
674
674
|
let evidenceQueue = Promise.resolve(), evidenceFailure = null;
|
|
675
|
-
let activeTurnId = null, turnCompleted = false, turnStart = null;
|
|
675
|
+
let activeTurnId = null, turnCompleted = false, turnStart = null, startReceipt = null;
|
|
676
676
|
let usage = null, lastTotal = null;
|
|
677
677
|
try {
|
|
678
678
|
// An unavailable login is a connection dependency, not an action approval.
|
|
@@ -789,7 +789,7 @@ export async function executeCodexAppServer(record, { webOrigin, openClient = op
|
|
|
789
789
|
}
|
|
790
790
|
if (signal?.aborted) throw new CodexAppServerError('cancelled', 'Worker dispatch was cancelled during preparation.');
|
|
791
791
|
const done = new Promise((resolve, reject) => {
|
|
792
|
-
let turnId = null, completion = null, settled = false;
|
|
792
|
+
let turnId = null, completion = null, settled = false, startRecorded = false;
|
|
793
793
|
const messages = new Map();
|
|
794
794
|
const activeTools = new Set();
|
|
795
795
|
const earlyActivity = [];
|
|
@@ -819,7 +819,7 @@ export async function executeCodexAppServer(record, { webOrigin, openClient = op
|
|
|
819
819
|
const cancelled = () => finish(new CodexAppServerError('cancelled', 'Codex turn was cancelled.'));
|
|
820
820
|
const closed = () => finish(new CodexAppServerError('closed', 'Codex app-server closed before turn completion.'));
|
|
821
821
|
const complete = () => {
|
|
822
|
-
if (!completion || !turnId || completion.turn?.id !== turnId) return;
|
|
822
|
+
if (!completion || !turnId || !startRecorded || completion.turn?.id !== turnId) return;
|
|
823
823
|
turnCompleted = true;
|
|
824
824
|
if (completion.turn.status !== 'completed') return finish(new CodexAppServerError('turn_failed', 'Codex turn did not complete.'));
|
|
825
825
|
const message = messages.get(turnId);
|
|
@@ -846,7 +846,10 @@ export async function executeCodexAppServer(record, { webOrigin, openClient = op
|
|
|
846
846
|
client.child.once('close', closed);
|
|
847
847
|
turnStart = client.request('turn/start', { threadId: targetSession, input: [{ type: 'text', text: `${prompt}\n${purposeInstruction}` }], cwd: isolated ? executionCwd : record.cwd,
|
|
848
848
|
model: run.managed.resumeModel, ...(isolated ? {sandboxPolicy} : {}) })
|
|
849
|
-
.then(response => { turnId = response?.turn?.id; activeTurnId = turnId; if (!turnId) finish(new CodexAppServerError('turn_failed', 'Codex did not start a turn.')); else {
|
|
849
|
+
.then(async response => { turnId = response?.turn?.id; activeTurnId = turnId; if (!turnId) finish(new CodexAppServerError('turn_failed', 'Codex did not start a turn.')); else {
|
|
850
|
+
startReceipt = Promise.resolve().then(() => onStarted({ turnId, session: targetSession }));
|
|
851
|
+
await startReceipt;
|
|
852
|
+
startRecorded = true;
|
|
850
853
|
for (const params of earlyUsage) if (params.turnId === turnId) observeUsage(params);
|
|
851
854
|
for (const params of earlyActivity) if (params.turnId === turnId) observeActivity(params.method, params);
|
|
852
855
|
complete();
|
|
@@ -912,6 +915,7 @@ export async function executeCodexAppServer(record, { webOrigin, openClient = op
|
|
|
912
915
|
await evidenceQueue;
|
|
913
916
|
throw error;
|
|
914
917
|
} finally {
|
|
918
|
+
if (startReceipt) await startReceipt.catch(() => {});
|
|
915
919
|
if (turnStart && !client.closed) {
|
|
916
920
|
// Only the controller-owned target may be interrupted. Isolated reads
|
|
917
921
|
// never load or change the original conversation's policy defaults.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSceneReasoning } from '../codex-plugins/simy/hooks/scene-reasoning.mjs';
|
|
1
2
|
import { readCodexConversation } from './codex-app-server.js';
|
|
2
3
|
import { readClaudeConversation } from './claude-sessions.js';
|
|
3
4
|
import { boundConversation, boundProviderGoal, providerGoalEvidence, sanitizedText } from './conversation-context.js';
|
|
@@ -5,7 +6,7 @@ import { runStructuredProviderReview } from '../sqm/review-worker.js';
|
|
|
5
6
|
import { inspectProviderStatus } from '../desktop-status.js';
|
|
6
7
|
import { createHash } from 'node:crypto';
|
|
7
8
|
|
|
8
|
-
export const CONTINUATION_ANALYSIS_VERSION =
|
|
9
|
+
export const CONTINUATION_ANALYSIS_VERSION = 6;
|
|
9
10
|
const BLOCKER_TYPES = ['approval', 'missing_information', 'external_dependency', 'execution_failure'];
|
|
10
11
|
const STATES = ['continue', 'no_action', 'needs_user', 'waiting', 'uncertain'];
|
|
11
12
|
const string = maximum => ({ type: 'string', maxLength: maximum });
|
|
@@ -228,7 +229,7 @@ export async function analyzeContinuation(candidate, {
|
|
|
228
229
|
await beforeProviderDispatch();
|
|
229
230
|
if (signal?.aborted) return uncertain('Conversation analysis was cancelled.', 'cancelled');
|
|
230
231
|
result = await runProvider(candidate.provider, {
|
|
231
|
-
system: SYSTEM, prompt: `Classify this conversation evidence only (older history omitted: ${context.truncated}):\n${JSON.stringify(context.providerGoal ? { originalProviderGoal: context.providerGoal, messages: context.messages } : context.messages)}`, output_schema: OUTPUT_SCHEMA,
|
|
232
|
+
system: withSceneReasoning(SYSTEM), prompt: `Classify this conversation evidence only (older history omitted: ${context.truncated}):\n${JSON.stringify(context.providerGoal ? { originalProviderGoal: context.providerGoal, messages: context.messages } : context.messages)}`, output_schema: OUTPUT_SCHEMA,
|
|
232
233
|
}, { ...providerOptions, signal, readOnlyAnalysis: true, beforeDispatch: beforeProviderDispatch });
|
|
233
234
|
} catch (error) {
|
|
234
235
|
if (error?.code === 'desktop_access_required') return uncertain('SIMY organization access is required before checking this conversation.', 'desktop_access_required');
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withSceneReasoning } from '../codex-plugins/simy/hooks/scene-reasoning.mjs';
|
|
2
|
+
import { currentRecoveryIncident, unresolvedRecoveryContext } from '../codex-plugins/simy/hooks/recovery-incident.mjs';
|
|
1
3
|
import { recallPerspectives, perspectiveInstruction } from '../codex-plugins/simy/hooks/perspective-memory.mjs';
|
|
2
4
|
import { loadHypothesisKnowledge, hypothesisKnowledgeInstruction } from '../sqm/hypothesis-knowledge.js';
|
|
3
5
|
import { selectiveRuleReview, applicableRuleIds } from '../codex-plugins/simy/hooks/purpose-rule-policy.mjs';
|
|
@@ -40,12 +42,18 @@ export async function loadPurposeRuleSet() {
|
|
|
40
42
|
return { digest: createHash('sha256').update(JSON.stringify(sources)).digest('hex'), rules };
|
|
41
43
|
}
|
|
42
44
|
export async function buildPurposeInstruction(managed, { reviewer = false, cwd, webOrigin, allowNetwork = false, signal, knowledgeLoader = loadHypothesisKnowledge, authorizeKnowledgeNetwork, root: memoryRoot, record, memoryLoader = recallPerspectives } = {}) {
|
|
43
|
-
if (!managed?.purpose?.enabled) return
|
|
45
|
+
if (!managed?.purpose?.enabled) return withSceneReasoning();
|
|
44
46
|
const ruleSet = await loadPurposeRuleSet();
|
|
45
47
|
if (ruleSet.digest !== managed.purpose.ruleDigest) throw Object.assign(new Error('Purpose workflow changed; refresh its rule inventory before dispatch.'), { code: 'purpose_rules_changed' });
|
|
46
48
|
if (reviewer && selectiveRuleReview(managed)) return `${llmDecisionInstruction}\nIndependently verify the original acceptance criteria and applicable canonical obligations from actual evidence. Consult the inventory as needed after forming an LLM judgment; do not enumerate every ID or impose core controls as universal checklist gates. Report only rules actually evaluated; omitted inventory entries are not passed or not_applicable. Known applicable obligations ${JSON.stringify(applicableRuleIds(managed))} must be independently assessed and cannot be discarded by worker claims. Do not copy worker dispositions as proof. Failed, unavailable or missing evidence remains unverified with a concrete acquisition/repair finding. No unrelated rule coverage obligation is implied.\nruleSet: ${JSON.stringify(ruleSet.rules.map(rule => ({ ...rule, path: fileURLToPath(new URL(rule.file, root)) })))}`;
|
|
47
49
|
if (reviewer) return `${llmDecisionInstruction}\nCanonical rule inventory. Review applicability and evidence for EVERY ID, including HC-01 through HC-102; these controls are requirements, not proof that their mechanisms exist. Read the referenced sections and acceptance details as needed. User instructions retain priority. Do not impose unrelated development/release controls on other task types. Core controls ${CORE_CONTROL_IDS.join(', ')} apply to every autonomous task; inspect their actual bounded behavior, including conditional duties whose trigger did not occur, rather than marking the entire control not_applicable. Return rules:[{id,evidenceRefs:[{ref,target,observation}],status:"satisfied|failed|unverified|not_applicable",basis:"inspected evidence or specific applicability reason"}]. Keep basis concise with source locators; stored receipts are limited to 160 UTF-8 bytes and basisTruncated means re-read the original evidence. Never copy worker dispositions as independent proof. Missing capability is unverified and requires an acquisition/repair finding, not a human certification request.\nruleSet: ${JSON.stringify(ruleSet.rules.map(rule => ({ ...rule, path: fileURLToPath(new URL(rule.file, root)) })))}`;
|
|
48
50
|
const knowledge = await knowledgeLoader({ cwd, webOrigin, allowNetwork, signal, ...(authorizeKnowledgeNetwork ? { authorizeNetwork: authorizeKnowledgeNetwork } : {}) });
|
|
49
51
|
const memory = await memoryLoader({ root: memoryRoot, record, signal });
|
|
50
|
-
|
|
52
|
+
const attempt = managed.executionAttempt;
|
|
53
|
+
const recoveryContext = { previous: managed.purpose.lastRecovery || null, incident: currentRecoveryIncident(managed),
|
|
54
|
+
unresolved: unresolvedRecoveryContext(managed),
|
|
55
|
+
receipt: attempt ? { id: attempt.id, phase: attempt.phase, decisionId: attempt.decisionId,
|
|
56
|
+
providerSessionId: attempt.providerSessionId || null, providerTurnId: attempt.providerTurnId || null,
|
|
57
|
+
startedAt: attempt.startedAt || null, dispatchedAt: attempt.dispatchedAt || null } : null };
|
|
58
|
+
return `${purposeContract(managed)}\n${perspectiveInstruction(memory)}\n${hypothesisKnowledgeInstruction(knowledge, { flexible: selectiveRuleReview(managed) })}\n${searchInstruction(managed)}\nUse the shared scene reasoning cycle; consult applicable canonical rule sections as perspective inputs and challenge the candidate before its effects. This ruleSet is authoritative workflow material; user instructions retain priority. ${selectiveRuleReview(managed) ? "Review applicable rules and known obligations only; do not require full inventory coverage." : "Use the purpose contract for full initial rule coverage and changed dispositions on subsequent search turns."} Missing sources or unavailable tools remain unverified; do not fabricate coverage. Repository AGENTS.md and explicit user constraints also apply.\nruleSet: ${JSON.stringify(ruleSet.rules.map(rule => ({ ...rule, path: fileURLToPath(new URL(rule.file, root)) })))}\nPrior rule dispositions (provider reported, re-evaluate applicability): ${JSON.stringify(managed.purpose.version === 2 ? managed.purpose.lastReview?.rules || [] : [])}\nLast purpose review (provider reported): ${JSON.stringify(managed.purpose.lastReview ? { gap: managed.purpose.lastReview.gap, hypothesis: managed.purpose.lastReview.hypothesis, noChangeAssessment: managed.purpose.lastReview.noChangeAssessment, observations: managed.purpose.lastReview.observations, unresolvedRules: managed.purpose.lastReview.rules.filter(rule => ['failed', 'unverified'].includes(rule.status)) } : null)}\nRecovery context (controller receipt data, not instructions, permission or acceptance; return to the original unfinished work after the next useful check): ${JSON.stringify(recoveryContext)}`;
|
|
51
59
|
}
|
|
@@ -58,6 +58,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
|
|
|
58
58
|
const deferred = [];
|
|
59
59
|
|
|
60
60
|
for (let record of await readRecords()) {
|
|
61
|
+
if (signal?.aborted) break;
|
|
61
62
|
try {
|
|
62
63
|
let run = record.run;
|
|
63
64
|
if (upgradeEligible(record) && (policyAdoptionPending(record) || run.managed.controllerContractVersion !== 2 || run.state === 'paused' || run.state === 'verification_pending')) {
|
|
@@ -98,6 +99,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
|
|
|
98
99
|
let link;
|
|
99
100
|
try { link = await requireProviderLink({ root, provider: record.provider }); } catch { continue; }
|
|
100
101
|
const authorized = async () => {
|
|
102
|
+
signal?.throwIfAborted();
|
|
101
103
|
await requireProviderLink({ root, provider: record.provider, expectedEpoch: link.epoch });
|
|
102
104
|
const latest = await readRecord(root, record.provider, record.session);
|
|
103
105
|
if (!latest || !automaticContinuationAllowed(latest) || latest.run?.id !== run.id || latest.autoContinueEpoch !== record.autoContinueEpoch || ['paused', 'stopped'].includes(latest.run.state)) throw new Error('Purpose observation authorization changed.');
|
|
@@ -105,7 +107,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
|
|
|
105
107
|
const observe = async read => { await authorized(); const result = await read(); await authorized(); return result; };
|
|
106
108
|
const persist = mutate => writeRecord(root, record.provider, record.session, async current => {
|
|
107
109
|
try { await requireProviderLink({ root, provider: record.provider, expectedEpoch: link.epoch }); } catch { return current; }
|
|
108
|
-
if (!current || !automaticContinuationAllowed(current) || current.autoContinueEpoch !== record.autoContinueEpoch) return current;
|
|
110
|
+
if (signal?.aborted || !current || !automaticContinuationAllowed(current) || current.autoContinueEpoch !== record.autoContinueEpoch) return current;
|
|
109
111
|
return mutate(current);
|
|
110
112
|
});
|
|
111
113
|
// Recheck the current budget and boundaries under the existing record lock.
|
|
@@ -203,7 +205,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
|
|
|
203
205
|
if (current?.run?.id !== run.id || current.run.state !== 'blocked' || current.run.revision !== run.revision || current.run.updatedAt !== run.updatedAt) return current;
|
|
204
206
|
current.run.managed.authenticationWait.nextCheckAt = now + 60_000;
|
|
205
207
|
if (authentication === 'available') {
|
|
206
|
-
const queued = queuePurposeDiagnosis(current.run, 'Configured provider login is available again. Inspect the original purpose and any interrupted action before choosing authorized work.', `authentication-restored:${run.managed.authenticationWait.since}`, now);
|
|
208
|
+
const queued = queuePurposeDiagnosis(current.run, 'Configured provider login is available again. Inspect the original purpose and any interrupted action before choosing authorized work.', `authentication-restored:${run.managed.authenticationWait.since}`, now, 'authentication');
|
|
207
209
|
if (queued) delete current.run.managed.authenticationWait;
|
|
208
210
|
}
|
|
209
211
|
return current;
|
|
@@ -219,7 +221,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
|
|
|
219
221
|
current.run.managed.purpose.observationState = observed.state;
|
|
220
222
|
return current;
|
|
221
223
|
});
|
|
222
|
-
if (observed.state === 'idle') obstacle = { type: 'idle_without_checkpoint', detail: observed.turnId };
|
|
224
|
+
if (observed.state === 'idle' && (!observed.session || observed.session === record.session)) obstacle = { type: 'idle_without_checkpoint', detail: observed.turnId };
|
|
223
225
|
} else if (run.state === 'blocked' && ['missing_information', 'authorization', 'external_dependency'].includes(run.blocker)) {
|
|
224
226
|
if (run.blockerSource === 'provider' || run.managed.attempts?.at(-1)?.errorCode === 'provider_approval') {
|
|
225
227
|
if (now < (run.managed.purpose.lastObservedAt || 0) + 60_000) continue;
|
|
@@ -309,7 +311,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
|
|
|
309
311
|
if (['native_wait_updated', 'native_wait_diagnosis'].includes(obstacle.type) && !nativeCurrent(current)) return current;
|
|
310
312
|
if (searchEnabled(current.run.managed) && ['original_context_updated', 'native_wait_updated'].includes(obstacle.type)) current.run.managed.purpose.nextSearchAt = 0;
|
|
311
313
|
const queued = queuePurposeDiagnosis(current.run,
|
|
312
|
-
`Reassess the original purpose after ${obstacle.type}: ${obstacle.detail}.
|
|
314
|
+
`Reassess the original purpose after ${obstacle.type}: ${obstacle.detail}. Use the shared scene reasoning cycle to select the next useful check. Preserve unresolved approvals/dependencies; choose independent work or a safe alternative where possible, then return to the original unfinished work.`, key, now, obstacle.type);
|
|
313
315
|
if (queued && obstacle.type === 'external_dependency') {
|
|
314
316
|
const purpose = current.run.managed.purpose;
|
|
315
317
|
purpose.dependencyChecks = (purpose.dependencyChecks || 0) + 1;
|
|
@@ -318,6 +320,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
|
|
|
318
320
|
return current;
|
|
319
321
|
});
|
|
320
322
|
} catch (error) {
|
|
323
|
+
if (signal?.aborted) break;
|
|
321
324
|
if (!/^Delivery state (?:is busy; retry the command\.|lock owner is unavailable; inspect the lock before retrying\.)$/.test(error.message)) throw error;
|
|
322
325
|
// One unavailable record cannot prevent independent conversations from
|
|
323
326
|
// progressing. Never delete or reclaim an ambiguous writer lock.
|
package/src/sqm/claude-plugin.js
CHANGED
|
@@ -21,7 +21,12 @@ export async function claudePluginFiles() {
|
|
|
21
21
|
const content = await readFile(path.join(SOURCE, relative), "utf8");
|
|
22
22
|
files.set(relative, content.replaceAll("current Codex thread", "current Claude Code conversation"));
|
|
23
23
|
}
|
|
24
|
+
// Codex's local delivery skill is retired. Keep the separately maintained
|
|
25
|
+
// Claude workflow without shipping it back into Codex's skill inventory.
|
|
26
|
+
files.set("skills/delivery-loop/SKILL.md", await readFile(
|
|
27
|
+
new URL("../delivery-hooks/claude-workflow.md", import.meta.url), "utf8"));
|
|
24
28
|
files.set("hooks/delivery-loop.mjs", await readFile(path.join(SOURCE, "hooks/delivery-loop.mjs"), "utf8"));
|
|
29
|
+
files.set("hooks/recovery-incident.mjs", await readFile(path.join(SOURCE, "hooks/recovery-incident.mjs"), "utf8"));
|
|
25
30
|
files.set("hooks/minimal-sufficient.mjs", await readFile(path.join(SOURCE, "hooks/minimal-sufficient.mjs"), "utf8"));
|
|
26
31
|
files.set("hooks/outcome-progress.mjs", await readFile(path.join(SOURCE, "hooks/outcome-progress.mjs"), "utf8"));
|
|
27
32
|
files.set("hooks/continuation-policy.mjs", await readFile(path.join(SOURCE, "hooks/continuation-policy.mjs"), "utf8"));
|
|
@@ -34,6 +39,7 @@ export async function claudePluginFiles() {
|
|
|
34
39
|
files.set("hooks/completion-binding.mjs", await readFile(path.join(SOURCE, "hooks/completion-binding.mjs"), "utf8"));
|
|
35
40
|
files.set("hooks/consistency-handoff.mjs", await readFile(path.join(SOURCE, "hooks/consistency-handoff.mjs"), "utf8"));
|
|
36
41
|
files.set("hooks/purpose-evidence.mjs", await readFile(path.join(SOURCE, "hooks/purpose-evidence.mjs"), "utf8"));
|
|
42
|
+
files.set("hooks/scene-reasoning.mjs", await readFile(path.join(SOURCE, "hooks/scene-reasoning.mjs"), "utf8"));
|
|
37
43
|
files.set("hooks/purpose-cycle.mjs", await readFile(path.join(SOURCE, "hooks/purpose-cycle.mjs"), "utf8"));
|
|
38
44
|
files.set("hooks/purpose-rule-policy.mjs", await readFile(path.join(SOURCE, "hooks/purpose-rule-policy.mjs"), "utf8"));
|
|
39
45
|
files.set("hooks/acceptance-policy.mjs", await readFile(path.join(SOURCE, "hooks/acceptance-policy.mjs"), "utf8"));
|
package/src/sqm/local-tests.js
CHANGED
|
@@ -8,6 +8,7 @@ import path from 'node:path';
|
|
|
8
8
|
// This catalog is shipped with the CLI; a repository cannot supply shell commands.
|
|
9
9
|
const profiles = {
|
|
10
10
|
'delivery-loop': [
|
|
11
|
+
{ id: 'autonomous-recovery', files: ['test/autonomous-recovery.test.js', 'test/scene-reasoning.test.js', 'test/supervisor-watchdog.test.js'] },
|
|
11
12
|
{ id: 'perspective-memory', files: ['test/perspective-memory.test.js'] },
|
|
12
13
|
{ id: 'optional-reasoning', files: ['test/purpose-selective-rules.test.js', 'test/purpose-evidence.test.js', 'test/purpose-rule-review.test.js'] },
|
|
13
14
|
// This connected controller group now contains over 200 cases. Keep a
|
package/src/sqm/review-worker.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSceneReasoning } from '../codex-plugins/simy/hooks/scene-reasoning.mjs';
|
|
1
2
|
import { requireProviderSubscription } from './subscription-auth.js';
|
|
2
3
|
import { signalExecutorProcess } from "../runner.js";
|
|
3
4
|
import spawn from "cross-spawn";
|
|
@@ -398,6 +399,8 @@ export function runStructuredCodexReview(request, options) {
|
|
|
398
399
|
}
|
|
399
400
|
|
|
400
401
|
export async function runStructuredProviderReview(provider, request, options = {}) {
|
|
402
|
+
// Keep malformed requests invalid; the reasoning policy must not fill missing authority or inputs.
|
|
403
|
+
if (typeof request?.system === 'string' && request.system.trim()) request = { ...request, system: withSceneReasoning(request.system) };
|
|
401
404
|
const fallbackProviders = options.enableEngineFallback === true && options.providerReportingSupported === true && options.readOnlyAnalysis !== false && Array.isArray(options.allowedFallbackProviders)
|
|
402
405
|
? [...new Set(options.allowedFallbackProviders.filter(candidate => ["codex", "claude"].includes(candidate) && candidate !== provider))] : [];
|
|
403
406
|
if (!fallbackProviders.length) return runSingleProviderReview(provider, request, options);
|
|
@@ -58,7 +58,7 @@ export async function prepareDaemonWorkflows({
|
|
|
58
58
|
try {
|
|
59
59
|
const installed = await install();
|
|
60
60
|
logger(
|
|
61
|
-
"SIMY workflows installed. In Codex, restart, then find and install SIMY in Plugins. In Claude Code, add the SIMY marketplace and install simy@simy-local before using /simy:sqm-loop or /simy:pr-review. For Delivery Loop, review and enable SIMY hooks in /hooks, then request /simy:delivery-loop for each task. Plugin installation does not grant hook trust.",
|
|
61
|
+
"SIMY workflows installed. In Codex, restart, then find and install SIMY in Plugins for SQM and PR review. For the Codex Agentic Loop, run simy in a terminal and select Codex. In Claude Code, add the SIMY marketplace and install simy@simy-local before using /simy:sqm-loop or /simy:pr-review. For Claude Code Delivery Loop, review and enable SIMY hooks in /hooks, then request /simy:delivery-loop for each task. Plugin installation does not grant hook trust.",
|
|
62
62
|
);
|
|
63
63
|
if (installed.claude_plugin?.marketplace_root) {
|
|
64
64
|
logger(`Claude Code: claude plugin marketplace add ${JSON.stringify(installed.claude_plugin.marketplace_root)}`);
|
/package/src/{codex-plugins/simy/skills/delivery-loop/SKILL.md → delivery-hooks/claude-workflow.md}
RENAMED
|
File without changes
|