sneakoscope 4.8.4 → 4.8.6
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 +3 -3
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -0
- package/dist/core/agents/agent-central-ledger.js +10 -5
- package/dist/core/agents/agent-orchestrator.js +22 -1
- package/dist/core/agents/agent-runner-codex-exec.js +24 -0
- package/dist/core/agents/fast-mode-policy.js +3 -4
- package/dist/core/agents/native-cli-session-proof.js +1 -1
- package/dist/core/agents/native-cli-worker.js +73 -113
- package/dist/core/agents/worker-pane-communication-contract.js +2 -3
- package/dist/core/codex/codex-cli-syntax-builder.js +2 -0
- package/dist/core/codex-app/codex-init-deep.js +10 -3
- package/dist/core/codex-control/codex-reliability-shield.js +36 -17
- package/dist/core/codex-control/codex-sdk-adapter.js +2 -2
- package/dist/core/codex-control/codex-sdk-config-policy.js +4 -0
- package/dist/core/codex-control/codex-task-runner.js +23 -11
- package/dist/core/codex-native/core-skill-manifest.js +2 -1
- package/dist/core/commands/gates-command.js +58 -0
- package/dist/core/commands/release-command.js +5 -5
- package/dist/core/commands/stop-gate-command.js +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime/team-digest.js +86 -0
- package/dist/core/hooks-runtime.js +1 -82
- package/dist/core/init/skills.js +395 -0
- package/dist/core/init.js +2 -376
- package/dist/core/lean-engineering-policy.js +2 -1
- package/dist/core/naruto/naruto-active-pool.js +36 -7
- package/dist/core/naruto/naruto-real-worker-child.js +67 -11
- package/dist/core/permission-gates.js +45 -19
- package/dist/core/pipeline-internals/runtime-gates.js +26 -45
- package/dist/core/ppt/artifacts.js +19 -0
- package/dist/core/ppt/html.js +96 -0
- package/dist/core/ppt/style-tokens.js +248 -0
- package/dist/core/ppt.js +6 -352
- package/dist/core/qa-loop/{qa-contract-v2.js → qa-contract.js} +1 -1
- package/dist/core/qa-loop/{qa-gate-v2.js → qa-gate.js} +1 -1
- package/dist/core/qa-loop/qa-runtime-artifacts.js +2 -2
- package/dist/core/qa-loop.js +11 -4
- package/dist/core/recallpulse/policy.js +226 -0
- package/dist/core/recallpulse.js +2 -225
- package/dist/core/release/release-gate-dag.js +6 -2
- package/dist/core/research/mock-result.js +406 -0
- package/dist/core/research/prompt.js +15 -0
- package/dist/core/research.js +2 -402
- package/dist/core/routes/constants.js +48 -0
- package/dist/core/routes/design-policy.js +37 -0
- package/dist/core/routes/evidence.js +77 -0
- package/dist/core/routes/ppt-policy.js +26 -0
- package/dist/core/routes.js +8 -179
- package/dist/core/safety/mutation-ledger.js +2 -0
- package/dist/core/safety/ssot-guard.js +2 -2
- package/dist/core/stop-gate/stop-gate-check.js +10 -17
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-pane-renderer.js +15 -8
- package/dist/core/zellij/zellij-slot-telemetry.js +28 -12
- package/dist/scripts/check-pipeline-budget.js +1 -7
- package/dist/scripts/check-pipeline-runtime.js +1 -8
- package/dist/scripts/codex-control-all-pipelines-check.js +5 -14
- package/dist/scripts/codex-sdk-all-pipelines-check.js +5 -9
- package/dist/scripts/fast-codex-service-tier-proof-check.js +2 -2
- package/dist/scripts/gate-policy-audit-check.js +70 -0
- package/dist/scripts/official-docs-compat-report.js +2 -2
- package/dist/scripts/product-design-plugin-routing-check.js +10 -5
- package/dist/scripts/qa-loop-surface-router-check.js +2 -2
- package/dist/scripts/release-check-dynamic-execute.js +32 -35
- package/dist/scripts/release-check-dynamic.js +19 -28
- package/dist/scripts/release-check-stamp.js +2 -2
- package/dist/scripts/release-dag-full-coverage-check.js +57 -394
- package/dist/scripts/release-dynamic-performance-check.js +18 -19
- package/dist/scripts/release-dynamic-presets-check.js +2 -2
- package/dist/scripts/release-gate-dag-runner.js +3 -1
- package/dist/scripts/release-gate-existence-audit.js +59 -91
- package/dist/scripts/release-gate-planner.js +43 -23
- package/dist/scripts/release-metadata-1-19-check.js +100 -12
- package/dist/scripts/worker-pane-communication-contract-check.js +1 -2
- package/dist/scripts/zellij-slot-pane-renderer-check.js +3 -2
- package/package.json +13 -965
- package/dist/core/pipeline-runtime.js +0 -2
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
import { ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { appendJsonlBounded, ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
5
4
|
import { runCodexTask } from '../codex-control/codex-control-plane.js';
|
|
6
5
|
import { CODEX_AGENT_WORKER_RESULT_SCHEMA_ID, codexAgentWorkerResultSchema } from '../codex-control/schemas/agent-worker-result.schema.js';
|
|
7
6
|
async function main() {
|
|
@@ -12,12 +11,17 @@ async function main() {
|
|
|
12
11
|
if (!intake?.result_path || !intake?.heartbeat_path || !intake?.item?.id) {
|
|
13
12
|
throw new Error('naruto worker intake is invalid');
|
|
14
13
|
}
|
|
15
|
-
|
|
14
|
+
const startedAt = Date.now();
|
|
15
|
+
const maxRuntimeMs = normalizeMaxRuntimeMs(intake.max_runtime_ms);
|
|
16
|
+
let finished = false;
|
|
17
|
+
const heartbeatTimer = startDeadmanHeartbeat({ intake, startedAt, maxRuntimeMs, finished: () => finished });
|
|
18
|
+
await appendJsonlBounded(intake.heartbeat_path, {
|
|
16
19
|
schema: 'sks.naruto-actual-worker-heartbeat.v1',
|
|
17
20
|
ts: nowIso(),
|
|
18
21
|
item_id: intake.item.id,
|
|
19
|
-
status: 'running'
|
|
20
|
-
|
|
22
|
+
status: 'running',
|
|
23
|
+
progress: null
|
|
24
|
+
}, 2 * 1024 * 1024);
|
|
21
25
|
if (intake.backend === 'fake')
|
|
22
26
|
process.env.SKS_CODEX_SDK_FAKE = '1';
|
|
23
27
|
const controlRoot = path.join(path.dirname(intake.result_path), 'codex-control');
|
|
@@ -76,14 +80,19 @@ async function main() {
|
|
|
76
80
|
changed_files: Array.isArray(workerResult?.changed_files) ? workerResult.changed_files : [],
|
|
77
81
|
blockers
|
|
78
82
|
});
|
|
79
|
-
|
|
83
|
+
finished = true;
|
|
84
|
+
clearInterval(heartbeatTimer);
|
|
85
|
+
await appendJsonlBounded(intake.heartbeat_path, {
|
|
80
86
|
schema: 'sks.naruto-actual-worker-heartbeat.v1',
|
|
81
87
|
ts: nowIso(),
|
|
82
88
|
item_id: intake.item.id,
|
|
83
|
-
status: blockers.length ? 'blocked' : 'done'
|
|
84
|
-
|
|
89
|
+
status: blockers.length ? 'blocked' : 'done',
|
|
90
|
+
progress: { done: 1, total: 1 }
|
|
91
|
+
}, 2 * 1024 * 1024);
|
|
85
92
|
}
|
|
86
93
|
catch (err) {
|
|
94
|
+
finished = true;
|
|
95
|
+
clearInterval(heartbeatTimer);
|
|
87
96
|
await writeJsonAtomic(intake.result_path, {
|
|
88
97
|
schema: 'sks.naruto-actual-worker-result.v1',
|
|
89
98
|
ok: false,
|
|
@@ -94,15 +103,62 @@ async function main() {
|
|
|
94
103
|
worktree_path: intake.worktree_path,
|
|
95
104
|
blockers: [`naruto_actual_worker_control_plane_exception:${err?.message || String(err)}`]
|
|
96
105
|
});
|
|
97
|
-
await
|
|
106
|
+
await appendJsonlBounded(intake.heartbeat_path, {
|
|
98
107
|
schema: 'sks.naruto-actual-worker-heartbeat.v1',
|
|
99
108
|
ts: nowIso(),
|
|
100
109
|
item_id: intake.item.id,
|
|
101
|
-
status: 'blocked'
|
|
102
|
-
|
|
110
|
+
status: 'blocked',
|
|
111
|
+
progress: null
|
|
112
|
+
}, 2 * 1024 * 1024);
|
|
103
113
|
throw err;
|
|
104
114
|
}
|
|
105
115
|
}
|
|
116
|
+
function startDeadmanHeartbeat(input) {
|
|
117
|
+
const parsed = Number(process.env.SKS_ZELLIJ_WORKER_PROGRESS_MS || 10000);
|
|
118
|
+
const intervalMs = Math.max(1000, Number.isFinite(parsed) ? Math.floor(parsed) : 10000);
|
|
119
|
+
return setInterval(async () => {
|
|
120
|
+
if (input.finished())
|
|
121
|
+
return;
|
|
122
|
+
const elapsedMs = Date.now() - input.startedAt;
|
|
123
|
+
if (elapsedMs > input.maxRuntimeMs) {
|
|
124
|
+
await writeJsonAtomic(input.intake.result_path, {
|
|
125
|
+
schema: 'sks.naruto-actual-worker-result.v1',
|
|
126
|
+
ok: false,
|
|
127
|
+
generated_at: nowIso(),
|
|
128
|
+
item_id: input.intake.item.id,
|
|
129
|
+
placement: input.intake.placement,
|
|
130
|
+
backend: input.intake.backend,
|
|
131
|
+
worktree_path: input.intake.worktree_path,
|
|
132
|
+
status: 'timed_out',
|
|
133
|
+
blockers: ['naruto_worker_hard_timeout']
|
|
134
|
+
}).catch(() => undefined);
|
|
135
|
+
await appendJsonlBounded(input.intake.heartbeat_path, {
|
|
136
|
+
schema: 'sks.naruto-actual-worker-heartbeat.v1',
|
|
137
|
+
ts: nowIso(),
|
|
138
|
+
item_id: input.intake.item.id,
|
|
139
|
+
status: 'blocked',
|
|
140
|
+
elapsed_ms: elapsedMs,
|
|
141
|
+
progress: null
|
|
142
|
+
}, 2 * 1024 * 1024).catch(() => undefined);
|
|
143
|
+
process.exit(124);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await appendJsonlBounded(input.intake.heartbeat_path, {
|
|
147
|
+
schema: 'sks.naruto-actual-worker-heartbeat.v1',
|
|
148
|
+
ts: nowIso(),
|
|
149
|
+
item_id: input.intake.item.id,
|
|
150
|
+
status: 'running',
|
|
151
|
+
elapsed_ms: elapsedMs,
|
|
152
|
+
progress: null
|
|
153
|
+
}, 2 * 1024 * 1024).catch(() => undefined);
|
|
154
|
+
}, intervalMs);
|
|
155
|
+
}
|
|
156
|
+
function normalizeMaxRuntimeMs(value) {
|
|
157
|
+
const parsed = Number(value);
|
|
158
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
159
|
+
return Math.max(1000, Math.min(Math.floor(parsed), 24 * 60 * 60 * 1000));
|
|
160
|
+
return 10 * 60 * 1000;
|
|
161
|
+
}
|
|
106
162
|
function backendPreference(value) {
|
|
107
163
|
const backend = String(value || '');
|
|
108
164
|
if (backend === 'ollama' || backend === 'local-llm')
|
|
@@ -86,29 +86,55 @@ export function madSksCatastrophicDbReasons(cls = {}) {
|
|
|
86
86
|
blocked.push('dangerous_project_or_branch_management');
|
|
87
87
|
return [...new Set(blocked)];
|
|
88
88
|
}
|
|
89
|
+
export function evaluatePermission({ action, scope, profile, classification, active = false, env = process.env } = {}) {
|
|
90
|
+
const selectedProfile = normalizePermissionProfile(profile, env);
|
|
91
|
+
const actionId = String(action || scope || '').trim();
|
|
92
|
+
const blocked = new Set((selectedProfile.blocked || []).map(String));
|
|
93
|
+
const allowed = new Set((selectedProfile.allowed || []).map(String));
|
|
94
|
+
const catastrophic = madSksCatastrophicDbReasons(classification || {});
|
|
95
|
+
const blockedCategories = [
|
|
96
|
+
...(actionId && blocked.has(actionId) ? [actionId] : []),
|
|
97
|
+
...catastrophic
|
|
98
|
+
];
|
|
99
|
+
if (!active) {
|
|
100
|
+
return { matched: false, active: false, allowed: false, action: 'ignore', reasons: ['permission_profile_inactive'], blocked_categories: [], profile: permissionGateSummary(selectedProfile) };
|
|
101
|
+
}
|
|
102
|
+
if (blockedCategories.length) {
|
|
103
|
+
return { matched: true, active: true, allowed: false, action: 'block', reasons: ['permission_profile_blocked_action'], blocked_categories: [...new Set(blockedCategories)], profile: permissionGateSummary(selectedProfile) };
|
|
104
|
+
}
|
|
105
|
+
if (actionId && allowed.size > 0 && !allowed.has(actionId)) {
|
|
106
|
+
return { matched: true, active: true, allowed: false, action: 'block', reasons: ['permission_profile_action_not_allowed'], blocked_categories: [actionId], profile: permissionGateSummary(selectedProfile) };
|
|
107
|
+
}
|
|
108
|
+
return { matched: true, active: true, allowed: true, action: 'allow', reasons: ['permission_profile_scoped_allow'], blocked_categories: [], profile: permissionGateSummary(selectedProfile) };
|
|
109
|
+
}
|
|
89
110
|
export function evaluateMadSksPermissionGate({ classification, active = false } = {}) {
|
|
90
111
|
const cls = classification || { level: 'none', reasons: [] };
|
|
91
|
-
if (!active || !['write', 'destructive'].includes(cls.level))
|
|
92
|
-
return { matched: false, active: Boolean(active), profile: permissionGateSummary() };
|
|
93
|
-
const catastrophic = madSksCatastrophicDbReasons(cls);
|
|
94
|
-
if (catastrophic.length) {
|
|
95
|
-
return {
|
|
96
|
-
matched: true,
|
|
97
|
-
active: true,
|
|
98
|
-
allowed: false,
|
|
99
|
-
action: 'block',
|
|
100
|
-
reasons: ['mad_sks_catastrophic_db_operation_blocked'],
|
|
101
|
-
blocked_categories: catastrophic,
|
|
102
|
-
profile: permissionGateSummary()
|
|
103
|
-
};
|
|
112
|
+
if (!active || !['write', 'destructive'].includes(cls.level)) {
|
|
113
|
+
return { matched: false, active: Boolean(active), allowed: false, action: 'ignore', reasons: [], blocked_categories: [], profile: permissionGateSummary() };
|
|
104
114
|
}
|
|
115
|
+
const decision = evaluatePermission({ action: null, profile: MAD_SKS_PERMISSION_PROFILE, classification: cls, active: true });
|
|
105
116
|
return {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
allowed: true,
|
|
109
|
-
action: 'allow',
|
|
110
|
-
reasons: ['mad_sks_scoped_live_full_access_active'],
|
|
111
|
-
profile: permissionGateSummary()
|
|
117
|
+
...decision,
|
|
118
|
+
reasons: decision.allowed ? ['mad_sks_scoped_live_full_access_active'] : ['mad_sks_catastrophic_db_operation_blocked']
|
|
112
119
|
};
|
|
113
120
|
}
|
|
121
|
+
function normalizePermissionProfile(profile, env = process.env) {
|
|
122
|
+
if (profile?.allowed && profile?.blocked)
|
|
123
|
+
return profile;
|
|
124
|
+
const configured = loadConfiguredPermissionProfiles(env);
|
|
125
|
+
const id = typeof profile === 'string' ? profile : profile?.id;
|
|
126
|
+
return configured.find((row) => row.id === id) || MAD_SKS_PERMISSION_PROFILE;
|
|
127
|
+
}
|
|
128
|
+
function loadConfiguredPermissionProfiles(env = process.env) {
|
|
129
|
+
const raw = String(env.SKS_PERMISSION_PROFILES_JSON || '').trim();
|
|
130
|
+
if (!raw)
|
|
131
|
+
return [];
|
|
132
|
+
try {
|
|
133
|
+
const parsed = JSON.parse(raw);
|
|
134
|
+
return Array.isArray(parsed?.profiles) ? parsed.profiles : Array.isArray(parsed) ? parsed : [];
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
114
140
|
//# sourceMappingURL=permission-gates.js.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fsp from 'node:fs/promises';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import { appendJsonl, exists, nowIso, readJson, readText, writeJsonAtomic } from '../fsx.js';
|
|
4
3
|
import { containsUserQuestion, noQuestionContinuationReason } from '../no-question-guard.js';
|
|
@@ -72,42 +71,9 @@ async function staleReflectionReasons(root, state = {}, gate = {}) {
|
|
|
72
71
|
const id = state?.mission_id;
|
|
73
72
|
if (!id)
|
|
74
73
|
return [];
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (await fileUpdatedAfter(path.join(dir, file), created))
|
|
79
|
-
missing.push(`${file}:updated_after_reflection`);
|
|
80
|
-
}
|
|
81
|
-
const transcript = await readText(path.join(dir, 'team-transcript.jsonl'), '');
|
|
82
|
-
const newerWorkEvent = transcript
|
|
83
|
-
.split(/\n/)
|
|
84
|
-
.filter(Boolean)
|
|
85
|
-
.map((line) => {
|
|
86
|
-
try {
|
|
87
|
-
return JSON.parse(line);
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
.find((event) => {
|
|
94
|
-
const ts = Date.parse(event?.ts || '');
|
|
95
|
-
if (!Number.isFinite(ts) || ts <= created)
|
|
96
|
-
return false;
|
|
97
|
-
return !/^(REFLECTION|HONEST|TEAM_CLEANUP)$/i.test(String(event?.phase || ''));
|
|
98
|
-
});
|
|
99
|
-
if (newerWorkEvent)
|
|
100
|
-
missing.push('team-transcript.jsonl:work_after_reflection');
|
|
101
|
-
return missing;
|
|
102
|
-
}
|
|
103
|
-
async function fileUpdatedAfter(file, timeMs) {
|
|
104
|
-
try {
|
|
105
|
-
const stat = await fsp.stat(file);
|
|
106
|
-
return stat.mtimeMs > timeMs + 1000;
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
74
|
+
if (state.reflection_invalidation_required !== true)
|
|
75
|
+
return [];
|
|
76
|
+
return ['reflection_invalidation_required'];
|
|
111
77
|
}
|
|
112
78
|
function reflectionStopReason(state = {}, status = {}) {
|
|
113
79
|
const id = state?.mission_id || 'latest';
|
|
@@ -227,7 +193,7 @@ export async function evaluateStop(root, state, payload, opts = {}) {
|
|
|
227
193
|
if (gate.ok) {
|
|
228
194
|
const reflection = await reflectionGateStatus(root, state);
|
|
229
195
|
if (!reflection.ok)
|
|
230
|
-
|
|
196
|
+
await appendHonestModeNote(root, state, `reflection stale: ${(reflection.missing || []).join(', ')}`);
|
|
231
197
|
return { continue: true };
|
|
232
198
|
}
|
|
233
199
|
const missing = gate.missing?.length ? ` Missing gate fields: ${gate.missing.join(', ')}.` : '';
|
|
@@ -250,7 +216,7 @@ export async function evaluateStop(root, state, payload, opts = {}) {
|
|
|
250
216
|
return { continue: true, systemMessage: `SKS: canonical stop-gate passed at ${stopCheck.gate_path}` };
|
|
251
217
|
}
|
|
252
218
|
else if (stopCheck.action === 'hard_blocked') {
|
|
253
|
-
return {
|
|
219
|
+
return { decision: 'block', reason: stopCheck.feedback, action: 'hard_blocked', gate: stopCheck.gate_path };
|
|
254
220
|
}
|
|
255
221
|
else {
|
|
256
222
|
const missing = stopCheck.diagnostics.missing_fields?.length ? ` Missing gate fields: ${stopCheck.diagnostics.missing_fields.join(', ')}.` : '';
|
|
@@ -304,7 +270,7 @@ async function complianceBlock(root, state = {}, reason = '', detail = {}) {
|
|
|
304
270
|
const normalized = normalizeComplianceReason(reason);
|
|
305
271
|
const previous = await readJson(guardPath, {});
|
|
306
272
|
const count = previous.normalized_reason === normalized ? Number(previous.repeat_count || 0) + 1 : 1;
|
|
307
|
-
const limit = complianceLoopLimit();
|
|
273
|
+
const limit = complianceLoopLimit(detail.gate || state.stop_gate || '');
|
|
308
274
|
const record = {
|
|
309
275
|
schema_version: 1,
|
|
310
276
|
updated_at: nowIso(),
|
|
@@ -323,7 +289,9 @@ async function complianceBlock(root, state = {}, reason = '', detail = {}) {
|
|
|
323
289
|
if (!record.tripped)
|
|
324
290
|
return { decision: 'block', reason, gate: detail.gate || state.stop_gate || null, missing: Array.isArray(detail.missing) ? detail.missing : [] };
|
|
325
291
|
await writeJsonAtomic(path.join(dir, HARD_BLOCKER_ARTIFACT), {
|
|
326
|
-
|
|
292
|
+
schema: 'sks.hard-blocker.v1',
|
|
293
|
+
passed: false,
|
|
294
|
+
status: 'hard_blocked',
|
|
327
295
|
created_at: nowIso(),
|
|
328
296
|
reason: 'compliance_loop_guard_tripped',
|
|
329
297
|
route: record.route,
|
|
@@ -337,10 +305,11 @@ async function complianceBlock(root, state = {}, reason = '', detail = {}) {
|
|
|
337
305
|
]
|
|
338
306
|
});
|
|
339
307
|
await appendJsonl(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'pipeline.compliance_loop_guard.tripped', gate: record.gate, repeat_count: count, limit });
|
|
340
|
-
return null;
|
|
308
|
+
return { decision: 'escalate', reason, gate: detail.gate || state.stop_gate || null, repeat_count: count, message: '동일 사유가 반복됩니다. 사용자 개입이 필요합니다.' };
|
|
341
309
|
}
|
|
342
|
-
function complianceLoopLimit() {
|
|
343
|
-
const
|
|
310
|
+
function complianceLoopLimit(gate = '') {
|
|
311
|
+
const gateKey = String(gate || '').toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
|
|
312
|
+
const raw = Number.parseInt(String((gateKey && process.env[`SKS_COMPLIANCE_LOOP_LIMIT_${gateKey}`]) || process.env.SKS_COMPLIANCE_LOOP_LIMIT || ''), 10);
|
|
344
313
|
if (!Number.isFinite(raw))
|
|
345
314
|
return DEFAULT_COMPLIANCE_LOOP_LIMIT;
|
|
346
315
|
return Math.max(1, Math.min(20, raw));
|
|
@@ -349,7 +318,6 @@ function normalizeComplianceReason(reason = '') {
|
|
|
349
318
|
return String(reason || '')
|
|
350
319
|
.replace(/\bM-\d{8}-\d{6}-[a-z0-9]+\b/gi, 'M-*')
|
|
351
320
|
.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z/g, 'TIMESTAMP')
|
|
352
|
-
.replace(/\d+/g, 'N')
|
|
353
321
|
.replace(/\s+/g, ' ')
|
|
354
322
|
.trim()
|
|
355
323
|
.slice(0, 1200);
|
|
@@ -386,8 +354,21 @@ async function passedHardBlocker(root, state) {
|
|
|
386
354
|
const blocker = await readJson(path.join(missionDir(root, state.mission_id), file), null);
|
|
387
355
|
if (!blocker)
|
|
388
356
|
return { ok: false };
|
|
357
|
+
if (String(blocker.status || '') === 'hard_blocked')
|
|
358
|
+
return { ok: false, file, missing: ['hard_blocked'] };
|
|
389
359
|
return { ok: blocker.passed === true && String(blocker.reason || '').trim() && Array.isArray(blocker.evidence) && blocker.evidence.length > 0, file };
|
|
390
360
|
}
|
|
361
|
+
async function appendHonestModeNote(root, state = {}, message) {
|
|
362
|
+
if (!state?.mission_id)
|
|
363
|
+
return;
|
|
364
|
+
const dir = missionDir(root, state.mission_id);
|
|
365
|
+
await appendJsonl(path.join(dir, 'honest-mode-notes.jsonl'), {
|
|
366
|
+
ts: nowIso(),
|
|
367
|
+
type: 'honest_mode_note',
|
|
368
|
+
route: state.route_command || state.route || state.mode || null,
|
|
369
|
+
message
|
|
370
|
+
});
|
|
371
|
+
}
|
|
391
372
|
function missingRequiredGateFields(file, state, gate = {}) {
|
|
392
373
|
const mode = String(state?.mode || '').toUpperCase();
|
|
393
374
|
if (file === 'team-gate.json' || mode === 'TEAM') {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const PPT_AUDIENCE_STRATEGY_ARTIFACT = 'ppt-audience-strategy.json';
|
|
2
|
+
export const PPT_GATE_ARTIFACT = 'ppt-gate.json';
|
|
3
|
+
export const PPT_SOURCE_LEDGER_ARTIFACT = 'ppt-source-ledger.json';
|
|
4
|
+
export const PPT_FACT_LEDGER_ARTIFACT = 'ppt-fact-ledger.json';
|
|
5
|
+
export const PPT_STORYBOARD_ARTIFACT = 'ppt-storyboard.json';
|
|
6
|
+
export const PPT_STYLE_TOKENS_ARTIFACT = 'ppt-style-tokens.json';
|
|
7
|
+
export const PPT_IMAGE_ASSET_LEDGER_ARTIFACT = 'ppt-image-asset-ledger.json';
|
|
8
|
+
export const PPT_ASSET_DIR = 'assets';
|
|
9
|
+
export const PPT_REVIEW_POLICY_ARTIFACT = 'ppt-review-policy.json';
|
|
10
|
+
export const PPT_REVIEW_LEDGER_ARTIFACT = 'ppt-review-ledger.json';
|
|
11
|
+
export const PPT_ITERATION_REPORT_ARTIFACT = 'ppt-iteration-report.json';
|
|
12
|
+
export const PPT_SOURCE_HTML_DIR = 'source-html';
|
|
13
|
+
export const PPT_HTML_ARTIFACT = `${PPT_SOURCE_HTML_DIR}/artifact.html`;
|
|
14
|
+
export const PPT_PDF_ARTIFACT = 'artifact.pdf';
|
|
15
|
+
export const PPT_RENDER_REPORT_ARTIFACT = 'ppt-render-report.json';
|
|
16
|
+
export const PPT_CLEANUP_REPORT_ARTIFACT = 'ppt-cleanup-report.json';
|
|
17
|
+
export const PPT_PARALLEL_REPORT_ARTIFACT = 'ppt-parallel-report.json';
|
|
18
|
+
export const PPT_TEMP_DIR = 'ppt-tmp';
|
|
19
|
+
//# sourceMappingURL=artifacts.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
function escapeHtml(value) {
|
|
2
|
+
return String(value ?? '')
|
|
3
|
+
.replace(/&/g, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"')
|
|
7
|
+
.replace(/'/g, ''');
|
|
8
|
+
}
|
|
9
|
+
function jsonScript(value) {
|
|
10
|
+
return JSON.stringify(value).replace(/</g, '\\u003c');
|
|
11
|
+
}
|
|
12
|
+
export function buildPptHtml({ contract = {}, audience, sourceLedger, factLedger, imageAssetLedger, reviewPolicy, storyboard, styleTokens }) {
|
|
13
|
+
const title = escapeHtml(storyboard.title);
|
|
14
|
+
const referenceName = escapeHtml(styleTokens.design_policy?.design_reference_selection?.primary?.name || 'selected design reference');
|
|
15
|
+
const audienceRaw = escapeHtml(audience?.audience_profile?.raw || 'Audience context');
|
|
16
|
+
const stpRaw = escapeHtml(audience?.stp?.raw || 'STP context');
|
|
17
|
+
const decisionRaw = escapeHtml(audience?.decision_context?.raw || storyboard.thesis || '');
|
|
18
|
+
const surfaceRule = styleTokens.layout?.treatment === 'shadow_as_border_minimal_depth'
|
|
19
|
+
? `box-shadow: 0 0 0 1px ${styleTokens.color.rule}; border: 0;`
|
|
20
|
+
: `border: 1px solid ${styleTokens.color.rule}; box-shadow: none;`;
|
|
21
|
+
const css = `@page { size: 16in 9in; margin: 0; }
|
|
22
|
+
* { box-sizing: border-box; }
|
|
23
|
+
body { margin: 0; background: ${styleTokens.color.bg}; color: ${styleTokens.color.text}; font-family: ${styleTokens.typography.font_stack}; }
|
|
24
|
+
.page { width: 100vw; min-height: 100vh; page-break-after: always; padding: 64px 88px 54px; display: grid; grid-template-rows: auto 1fr auto; gap: 34px; }
|
|
25
|
+
.topline { display: grid; grid-template-columns: 1fr auto; align-items: end; border-bottom: 1px solid ${styleTokens.color.rule}; padding-bottom: 14px; }
|
|
26
|
+
.kicker { color: ${styleTokens.color.primary}; font-size: ${styleTokens.typography.caption_px}px; font-weight: 600; letter-spacing: 0; text-transform: uppercase; }
|
|
27
|
+
.reference { color: ${styleTokens.color.muted}; font-size: ${styleTokens.typography.caption_px}px; letter-spacing: 0; }
|
|
28
|
+
.content { display: grid; grid-template-columns: minmax(0, 6fr) minmax(320px, 4fr); gap: 58px; align-items: center; }
|
|
29
|
+
h1 { margin: 0; font-size: ${styleTokens.typography.display_px}px; line-height: 1.08; letter-spacing: 0; max-width: 1040px; font-weight: 600; }
|
|
30
|
+
p { margin: 0; color: ${styleTokens.color.muted}; font-size: ${styleTokens.typography.body_px}px; line-height: ${styleTokens.typography.line_height}; max-width: 920px; }
|
|
31
|
+
.claim { display: grid; gap: 26px; }
|
|
32
|
+
.evidence { ${surfaceRule} border-radius: ${styleTokens.layout.radius_px}px; background: ${styleTokens.color.surface}; display: grid; }
|
|
33
|
+
.image-asset { padding: 12px; border-bottom: 1px solid ${styleTokens.color.rule}; }
|
|
34
|
+
.image-asset img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: ${styleTokens.layout.radius_px}px; }
|
|
35
|
+
.evidence-row { padding: 22px 24px; border-bottom: 1px solid ${styleTokens.color.rule}; }
|
|
36
|
+
.evidence-row:last-child { border-bottom: 0; }
|
|
37
|
+
.label { color: ${styleTokens.color.primary}; font-size: ${styleTokens.typography.caption_px}px; font-weight: 600; letter-spacing: 0; text-transform: uppercase; margin-bottom: 8px; }
|
|
38
|
+
.value { color: ${styleTokens.color.text}; font-size: 20px; line-height: 1.42; }
|
|
39
|
+
.source { display: grid; grid-template-columns: 1fr auto; gap: 24px; color: ${styleTokens.color.muted}; font-size: ${styleTokens.typography.caption_px}px; border-top: 1px solid ${styleTokens.color.rule}; padding-top: 14px; }
|
|
40
|
+
.accent { width: 64px; height: 3px; background: ${styleTokens.color.accent}; }`;
|
|
41
|
+
const generatedAssets = (imageAssetLedger?.assets || []).filter((asset) => asset.status === 'generated' && asset.html_src);
|
|
42
|
+
const pages = storyboard.pages.map((page) => {
|
|
43
|
+
const asset = generatedAssets.find((candidate) => Number(candidate.slide) === Number(page.number));
|
|
44
|
+
return `<section class="page">
|
|
45
|
+
<header class="topline">
|
|
46
|
+
<div class="kicker">${escapeHtml(page.kind)} / ${page.number}</div>
|
|
47
|
+
<div class="reference">${referenceName}</div>
|
|
48
|
+
</header>
|
|
49
|
+
<main class="content">
|
|
50
|
+
<div class="claim">
|
|
51
|
+
<div class="accent"></div>
|
|
52
|
+
<h1>${escapeHtml(page.claim)}</h1>
|
|
53
|
+
<p>${escapeHtml(page.support)}</p>
|
|
54
|
+
</div>
|
|
55
|
+
<aside class="evidence" aria-label="decision evidence">
|
|
56
|
+
${asset ? `<div class="image-asset"><img src="${escapeHtml(asset.html_src)}" alt="${escapeHtml(asset.role || 'generated presentation visual')}"></div>` : ''}
|
|
57
|
+
<div class="evidence-row">
|
|
58
|
+
<div class="label">Audience</div>
|
|
59
|
+
<div class="value">${audienceRaw}</div>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="evidence-row">
|
|
62
|
+
<div class="label">STP</div>
|
|
63
|
+
<div class="value">${stpRaw}</div>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="evidence-row">
|
|
66
|
+
<div class="label">Decision</div>
|
|
67
|
+
<div class="value">${decisionRaw}</div>
|
|
68
|
+
</div>
|
|
69
|
+
</aside>
|
|
70
|
+
</main>
|
|
71
|
+
<div class="source">
|
|
72
|
+
<span>Sources: ${escapeHtml((page.source_ids || []).join(', ') || 'none')}</span>
|
|
73
|
+
<span>${escapeHtml(styleTokens.layout?.composition || 'presentation-grid')}</span>
|
|
74
|
+
</div>
|
|
75
|
+
</section>`;
|
|
76
|
+
}).join('\n');
|
|
77
|
+
return `<!doctype html>
|
|
78
|
+
<html lang="${styleTokens.typography.language}">
|
|
79
|
+
<head>
|
|
80
|
+
<meta charset="utf-8">
|
|
81
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
82
|
+
<title>${title}</title>
|
|
83
|
+
<style>${css}</style>
|
|
84
|
+
</head>
|
|
85
|
+
<body>
|
|
86
|
+
${pages}
|
|
87
|
+
<script type="application/json" id="ppt-audience-strategy">${jsonScript(audience)}</script>
|
|
88
|
+
<script type="application/json" id="ppt-source-ledger">${jsonScript(sourceLedger)}</script>
|
|
89
|
+
<script type="application/json" id="ppt-fact-ledger">${jsonScript(factLedger || null)}</script>
|
|
90
|
+
<script type="application/json" id="ppt-image-asset-ledger">${jsonScript(imageAssetLedger || null)}</script>
|
|
91
|
+
<script type="application/json" id="ppt-review-policy">${jsonScript(reviewPolicy || null)}</script>
|
|
92
|
+
</body>
|
|
93
|
+
</html>
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=html.js.map
|