@awak-app/simy-cli 0.1.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.
@@ -0,0 +1,91 @@
1
+ import { cleanString, stringArray } from "./shared.js";
2
+
3
+ export function buildAttempt({ attemptNumber, charter, instruction, promptInterventions, execution }) {
4
+ const result = execution.result && typeof execution.result === "object" ? execution.result : {};
5
+ const summary =
6
+ cleanString(result.summary) ||
7
+ cleanString(execution.error) ||
8
+ `Executor exited with ${execution.exitCode}.`;
9
+ const completed = execution.exitCode === 0 && !execution.error;
10
+ const now = new Date().toISOString();
11
+ return {
12
+ attempt_number: attemptNumber,
13
+ backend: charter.backend,
14
+ base_branch: charter.base_branch,
15
+ pr_base_branch: cleanString(result.pr_base_branch) || null,
16
+ status: completed ? "completed" : "failed",
17
+ instruction,
18
+ outcome_kind:
19
+ cleanString(result.outcome_kind) ||
20
+ (completed ? "coding.completed" : "coding.executor_failure"),
21
+ branch_name: cleanString(result.branch_name) || null,
22
+ commit_sha: cleanString(result.commit_sha) || null,
23
+ pr_url: cleanString(result.pr_url) || null,
24
+ pr_number: Number.isInteger(result.pr_number) ? result.pr_number : null,
25
+ tests_run: stringArray(result.tests_run),
26
+ tests_passed: typeof result.tests_passed === "boolean" ? result.tests_passed : null,
27
+ ui_evidence_path: cleanString(result.ui_evidence_path) || null,
28
+ summary,
29
+ prompt_interventions: promptInterventions,
30
+ work_log_signals: [],
31
+ work_log_interventions: [],
32
+ artifact_previews: buildArtifactPreviews(result),
33
+ visual_qa_result: null,
34
+ release_gate: null,
35
+ executor_logs: stringArray(execution.logs).slice(-100),
36
+ token_usage:
37
+ execution.tokenUsage && typeof execution.tokenUsage === "object"
38
+ ? execution.tokenUsage
39
+ : {},
40
+ audit: null,
41
+ raw_output: result,
42
+ started_at: execution.startedAt || now,
43
+ finished_at: execution.finishedAt || now,
44
+ };
45
+ }
46
+
47
+ export function parseStructuredResult(text) {
48
+ return parseStructuredMarker(text, "SIMY_RESULT_JSON:");
49
+ }
50
+
51
+ export function parseStructuredMarker(text, marker) {
52
+ const index = String(text || "").lastIndexOf(marker);
53
+ if (index < 0) return {};
54
+ const afterMarker = String(text).slice(index + marker.length).trim();
55
+ const firstLine = afterMarker.split(/\r?\n/, 1)[0].trim();
56
+ try {
57
+ const value = JSON.parse(firstLine);
58
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
59
+ } catch {
60
+ return {};
61
+ }
62
+ }
63
+
64
+ function buildArtifactPreviews(result) {
65
+ return [
66
+ result.pr_url
67
+ ? {
68
+ id: "pr",
69
+ title: "Pull request",
70
+ kind: "pull_request",
71
+ uri: result.pr_url,
72
+ path: null,
73
+ summary: cleanString(result.pr_title),
74
+ content_excerpt: null,
75
+ metadata: { number: Number.isInteger(result.pr_number) ? result.pr_number : null },
76
+ }
77
+ : null,
78
+ result.ui_evidence_path
79
+ ? {
80
+ id: "evidence",
81
+ title: "Local evidence",
82
+ kind: "evidence",
83
+ uri: null,
84
+ path: result.ui_evidence_path,
85
+ summary: "Local evidence reported by the executor.",
86
+ content_excerpt: null,
87
+ metadata: {},
88
+ }
89
+ : null,
90
+ ].filter(Boolean);
91
+ }
@@ -0,0 +1,63 @@
1
+ import { cleanString, stringArray } from "./shared.js";
2
+
3
+ const RISK_RULES = [
4
+ {
5
+ level: "critical",
6
+ tag: "payments_or_sensitive_data",
7
+ pattern: /\b(payment|billing|charge|refund|pii|personal data|medical|patient|secret)\b|決済|個人情報|医療|患者|支付|退款|个人信息/i,
8
+ },
9
+ {
10
+ level: "high",
11
+ tag: "security_or_identity",
12
+ pattern: /\b(auth|authentication|authorization|permission|tenant|security|oauth|session)\b|認証|認可|権限|安全|租户/i,
13
+ },
14
+ {
15
+ level: "high",
16
+ tag: "data_or_infrastructure",
17
+ pattern: /\b(migration|database|schema|backfill|queue|transaction|infrastructure|terraform|cloudformation)\b|移行|データベース|数据库|迁移|基础设施/i,
18
+ },
19
+ {
20
+ level: "medium",
21
+ tag: "external_contract",
22
+ pattern: /\b(api|webhook|integration|dependency|package|contract)\b|外部連携|依存|接口|集成/i,
23
+ },
24
+ {
25
+ level: "medium",
26
+ tag: "user_interface",
27
+ pattern: /\b(ui|ux|page|screen|component|frontend|browser|css|react|next)\b|画面|ページ|页面|界面|按钮|表示/i,
28
+ },
29
+ ];
30
+
31
+ const LEVEL_WEIGHT = { low: 0, medium: 1, high: 2, critical: 3 };
32
+
33
+ export function classifyRisk(request) {
34
+ const text = [
35
+ cleanString(request.requirement),
36
+ ...stringArray(request.acceptance_criteria),
37
+ ...stringArray(request.must_not),
38
+ ].join("\n");
39
+ const tags = [];
40
+ let level = /\b(docs?|readme|comment|typo)\b|文書|ドキュメント|文档/i.test(text)
41
+ ? "low"
42
+ : "medium";
43
+
44
+ for (const rule of RISK_RULES) {
45
+ if (!rule.pattern.test(text)) continue;
46
+ tags.push(rule.tag);
47
+ if (LEVEL_WEIGHT[rule.level] > LEVEL_WEIGHT[level]) level = rule.level;
48
+ }
49
+
50
+ const requiresDesignReview = level === "high" || level === "critical";
51
+ return {
52
+ level,
53
+ tags,
54
+ requires_design_review: requiresDesignReview,
55
+ required_controls: [
56
+ "verified_local_git",
57
+ "independent_ai_audit",
58
+ "github_status_checks",
59
+ "human_peer_approval",
60
+ ...(requiresDesignReview ? ["design_review"] : []),
61
+ ],
62
+ };
63
+ }
@@ -0,0 +1,60 @@
1
+ import path from "node:path";
2
+
3
+ const ALLOWED_PREFIXES = ["feat:", "chore:", "fix:", "refactor:", "infra:"];
4
+ const MAX_ATTEMPTS = 5;
5
+
6
+ export function appendEvent(snapshot, state, message, detail = {}) {
7
+ snapshot.events.push(event(state, message, detail));
8
+ }
9
+
10
+ export function bullets(items) {
11
+ return items.length > 0 ? items.map((item) => `- ${item}`).join("\n") : "- not provided";
12
+ }
13
+
14
+ export function clampAttempts(value) {
15
+ const number = Number.parseInt(String(value ?? 3), 10);
16
+ if (!Number.isFinite(number)) return 3;
17
+ return Math.min(MAX_ATTEMPTS, Math.max(1, number));
18
+ }
19
+
20
+ export function cleanString(value) {
21
+ return typeof value === "string" ? value.trim() : "";
22
+ }
23
+
24
+ export function event(state, message, detail = {}) {
25
+ return { state, message, detail, occurred_at: new Date().toISOString() };
26
+ }
27
+
28
+ export function evidenceKind(value) {
29
+ const extension = path.extname(value).toLowerCase();
30
+ if ([".png", ".jpg", ".jpeg", ".webp", ".gif"].includes(extension)) return "image";
31
+ if ([".mp4", ".mov", ".webm"].includes(extension)) return "video";
32
+ if (extension === ".json") return "json";
33
+ if ([".txt", ".md", ".log"].includes(extension)) return "text";
34
+ return "unknown";
35
+ }
36
+
37
+ export function hasAllowedPrefix(value) {
38
+ const text = cleanString(value).toLowerCase();
39
+ return ALLOWED_PREFIXES.some((prefix) => text.startsWith(prefix));
40
+ }
41
+
42
+ export function looksLikeUiTask(value) {
43
+ return /\b(ui|ux|page|screen|component|frontend|browser|css|tailwind|react|next)\b|画面|页面|界面|按钮|表示/i.test(
44
+ cleanString(value),
45
+ );
46
+ }
47
+
48
+ export async function publish(snapshot, state, onUpdate) {
49
+ snapshot.state = state;
50
+ snapshot.updated_at = new Date().toISOString();
51
+ if (onUpdate) await onUpdate(snapshot);
52
+ }
53
+
54
+ export function section(name, body) {
55
+ return `<${name}>\n${body}\n</${name}>`;
56
+ }
57
+
58
+ export function stringArray(value) {
59
+ return Array.isArray(value) ? value.map(cleanString).filter(Boolean) : [];
60
+ }