@peterxiaoyang/superspec 0.1.14 → 0.1.15-alpha
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/dist/cli.d.ts +2 -0
- package/dist/cli.js +302 -0
- package/dist/format.d.ts +29 -0
- package/dist/format.js +141 -0
- package/dist/next.d.ts +3 -0
- package/dist/next.js +161 -0
- package/dist/openspec.d.ts +9 -0
- package/dist/openspec.js +41 -0
- package/dist/record.d.ts +20 -0
- package/dist/record.js +211 -0
- package/dist/skill_loop.d.ts +23 -0
- package/dist/skill_loop.js +58 -0
- package/dist/store.d.ts +31 -0
- package/dist/store.js +213 -0
- package/dist/sync.d.ts +3 -0
- package/dist/sync.js +135 -0
- package/dist/task.d.ts +7 -0
- package/dist/task.js +44 -0
- package/dist/transition.d.ts +34 -0
- package/dist/transition.js +400 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.js +3 -0
- package/package.json +8 -53
- package/templates/workflow/skills/superspec-apply/SKILL.md +46 -118
- package/templates/workflow/skills/superspec-archive/SKILL.md +22 -47
- package/templates/workflow/skills/superspec-explore/SKILL.md +38 -69
- package/templates/workflow/skills/superspec-propose/SKILL.md +58 -60
- package/templates/workflow/skills/superspec-review/SKILL.md +36 -81
- package/adapters/codex/agents/architect.toml +0 -13
- package/adapters/codex/agents/code-reviewer.toml +0 -13
- package/adapters/codex/agents/critic.toml +0 -13
- package/adapters/codex/agents/executor.toml +0 -13
- package/adapters/codex/agents/explore.toml +0 -13
- package/adapters/codex/agents/test-engineer.toml +0 -13
- package/adapters/codex/agents/test-runner.toml +0 -13
- package/adapters/codex/agents/verifier.toml +0 -13
- package/adapters/codex/install-map.json +0 -116
- package/bin/launch.js +0 -37
- package/bin/superspec-check.js +0 -4
- package/bin/superspec-hook.js +0 -4
- package/bin/superspec-init.js +0 -4
- package/bin/superspec.js +0 -4
- package/dist/src/apply_worker_chain.d.ts +0 -60
- package/dist/src/apply_worker_chain.js +0 -1283
- package/dist/src/apply_worker_chain_lifecycle.d.ts +0 -21
- package/dist/src/apply_worker_chain_lifecycle.js +0 -409
- package/dist/src/archive.d.ts +0 -23
- package/dist/src/archive.js +0 -430
- package/dist/src/cli.d.ts +0 -1
- package/dist/src/cli.js +0 -33
- package/dist/src/cli_args.d.ts +0 -32
- package/dist/src/cli_args.js +0 -454
- package/dist/src/core.d.ts +0 -20
- package/dist/src/core.js +0 -394
- package/dist/src/disclosure.d.ts +0 -35
- package/dist/src/disclosure.js +0 -671
- package/dist/src/doctor.d.ts +0 -44
- package/dist/src/doctor.js +0 -230
- package/dist/src/evidence.d.ts +0 -29
- package/dist/src/evidence.js +0 -1107
- package/dist/src/gates.d.ts +0 -19
- package/dist/src/gates.js +0 -1631
- package/dist/src/git.d.ts +0 -8
- package/dist/src/git.js +0 -112
- package/dist/src/hooks/adapter.d.ts +0 -5
- package/dist/src/hooks/adapter.js +0 -347
- package/dist/src/hooks/guard_api.d.ts +0 -12
- package/dist/src/hooks/guard_api.js +0 -797
- package/dist/src/hooks/health.d.ts +0 -4
- package/dist/src/hooks/health.js +0 -86
- package/dist/src/hooks/policy_event.d.ts +0 -41
- package/dist/src/hooks/policy_event.js +0 -2634
- package/dist/src/hooks/types.d.ts +0 -72
- package/dist/src/hooks/types.js +0 -1
- package/dist/src/hooks/validation.d.ts +0 -3
- package/dist/src/hooks/validation.js +0 -70
- package/dist/src/i18n.d.ts +0 -21
- package/dist/src/i18n.js +0 -720
- package/dist/src/init_cli.d.ts +0 -28
- package/dist/src/init_cli.js +0 -404
- package/dist/src/install_engine.d.ts +0 -71
- package/dist/src/install_engine.js +0 -538
- package/dist/src/invariants.d.ts +0 -16
- package/dist/src/invariants.js +0 -363
- package/dist/src/openspec.d.ts +0 -37
- package/dist/src/openspec.js +0 -274
- package/dist/src/packet_render.d.ts +0 -5
- package/dist/src/packet_render.js +0 -1767
- package/dist/src/packet_schema.d.ts +0 -59
- package/dist/src/packet_schema.js +0 -1
- package/dist/src/paths.d.ts +0 -22
- package/dist/src/paths.js +0 -203
- package/dist/src/project_init.d.ts +0 -33
- package/dist/src/project_init.js +0 -191
- package/dist/src/self_update.d.ts +0 -14
- package/dist/src/self_update.js +0 -56
- package/dist/src/state.d.ts +0 -37
- package/dist/src/state.js +0 -464
- package/dist/src/tasks.d.ts +0 -38
- package/dist/src/tasks.js +0 -373
- package/dist/src/util.d.ts +0 -163
- package/dist/src/util.js +0 -792
- package/dist/superspec.d.ts +0 -6
- package/dist/superspec.js +0 -97
- package/dist/superspec_guard.d.ts +0 -4
- package/dist/superspec_guard.js +0 -19
- package/dist/superspec_hook.d.ts +0 -4
- package/dist/superspec_hook.js +0 -35
- package/dist/superspec_init.d.ts +0 -2
- package/dist/superspec_init.js +0 -17
- package/schemas/hook-event.schema.json +0 -27
- package/schemas/install-manifest.schema.json +0 -97
- package/templates/hooks/codex-hooks.json +0 -35
- package/templates/sidecar/archive-preservation.json +0 -11
- package/templates/sidecar/business-invariants.md +0 -38
- package/templates/sidecar/config.yaml +0 -13
- package/templates/sidecar/discovery.md +0 -33
- package/templates/sidecar/test-contract.md +0 -27
- package/templates/workflow/prompts/architect.md +0 -27
- package/templates/workflow/prompts/code-reviewer.md +0 -34
- package/templates/workflow/prompts/critic.md +0 -30
- package/templates/workflow/prompts/executor.md +0 -32
- package/templates/workflow/prompts/explore.md +0 -27
- package/templates/workflow/prompts/test-engineer.md +0 -28
- package/templates/workflow/prompts/test-runner.md +0 -35
- package/templates/workflow/prompts/verifier.md +0 -35
package/dist/src/util.js
DELETED
|
@@ -1,792 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3
|
-
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { action_detail_zh, action_label_zh, action_status_zh, command_zh, decision_zh, gate_zh, reason_message_zh, reason_zh, translate_action_zh, trust_warning_zh, workflow_terms_zh_for } from "./i18n.js";
|
|
6
|
-
export const SCHEMA_VERSION = 1;
|
|
7
|
-
export const GUARD_VERSION = "superspec-guard@1";
|
|
8
|
-
export const CONFIG_FILENAME = "config.yaml";
|
|
9
|
-
export const STATE_FILENAME = "superspec-state.json";
|
|
10
|
-
export const STATE_LOCK_FILENAME = "superspec-state.lock";
|
|
11
|
-
export const PROJECT_CONFIG_ALIASES = [
|
|
12
|
-
".superspec.yaml",
|
|
13
|
-
".superspec.yml",
|
|
14
|
-
"superspec.yaml",
|
|
15
|
-
"superspec.yml",
|
|
16
|
-
"superspec.json",
|
|
17
|
-
".superspecrc",
|
|
18
|
-
".superspec/config.json",
|
|
19
|
-
];
|
|
20
|
-
export const CHANGE_CONFIG_ALIASES = PROJECT_CONFIG_ALIASES;
|
|
21
|
-
export const STATE_ALIASES = [
|
|
22
|
-
".superspec/state.json",
|
|
23
|
-
".superspec/state.lock",
|
|
24
|
-
".superspec/superspec_state.json",
|
|
25
|
-
".superspec/superspec-state.yaml",
|
|
26
|
-
];
|
|
27
|
-
export const REQUIRED_SIDECAR_DIRS = [
|
|
28
|
-
"artifacts",
|
|
29
|
-
"evidence/discovery",
|
|
30
|
-
"evidence/design",
|
|
31
|
-
"evidence/invariants",
|
|
32
|
-
"evidence/test-contract",
|
|
33
|
-
"evidence/tasks",
|
|
34
|
-
"evidence/red",
|
|
35
|
-
"evidence/green",
|
|
36
|
-
"evidence/reviews",
|
|
37
|
-
"evidence/verification",
|
|
38
|
-
"evidence/archive",
|
|
39
|
-
"handoffs",
|
|
40
|
-
"reports",
|
|
41
|
-
"raw",
|
|
42
|
-
];
|
|
43
|
-
export const BUILTIN_CONFIG = {
|
|
44
|
-
preset: "full",
|
|
45
|
-
commands: {},
|
|
46
|
-
roles: {},
|
|
47
|
-
rules: {},
|
|
48
|
-
trust: { v1_evidence: "audit-only" },
|
|
49
|
-
archive: {},
|
|
50
|
-
};
|
|
51
|
-
export const ALLOWED_CONFIG_KEYS = new Set(Object.keys(BUILTIN_CONFIG));
|
|
52
|
-
export const EVIDENCE_STATUSES = new Set(["pass", "fail", "blocked", "superseded"]);
|
|
53
|
-
// FIX-7 (audit C-3): kind whitelist assembled from every kind the guard recognizes plus the
|
|
54
|
-
// conventional role-report kinds. A typo'd kind must fail loudly (evidence_unknown_kind)
|
|
55
|
-
// instead of silently surfacing as "missing evidence" at some downstream gate.
|
|
56
|
-
export const EVIDENCE_KINDS = new Set([
|
|
57
|
-
"review",
|
|
58
|
-
"subagent_report",
|
|
59
|
-
"workflow_review",
|
|
60
|
-
"source_guidance",
|
|
61
|
-
"main_adjudication",
|
|
62
|
-
"verification_review",
|
|
63
|
-
"final_test",
|
|
64
|
-
"test_run",
|
|
65
|
-
"alternative_verification",
|
|
66
|
-
"manual_verification",
|
|
67
|
-
"apply_worker_chain",
|
|
68
|
-
"task_reopen",
|
|
69
|
-
"task_reopen_resolved",
|
|
70
|
-
"human_confirmation",
|
|
71
|
-
"superseded",
|
|
72
|
-
// Disclosure fixed-point loop (DISC Phase 1): user-visible review disclosure evidence.
|
|
73
|
-
"main_review_digest",
|
|
74
|
-
"user_review_decision",
|
|
75
|
-
"review_standing_authorization",
|
|
76
|
-
]);
|
|
77
|
-
// FIX-7 (audit C-3): gates where the guard consumes human_confirmation evidence.
|
|
78
|
-
// A confirmation recorded against any other gate is unreachable and therefore a mistake.
|
|
79
|
-
// FIX-8 (audit A-5) adds the previously anchor-less human pause points:
|
|
80
|
-
// apply isolation choice, apply-phase scope expansion, and verify-failure disposition.
|
|
81
|
-
export const HUMAN_CONFIRMATION_GATES = new Set([
|
|
82
|
-
"explore_complete",
|
|
83
|
-
"design_complete",
|
|
84
|
-
"invariants_reviewed",
|
|
85
|
-
"archive_ready",
|
|
86
|
-
"preset_upgrade",
|
|
87
|
-
"branch_handling",
|
|
88
|
-
"apply_isolation",
|
|
89
|
-
"scope_expansion",
|
|
90
|
-
"serial_takeover",
|
|
91
|
-
"verify_failure_handling",
|
|
92
|
-
]);
|
|
93
|
-
// Pending-confirmation wording shared by two defenses so they cannot drift:
|
|
94
|
-
// - the explore_complete open-questions gate (B1) counts list items inside discovery.md's
|
|
95
|
-
// "待确认问题" section that carry this wording;
|
|
96
|
-
// - the human_confirmation self-pending guard (B3) rejects confirmation_text that carries it.
|
|
97
|
-
// Worded to confirmation semantics only: "仍需/还需要 + 评估/细化" (legitimate follow-up prose,
|
|
98
|
-
// not an unresolved confirmation) is intentionally excluded because it does not end in 确认.
|
|
99
|
-
export const PENDING_CONFIRMATION_RE = /仍需确认|待确认|尚未确认|未确认完|还需要[^,。;\n]{0,20}确认/u;
|
|
100
|
-
export const UNCHECKED_CHECKBOX_RE = /\[ \]/u;
|
|
101
|
-
export const CHECKED_CHECKBOX_RE = /\[[xX]\]/u;
|
|
102
|
-
export function confirmation_text_has_pending_wording(text) {
|
|
103
|
-
return PENDING_CONFIRMATION_RE.test(text);
|
|
104
|
-
}
|
|
105
|
-
export const NO_TDD_REASONS = new Set([
|
|
106
|
-
"documentation-only",
|
|
107
|
-
"configuration-only",
|
|
108
|
-
"test-only-refactor",
|
|
109
|
-
"mechanical-rename",
|
|
110
|
-
"generated-artifact-only",
|
|
111
|
-
"non-executable-spec-change",
|
|
112
|
-
]);
|
|
113
|
-
export const TDD_MODES = new Set(["new-behavior", "behavior-preserving-refactor", "hotfix"]);
|
|
114
|
-
export const ROLE_EVIDENCE_FIELDS = ["agent_role", "agent_id", "prompt_ref", "output_ref", "source_anchors", "target_refs"];
|
|
115
|
-
export const SELF_REVIEW_MARKERS = new Set(["main", "main-thread", "current-agent", "self", "lead", "leader", "orchestrator", "codex_exec"]);
|
|
116
|
-
export const REVIEW_EVIDENCE_REQUIRED_FIELDS = ["base_ref", "head_ref", "reviewed_files"];
|
|
117
|
-
export const VERIFY_EVIDENCE_REQUIRED_FIELDS = ["openspec_validate_ref", "task_matrix_ref", "invariant_matrix_ref", "scope_drift_ref", "test_evidence_refs"];
|
|
118
|
-
export const REVIEW_GUIDANCE_ROLES = ["code-reviewer", "architect", "critic"];
|
|
119
|
-
export const FINAL_VERIFICATION_ROLES = ["verifier", "critic"];
|
|
120
|
-
export const SOURCE_GUIDANCE_REQUIRED_FIELDS = ["source_refs", "required_load_refs", "required_claim_ids"];
|
|
121
|
-
export const MAIN_ADJUDICATION_REQUIRED_FIELDS = ["output_ref", "source_evidence_refs", "verification_evidence_refs", "loaded_refs", "claim_adjudications", "finding_adjudications"];
|
|
122
|
-
export const MAIN_ADJUDICATION_DECISIONS = ["allow", "request_changes"];
|
|
123
|
-
export const REQUEST_CHANGES_ROUTES = ["reopen_tasks", "change_update"];
|
|
124
|
-
export const CLAIM_ADJUDICATION_DECISIONS = ["accept", "reject", "needs_fix"];
|
|
125
|
-
export const FINDING_ADJUDICATION_DECISIONS = ["dismissed", "accepted_fixed", "accepted_deviation", "needs_fix"];
|
|
126
|
-
export const FINAL_TEST_REQUIRED_FIELDS = ["test_command", "output_ref"];
|
|
127
|
-
export const TASK_REOPEN_REQUIRED_FIELDS = [
|
|
128
|
-
"task_id",
|
|
129
|
-
"reopen_id",
|
|
130
|
-
"source_adjudication_evidence_id",
|
|
131
|
-
"source_guidance_evidence_id",
|
|
132
|
-
"violated_test_ids",
|
|
133
|
-
"violated_requirement_refs",
|
|
134
|
-
"invalidated_completion_evidence_ids",
|
|
135
|
-
"required_supersede_evidence_ids",
|
|
136
|
-
"completion_invalidity_class",
|
|
137
|
-
"scope_expansion",
|
|
138
|
-
"why_completion_invalid",
|
|
139
|
-
"required_fix",
|
|
140
|
-
"before_tasks_sha256",
|
|
141
|
-
"after_tasks_sha256",
|
|
142
|
-
];
|
|
143
|
-
export const TASK_REOPEN_RESOLVED_REQUIRED_FIELDS = [
|
|
144
|
-
"reopen_evidence_id",
|
|
145
|
-
"reopen_id",
|
|
146
|
-
"task_id",
|
|
147
|
-
"successor_completion_evidence_ids",
|
|
148
|
-
"after_tasks_sha256",
|
|
149
|
-
];
|
|
150
|
-
export const TASK_REOPEN_INVALIDITY_CLASSES = ["insufficient_completion_evidence"];
|
|
151
|
-
export const FORBIDDEN_FIELDS = new Set([
|
|
152
|
-
"current",
|
|
153
|
-
"current_phase",
|
|
154
|
-
"current_stage",
|
|
155
|
-
"stage",
|
|
156
|
-
"stage_order",
|
|
157
|
-
"allowed_next",
|
|
158
|
-
"completed_phases",
|
|
159
|
-
"openspec_artifact_done",
|
|
160
|
-
"artifact_status",
|
|
161
|
-
]);
|
|
162
|
-
export const OPENSPEC_ARTIFACTS = new Set(["proposal", "specs", "design", "tasks"]);
|
|
163
|
-
// Compatibility export for callers that imported the old repo-local OpenSpec bridge list.
|
|
164
|
-
// Phase 4 makes the OpenSpec CLI surface the runtime truth, so no repo-local OpenSpec skills are
|
|
165
|
-
// required by SuperSpec init or health checks.
|
|
166
|
-
export const REQUIRED_OPENSPEC_CODEX_SKILLS = [];
|
|
167
|
-
// D4 (audit G-2): SuperSpec's own workflow skills are part of the init health surface — a deleted
|
|
168
|
-
// or renamed superspec-* skill must be visible at check-init, not discovered mid-workflow.
|
|
169
|
-
export const REQUIRED_SUPERSPEC_WORKFLOW_SKILLS = [
|
|
170
|
-
"superspec-explore",
|
|
171
|
-
"superspec-propose",
|
|
172
|
-
"superspec-apply",
|
|
173
|
-
"superspec-review",
|
|
174
|
-
"superspec-archive",
|
|
175
|
-
];
|
|
176
|
-
export const REQUIRED_SUPERSPEC_AGENT_ROLES = [
|
|
177
|
-
"architect",
|
|
178
|
-
"critic",
|
|
179
|
-
"executor",
|
|
180
|
-
"test-runner",
|
|
181
|
-
"test-engineer",
|
|
182
|
-
"code-reviewer",
|
|
183
|
-
"verifier",
|
|
184
|
-
];
|
|
185
|
-
export const REQUIRED_OPENSPEC_CLI_SURFACES = [
|
|
186
|
-
["list", "--help"],
|
|
187
|
-
["instructions", "--help"],
|
|
188
|
-
["archive", "--help"],
|
|
189
|
-
["validate", "--help"],
|
|
190
|
-
["status", "--help"],
|
|
191
|
-
];
|
|
192
|
-
export const ARTIFACT_ENTER_GATE = {
|
|
193
|
-
proposal: "explore_complete",
|
|
194
|
-
// DISC Phase 2: specs/design authoring cannot start before the proposal critic review
|
|
195
|
-
// has been run and its findings disclosed.
|
|
196
|
-
specs: "proposal_reviewed",
|
|
197
|
-
design: "proposal_reviewed",
|
|
198
|
-
tasks: "test_contract_drafted",
|
|
199
|
-
};
|
|
200
|
-
export const ROUTE_ORDER = {
|
|
201
|
-
init: 0,
|
|
202
|
-
explore: 1,
|
|
203
|
-
propose: 2,
|
|
204
|
-
apply: 3,
|
|
205
|
-
review: 4,
|
|
206
|
-
archive: 5,
|
|
207
|
-
};
|
|
208
|
-
export const ROUTE_ALIASES = {
|
|
209
|
-
start: "init",
|
|
210
|
-
proposal: "propose",
|
|
211
|
-
specs: "propose",
|
|
212
|
-
design: "propose",
|
|
213
|
-
tasks: "propose",
|
|
214
|
-
"test-contract": "propose",
|
|
215
|
-
"test-contract-drafted": "propose",
|
|
216
|
-
"test-contract-honored": "propose",
|
|
217
|
-
"business-invariants": "propose",
|
|
218
|
-
"invariants-reviewed": "propose",
|
|
219
|
-
"tasks-complete": "propose",
|
|
220
|
-
"apply-ready": "propose",
|
|
221
|
-
verify: "review",
|
|
222
|
-
verification: "review",
|
|
223
|
-
"archive-ready": "archive",
|
|
224
|
-
archived: "archive",
|
|
225
|
-
recompute: "init",
|
|
226
|
-
};
|
|
227
|
-
export const GATE_ALIASES = {
|
|
228
|
-
"propose.explore_linked": "explore_complete",
|
|
229
|
-
"propose.proposal_reviewed": "proposal_reviewed",
|
|
230
|
-
"propose.design_reviewed": "design_complete",
|
|
231
|
-
"propose.invariants_reviewed": "invariants_reviewed",
|
|
232
|
-
"propose.test_plan_drafted": "test_contract_drafted",
|
|
233
|
-
"propose.tasks_mapped": "tasks_complete",
|
|
234
|
-
"propose.apply_ready": "apply_ready",
|
|
235
|
-
apply_ready: "apply_ready",
|
|
236
|
-
};
|
|
237
|
-
export const GATE_ROUTE = {
|
|
238
|
-
explore_complete: "explore",
|
|
239
|
-
proposal_reviewed: "propose",
|
|
240
|
-
design_complete: "propose",
|
|
241
|
-
invariants_reviewed: "propose",
|
|
242
|
-
test_contract_drafted: "propose",
|
|
243
|
-
test_contract_honored: "propose",
|
|
244
|
-
tasks_complete: "propose",
|
|
245
|
-
propose_complete: "propose",
|
|
246
|
-
apply_ready: "propose",
|
|
247
|
-
review_complete: "review",
|
|
248
|
-
verify_complete: "review",
|
|
249
|
-
archive_ready: "archive",
|
|
250
|
-
};
|
|
251
|
-
export class GuardError extends Error {
|
|
252
|
-
}
|
|
253
|
-
export const runtime = {};
|
|
254
|
-
export function parseDecisionOutputFormat(raw) {
|
|
255
|
-
if (raw === "json" || raw === "agent" || raw === "user")
|
|
256
|
-
return raw;
|
|
257
|
-
throw new GuardError("--format 只允许 json、agent 或 user");
|
|
258
|
-
}
|
|
259
|
-
export function parsePacketOutputFormat(raw, opts = {}) {
|
|
260
|
-
if (raw === "agent")
|
|
261
|
-
return raw;
|
|
262
|
-
if (opts.allowPrompt && raw === "prompt")
|
|
263
|
-
return raw;
|
|
264
|
-
throw new GuardError(opts.allowPrompt ? "--format 只允许 agent 或 prompt" : "--format 只允许 agent");
|
|
265
|
-
}
|
|
266
|
-
export function reason(code, message, refs = null) {
|
|
267
|
-
const zh = reason_zh(code);
|
|
268
|
-
return { code, message, refs: refs ?? [], label_zh: zh.label_zh, hint_zh: zh.hint_zh };
|
|
269
|
-
}
|
|
270
|
-
export function pinned_ref_key(item) {
|
|
271
|
-
return `${String(item.path)}\u0000${String(item.blob_sha)}`;
|
|
272
|
-
}
|
|
273
|
-
export function trustWarnings() {
|
|
274
|
-
return [
|
|
275
|
-
"v1 evidence is audit-only/self-reported unless explicitly backed by OpenSpec facts",
|
|
276
|
-
"v1 role evidence cannot mechanically prove a native subagent ran; it only checks schema, freshness, output refs, and non-direct authorship markers",
|
|
277
|
-
];
|
|
278
|
-
}
|
|
279
|
-
export function allow(change, gate, opts = {}) {
|
|
280
|
-
return {
|
|
281
|
-
allowed: true,
|
|
282
|
-
decision: "allow",
|
|
283
|
-
change_id: change,
|
|
284
|
-
gate,
|
|
285
|
-
task_id: opts.task_id ?? null,
|
|
286
|
-
openspec_status_summary: opts.openspec_summary ?? {},
|
|
287
|
-
superspec_gate_summary: opts.gate_summary ?? {},
|
|
288
|
-
block_reasons: [],
|
|
289
|
-
next_allowed_actions: [],
|
|
290
|
-
trust_warnings: trustWarnings(),
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
export function block(change, gate, reasons, opts = {}) {
|
|
294
|
-
return {
|
|
295
|
-
allowed: false,
|
|
296
|
-
decision: "block",
|
|
297
|
-
change_id: change,
|
|
298
|
-
gate,
|
|
299
|
-
task_id: opts.task_id ?? null,
|
|
300
|
-
openspec_status_summary: opts.openspec_summary ?? {},
|
|
301
|
-
superspec_gate_summary: opts.gate_summary ?? {},
|
|
302
|
-
block_reasons: reasons,
|
|
303
|
-
next_allowed_actions: opts.next_actions ?? [],
|
|
304
|
-
trust_warnings: trustWarnings(),
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
export function decorateDecision(decision, opts = {}) {
|
|
308
|
-
const gateInfo = gate_zh(String(decision.gate ?? ""));
|
|
309
|
-
const command = opts.command ?? (typeof decision.command === "string" ? String(decision.command) : "");
|
|
310
|
-
const commandInfo = command ? command_zh(command) : null;
|
|
311
|
-
const reasons = Array.isArray(decision.block_reasons)
|
|
312
|
-
? decision.block_reasons.map((item) => {
|
|
313
|
-
const base = item && typeof item === "object" ? { ...item } : { code: String(item), message: "", refs: [] };
|
|
314
|
-
const zh = reason_zh(String(base.code ?? ""));
|
|
315
|
-
return { ...base, label_zh: zh.label_zh, hint_zh: zh.hint_zh };
|
|
316
|
-
})
|
|
317
|
-
: [];
|
|
318
|
-
const nextActions = Array.isArray(decision.next_allowed_actions) ? decision.next_allowed_actions.map((item) => String(item)) : [];
|
|
319
|
-
const windowsPowerShellHints = windowsPowerShellCommandHints([
|
|
320
|
-
...nextActions,
|
|
321
|
-
...reasons.map((item) => String(item.message ?? "")),
|
|
322
|
-
]);
|
|
323
|
-
return {
|
|
324
|
-
...decision,
|
|
325
|
-
command: command || decision.command,
|
|
326
|
-
decision_zh: decision_zh(String(decision.decision ?? "")),
|
|
327
|
-
gate_label_zh: gateInfo.label_zh,
|
|
328
|
-
gate_hint_zh: gateInfo.hint_zh,
|
|
329
|
-
command_label_zh: commandInfo?.label_zh,
|
|
330
|
-
command_hint_zh: commandInfo?.hint_zh,
|
|
331
|
-
block_reasons: reasons,
|
|
332
|
-
next_allowed_actions_zh: nextActions.map((item) => translate_action_zh(item)),
|
|
333
|
-
windows_powershell_command_hints: windowsPowerShellHints.length > 0 ? windowsPowerShellHints : undefined,
|
|
334
|
-
trust_warnings_zh: Array.isArray(decision.trust_warnings) ? decision.trust_warnings.map((item) => trust_warning_zh(String(item))) : [],
|
|
335
|
-
workflow_terms_zh: workflow_terms_zh_for(command || undefined, String(decision.gate ?? ""), reasons.map((item) => String(item.code ?? ""))),
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
function windowsPowerShellCommandHints(texts) {
|
|
339
|
-
const hints = new Set();
|
|
340
|
-
for (const text of texts) {
|
|
341
|
-
for (const match of text.matchAll(/`((?:superspec|openspec)\s+[^`]+)`/giu)) {
|
|
342
|
-
const command = match[1]?.trim();
|
|
343
|
-
if (!command)
|
|
344
|
-
continue;
|
|
345
|
-
hints.add(`Windows PowerShell: use \`${command.replace(/^(superspec|openspec)\b/iu, "$1.cmd")}\``);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return [...hints];
|
|
349
|
-
}
|
|
350
|
-
function sanitizeReasonForOutput(item) {
|
|
351
|
-
const refs = Array.isArray(item.refs) ? item.refs.map((ref) => String(ref)) : [];
|
|
352
|
-
return {
|
|
353
|
-
...item,
|
|
354
|
-
refs,
|
|
355
|
-
message: String(item.message ?? ""),
|
|
356
|
-
message_zh: reason_message_zh(String(item.code ?? ""), String(item.message ?? ""), refs),
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
function sanitizeDecisionForOutput(decision) {
|
|
360
|
-
const reasons = Array.isArray(decision.block_reasons) ? decision.block_reasons.map((item) => sanitizeReasonForOutput(item)) : [];
|
|
361
|
-
const nextActions = Array.isArray(decision.next_allowed_actions)
|
|
362
|
-
? decision.next_allowed_actions.map((item) => String(item))
|
|
363
|
-
: [];
|
|
364
|
-
const nextActionsZh = Array.isArray(decision.next_allowed_actions_zh)
|
|
365
|
-
? decision.next_allowed_actions_zh.map((item) => String(item))
|
|
366
|
-
: [];
|
|
367
|
-
const trustWarnings = Array.isArray(decision.trust_warnings)
|
|
368
|
-
? decision.trust_warnings.map((item) => String(item))
|
|
369
|
-
: [];
|
|
370
|
-
const trustWarningsZh = Array.isArray(decision.trust_warnings_zh)
|
|
371
|
-
? decision.trust_warnings_zh.map((item) => String(item))
|
|
372
|
-
: [];
|
|
373
|
-
const actions = Array.isArray(decision.actions)
|
|
374
|
-
? decision.actions.map((item) => {
|
|
375
|
-
const base = item && typeof item === "object" ? { ...item } : { action: String(item) };
|
|
376
|
-
const action = String(base.action ?? "");
|
|
377
|
-
const status = typeof base.status === "string" ? String(base.status) : "";
|
|
378
|
-
const detail = typeof base.detail === "string" ? String(base.detail) : "";
|
|
379
|
-
return {
|
|
380
|
-
...base,
|
|
381
|
-
action,
|
|
382
|
-
status: status || base.status,
|
|
383
|
-
detail: detail || base.detail,
|
|
384
|
-
action_zh: action_label_zh(action),
|
|
385
|
-
status_zh: status ? action_status_zh(status) : undefined,
|
|
386
|
-
detail_zh: detail ? action_detail_zh(detail) : undefined,
|
|
387
|
-
};
|
|
388
|
-
})
|
|
389
|
-
: decision.actions;
|
|
390
|
-
return {
|
|
391
|
-
...decision,
|
|
392
|
-
block_reasons: reasons,
|
|
393
|
-
next_allowed_actions: nextActions,
|
|
394
|
-
next_allowed_actions_zh: nextActionsZh,
|
|
395
|
-
trust_warnings: trustWarnings,
|
|
396
|
-
trust_warnings_zh: trustWarningsZh,
|
|
397
|
-
actions,
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
function userFacingLine(value) {
|
|
401
|
-
return String(value ?? "").replace(/\s+/gu, " ").trim();
|
|
402
|
-
}
|
|
403
|
-
const SAFE_TEXT_REPLACEMENTS = [
|
|
404
|
-
[/\bneeds_user_decision_pending\b/giu, "等待用户确认"],
|
|
405
|
-
[/\bneeds_user_decision\b/giu, "等待用户确认"],
|
|
406
|
-
[/\buser_review_decision\b/giu, "用户确认记录"],
|
|
407
|
-
[/\bmain_review_digest\b/giu, "审查问题记录"],
|
|
408
|
-
[/\breview_standing_authorization\b/giu, "长期授权记录"],
|
|
409
|
-
[/\bdecision_scope_key\b/giu, "确认范围"],
|
|
410
|
-
[/\bfinding_uid\b/giu, "问题标识"],
|
|
411
|
-
[/\bexport\s+function\s+[A-Za-z_$][\w$]*\s*\([^)]*\)/gu, "内部实现细节"],
|
|
412
|
-
[/\bfunction\s+[A-Za-z_$][\w$]*\s*\([^)]*\)/gu, "内部实现细节"],
|
|
413
|
-
[/\b[A-Za-z_$][\w$]*\s*\([^)]*\)/gu, "内部调用细节"],
|
|
414
|
-
[/裁决/gu, "确认"],
|
|
415
|
-
];
|
|
416
|
-
function safeDisplayText(value) {
|
|
417
|
-
let out = userFacingLine(value);
|
|
418
|
-
for (const [pattern, replacement] of SAFE_TEXT_REPLACEMENTS)
|
|
419
|
-
out = out.replace(pattern, replacement);
|
|
420
|
-
return out;
|
|
421
|
-
}
|
|
422
|
-
function fallbackReasonForWorkflowAction(action) {
|
|
423
|
-
const fallbacks = {
|
|
424
|
-
continue: "当前检查已通过。",
|
|
425
|
-
ask_user_confirmation: "需要用户确认后才能继续。",
|
|
426
|
-
collect_review_evidence: "需要补齐审查或验证复核记录。",
|
|
427
|
-
collect_test_evidence: "需要补齐测试或校验证据。",
|
|
428
|
-
fix_artifacts: "需要修正方案、任务或证据结构。",
|
|
429
|
-
repair_evidence: "需要修复证据记录。",
|
|
430
|
-
rerun_check: "需要重新运行当前检查。",
|
|
431
|
-
inspect_diagnostics: "需要查看诊断输出后处理。",
|
|
432
|
-
};
|
|
433
|
-
return fallbacks[action];
|
|
434
|
-
}
|
|
435
|
-
function safeReasonText(_item, action = "inspect_diagnostics") {
|
|
436
|
-
return fallbackReasonForWorkflowAction(action);
|
|
437
|
-
}
|
|
438
|
-
const WORKFLOW_ACTION_BY_REASON = [
|
|
439
|
-
[new Set([
|
|
440
|
-
"needs_user_decision_pending",
|
|
441
|
-
"user_decision_unbound",
|
|
442
|
-
"missing_review_digest",
|
|
443
|
-
"missing_human_confirmation",
|
|
444
|
-
"apply_isolation_unconfirmed",
|
|
445
|
-
"scope_expansion_unconfirmed",
|
|
446
|
-
"finding_unresolved",
|
|
447
|
-
"round_budget_exhausted",
|
|
448
|
-
]), "ask_user_confirmation"],
|
|
449
|
-
[new Set([
|
|
450
|
-
"missing_source_guidance",
|
|
451
|
-
"missing_verification_review",
|
|
452
|
-
"missing_final_verification_review",
|
|
453
|
-
"missing_roles",
|
|
454
|
-
"missing_native_subagent_evidence",
|
|
455
|
-
"missing_invariant_review",
|
|
456
|
-
"missing_test_contract_review",
|
|
457
|
-
"missing_architect_review",
|
|
458
|
-
"missing_critic_review",
|
|
459
|
-
"missing_test-engineer_review",
|
|
460
|
-
"missing_code-reviewer_review",
|
|
461
|
-
"missing_verifier_review",
|
|
462
|
-
"missing_main_adjudication",
|
|
463
|
-
"proposal_reviewed_failed",
|
|
464
|
-
"review_not_ready",
|
|
465
|
-
"missing_proposal_review",
|
|
466
|
-
]), "collect_review_evidence"],
|
|
467
|
-
[new Set([
|
|
468
|
-
"missing_red_evidence",
|
|
469
|
-
"missing_green_evidence",
|
|
470
|
-
"missing_characterization",
|
|
471
|
-
"test_contract_not_honored",
|
|
472
|
-
"validate_failed",
|
|
473
|
-
"missing_final_tests",
|
|
474
|
-
"verify_failure_unconfirmed",
|
|
475
|
-
]), "collect_test_evidence"],
|
|
476
|
-
[new Set([
|
|
477
|
-
"missing_discovery",
|
|
478
|
-
"missing_proposal",
|
|
479
|
-
"missing_design",
|
|
480
|
-
"missing_tasks",
|
|
481
|
-
"invalid_task_graph",
|
|
482
|
-
"invalid_business_invariants",
|
|
483
|
-
"review_finding_invalid",
|
|
484
|
-
"review_digest_invalid",
|
|
485
|
-
"user_decision_invalid",
|
|
486
|
-
"human_confirmation_invalid",
|
|
487
|
-
"standing_authorization_invalid",
|
|
488
|
-
"evidence_unknown_kind",
|
|
489
|
-
"evidence_missing_field",
|
|
490
|
-
"artifact_update_required",
|
|
491
|
-
"rereview_required",
|
|
492
|
-
]), "fix_artifacts"],
|
|
493
|
-
[new Set([
|
|
494
|
-
"state_concurrent_update",
|
|
495
|
-
"state_fingerprint_stale",
|
|
496
|
-
]), "rerun_check"],
|
|
497
|
-
[new Set([
|
|
498
|
-
"state_corrupt",
|
|
499
|
-
"openspec_cli_unavailable",
|
|
500
|
-
"openspec_native_surface_missing",
|
|
501
|
-
"dirty_worktree_unavailable",
|
|
502
|
-
"guard_error",
|
|
503
|
-
"guard_internal_error",
|
|
504
|
-
"unknown_gate",
|
|
505
|
-
"unknown_artifact",
|
|
506
|
-
"not_openspec_artifact",
|
|
507
|
-
]), "inspect_diagnostics"],
|
|
508
|
-
];
|
|
509
|
-
export function workflowActionForReasonCodes(reasonCodes, allowed = false) {
|
|
510
|
-
if (allowed)
|
|
511
|
-
return "continue";
|
|
512
|
-
const codes = new Set(reasonCodes);
|
|
513
|
-
for (const [matches, action] of WORKFLOW_ACTION_BY_REASON) {
|
|
514
|
-
for (const code of matches) {
|
|
515
|
-
if (codes.has(code))
|
|
516
|
-
return action;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
return "inspect_diagnostics";
|
|
520
|
-
}
|
|
521
|
-
function summaryForWorkflowAction(action, allowed) {
|
|
522
|
-
if (allowed || action === "continue")
|
|
523
|
-
return "当前检查已通过,可以继续下一步。";
|
|
524
|
-
const summaries = {
|
|
525
|
-
ask_user_confirmation: "当前阶段需要用户确认一个范围或处理方式选择后才能继续。",
|
|
526
|
-
collect_review_evidence: "当前阶段缺少必要审查或验证复核记录,补齐后再继续。",
|
|
527
|
-
collect_test_evidence: "当前阶段缺少测试或校验证据,补齐后再继续。",
|
|
528
|
-
fix_artifacts: "当前阶段的方案、任务或证据结构需要修正后再继续。",
|
|
529
|
-
repair_evidence: "当前证据记录需要修复后再继续。",
|
|
530
|
-
rerun_check: "当前检查需要在输入稳定后重新运行。",
|
|
531
|
-
inspect_diagnostics: "当前检查需要查看诊断输出后处理。",
|
|
532
|
-
};
|
|
533
|
-
return summaries[action];
|
|
534
|
-
}
|
|
535
|
-
function nextStepsForWorkflowAction(action, allowed) {
|
|
536
|
-
if (allowed || action === "continue")
|
|
537
|
-
return ["继续执行下一步。"];
|
|
538
|
-
const steps = {
|
|
539
|
-
ask_user_confirmation: ["向用户展示待确认的问题与选项,记录选择后重新运行检查。"],
|
|
540
|
-
collect_review_evidence: ["补齐所需审查或验证复核记录,然后重新运行检查。"],
|
|
541
|
-
collect_test_evidence: ["补齐失败/通过测试或校验证据,然后重新运行检查。"],
|
|
542
|
-
fix_artifacts: ["修正相关方案、任务或证据结构,然后重新运行检查。"],
|
|
543
|
-
repair_evidence: ["修复证据记录中的结构或引用问题,然后重新运行检查。"],
|
|
544
|
-
rerun_check: ["等待输入稳定后重新运行当前检查。"],
|
|
545
|
-
inspect_diagnostics: ["使用诊断输出查看内部细节,再按对应问题处理。"],
|
|
546
|
-
};
|
|
547
|
-
return steps[action];
|
|
548
|
-
}
|
|
549
|
-
const DIAGNOSTIC_HINT = "需要排查内部细节时使用 --format json。";
|
|
550
|
-
export function renderAgentDecision(decision, opts = {}) {
|
|
551
|
-
const decorated = sanitizeDecisionForOutput(decorateDecision(decision, opts));
|
|
552
|
-
const reasons = Array.isArray(decorated.block_reasons) ? decorated.block_reasons : [];
|
|
553
|
-
const reasonCodes = reasons.map((item) => String(item.code ?? ""));
|
|
554
|
-
const allowed = Boolean(decorated.allowed);
|
|
555
|
-
const workflowAction = workflowActionForReasonCodes(reasonCodes, allowed);
|
|
556
|
-
const renderedReasons = reasons.map((item) => safeReasonText(item, workflowAction)).filter(Boolean);
|
|
557
|
-
return {
|
|
558
|
-
allowed,
|
|
559
|
-
status: allowed ? "allowed" : "blocked",
|
|
560
|
-
workflow_action: workflowAction,
|
|
561
|
-
stage_label_zh: safeDisplayText(decorated.gate_label_zh) || "当前阶段",
|
|
562
|
-
check_label_zh: safeDisplayText(decorated.command_label_zh) || "当前检查",
|
|
563
|
-
summary_zh: safeDisplayText(summaryForWorkflowAction(workflowAction, allowed)),
|
|
564
|
-
reasons_zh: renderedReasons.length > 0 ? renderedReasons : undefined,
|
|
565
|
-
next_steps_zh: nextStepsForWorkflowAction(workflowAction, allowed).map(safeDisplayText),
|
|
566
|
-
diagnostic_hint: DIAGNOSTIC_HINT,
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
export function renderUserFacingDecision(decision, opts = {}) {
|
|
570
|
-
const decorated = sanitizeDecisionForOutput(decorateDecision(decision, opts));
|
|
571
|
-
const agentView = renderAgentDecision(decorated, opts);
|
|
572
|
-
const gate = safeDisplayText(decorated.gate_label_zh) || "当前检查";
|
|
573
|
-
const command = safeDisplayText(decorated.command_label_zh);
|
|
574
|
-
const lines = [];
|
|
575
|
-
if (decorated.allowed) {
|
|
576
|
-
lines.push(`检查通过:${gate}。`);
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
579
|
-
lines.push(`暂时不能继续:${gate}。`);
|
|
580
|
-
}
|
|
581
|
-
if (command && command !== gate) {
|
|
582
|
-
lines.push(`检查项:${command}。`);
|
|
583
|
-
}
|
|
584
|
-
const reasons = Array.isArray(decorated.block_reasons) ? decorated.block_reasons : [];
|
|
585
|
-
if (!decorated.allowed && reasons.length > 0) {
|
|
586
|
-
lines.push("原因:");
|
|
587
|
-
for (const item of reasons) {
|
|
588
|
-
const reasonText = safeReasonText(item, agentView.workflow_action);
|
|
589
|
-
lines.push(`- ${reasonText}`);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
const nextActions = Array.isArray(agentView.next_steps_zh) ? agentView.next_steps_zh.map(safeDisplayText).filter(Boolean) : [];
|
|
593
|
-
if (nextActions.length > 0) {
|
|
594
|
-
lines.push("下一步:");
|
|
595
|
-
for (const action of nextActions)
|
|
596
|
-
lines.push(`- ${action}`);
|
|
597
|
-
}
|
|
598
|
-
lines.push(`诊断:${DIAGNOSTIC_HINT}`);
|
|
599
|
-
return `${lines.join("\n")}\n`;
|
|
600
|
-
}
|
|
601
|
-
export function printDecision(decision, opts = {}) {
|
|
602
|
-
const decorated = decorateDecision(decision, opts);
|
|
603
|
-
if (opts.format === "user") {
|
|
604
|
-
process.stdout.write(renderUserFacingDecision(decorated, opts));
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
if (opts.format === "agent") {
|
|
608
|
-
process.stdout.write(`${JSON.stringify(renderAgentDecision(decorated, opts), null, 2)}\n`);
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
process.stdout.write(`${JSON.stringify(sanitizeDecisionForOutput(decorated), null, 2)}\n`);
|
|
612
|
-
}
|
|
613
|
-
export function printPacket(payload, opts) {
|
|
614
|
-
if (opts.format === "prompt") {
|
|
615
|
-
process.stdout.write(String(payload));
|
|
616
|
-
if (!String(payload).endsWith("\n"))
|
|
617
|
-
process.stdout.write("\n");
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
621
|
-
}
|
|
622
|
-
export function printPacketError(code, message) {
|
|
623
|
-
process.stdout.write(`${JSON.stringify({ status: "error", error_code: code, message }, null, 2)}\n`);
|
|
624
|
-
}
|
|
625
|
-
export function runCommand(cmd, args, opts = {}) {
|
|
626
|
-
const platform = opts.platform ?? process.platform;
|
|
627
|
-
const invocation = platform === "win32" ? windowsCommandInvocation(cmd, args, opts.cwd) : { cmd, args };
|
|
628
|
-
const result = spawnSync(invocation.cmd, invocation.args, {
|
|
629
|
-
cwd: opts.cwd,
|
|
630
|
-
encoding: "utf8",
|
|
631
|
-
timeout: opts.timeout,
|
|
632
|
-
});
|
|
633
|
-
return {
|
|
634
|
-
status: result.status,
|
|
635
|
-
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
636
|
-
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
637
|
-
error: result.error,
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
export function sha256_text(text) {
|
|
641
|
-
return `sha256:${createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex")}`;
|
|
642
|
-
}
|
|
643
|
-
export function sha256_file(filePath) {
|
|
644
|
-
if (!existsSync(filePath) || !statSync(filePath).isFile())
|
|
645
|
-
return null;
|
|
646
|
-
return `sha256:${createHash("sha256").update(readFileSync(filePath)).digest("hex")}`;
|
|
647
|
-
}
|
|
648
|
-
export function fingerprint_obj(obj) {
|
|
649
|
-
return sha256_text(stableJson(obj));
|
|
650
|
-
}
|
|
651
|
-
export function safe_within(base, candidate) {
|
|
652
|
-
if (isAbsolute(candidate) || candidate.split(/[\\/]+/).includes(".."))
|
|
653
|
-
return null;
|
|
654
|
-
const baseReal = realpathMaybe(base);
|
|
655
|
-
const resolved = resolve(base, candidate);
|
|
656
|
-
const candidateReal = resolveExistingPrefix(resolved);
|
|
657
|
-
const rel = relative(baseReal, candidateReal);
|
|
658
|
-
if (rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)))
|
|
659
|
-
return candidateReal;
|
|
660
|
-
return null;
|
|
661
|
-
}
|
|
662
|
-
export function now() {
|
|
663
|
-
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
664
|
-
}
|
|
665
|
-
export function isObject(value) {
|
|
666
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
667
|
-
}
|
|
668
|
-
export function repr(value) {
|
|
669
|
-
if (typeof value === "string")
|
|
670
|
-
return `'${value}'`;
|
|
671
|
-
if (value === null)
|
|
672
|
-
return "None";
|
|
673
|
-
if (value === undefined)
|
|
674
|
-
return "None";
|
|
675
|
-
return JSON.stringify(value);
|
|
676
|
-
}
|
|
677
|
-
export function renderList(items) {
|
|
678
|
-
return `[${items.map((item) => (typeof item === "string" ? `'${item}'` : String(item))).join(", ")}]`;
|
|
679
|
-
}
|
|
680
|
-
function stableJson(value) {
|
|
681
|
-
if (value === null)
|
|
682
|
-
return "null";
|
|
683
|
-
if (typeof value === "boolean")
|
|
684
|
-
return value ? "true" : "false";
|
|
685
|
-
if (typeof value === "number")
|
|
686
|
-
return JSON.stringify(value);
|
|
687
|
-
if (typeof value === "string")
|
|
688
|
-
return JSON.stringify(value);
|
|
689
|
-
if (Array.isArray(value))
|
|
690
|
-
return `[${value.map((item) => stableJson(item)).join(", ")}]`;
|
|
691
|
-
if (isObject(value)) {
|
|
692
|
-
const parts = Object.keys(value).sort().map((key) => `${JSON.stringify(key)}: ${stableJson(value[key])}`);
|
|
693
|
-
return `{${parts.join(", ")}}`;
|
|
694
|
-
}
|
|
695
|
-
return JSON.stringify(value);
|
|
696
|
-
}
|
|
697
|
-
function realpathMaybe(filePath) {
|
|
698
|
-
try {
|
|
699
|
-
return realpathSync(filePath);
|
|
700
|
-
}
|
|
701
|
-
catch {
|
|
702
|
-
return resolve(filePath);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
function resolveExistingPrefix(filePath) {
|
|
706
|
-
const abs = resolve(filePath);
|
|
707
|
-
if (existsSync(abs))
|
|
708
|
-
return realpathMaybe(abs);
|
|
709
|
-
const tail = [];
|
|
710
|
-
let cur = abs;
|
|
711
|
-
while (!existsSync(cur)) {
|
|
712
|
-
const parent = dirname(cur);
|
|
713
|
-
if (parent === cur)
|
|
714
|
-
return abs;
|
|
715
|
-
tail.unshift(cur.split(sep).at(-1) ?? "");
|
|
716
|
-
cur = parent;
|
|
717
|
-
}
|
|
718
|
-
return resolve(realpathMaybe(cur), ...tail);
|
|
719
|
-
}
|
|
720
|
-
export function walkFiles(root) {
|
|
721
|
-
const out = [];
|
|
722
|
-
if (!existsSync(root))
|
|
723
|
-
return out;
|
|
724
|
-
for (const name of readdirSync(root)) {
|
|
725
|
-
const item = join(root, name);
|
|
726
|
-
const stat = lstatSync(item);
|
|
727
|
-
if (stat.isSymbolicLink())
|
|
728
|
-
continue;
|
|
729
|
-
if (stat.isDirectory())
|
|
730
|
-
out.push(...walkFiles(item));
|
|
731
|
-
// Hardlinked files can alias content outside the change root; skip them like symlinks.
|
|
732
|
-
else if (stat.isFile() && stat.nlink <= 1)
|
|
733
|
-
out.push(item);
|
|
734
|
-
}
|
|
735
|
-
return out;
|
|
736
|
-
}
|
|
737
|
-
export function toPosix(pathValue) {
|
|
738
|
-
return pathValue.split(sep).join("/");
|
|
739
|
-
}
|
|
740
|
-
export function commandLookupInvocation(cmd, platform = process.platform) {
|
|
741
|
-
if (platform === "win32")
|
|
742
|
-
return { cmd: "where.exe", args: [cmd], shell: false };
|
|
743
|
-
return { cmd: "sh", args: ["-c", `command -v ${cmd}`], shell: false };
|
|
744
|
-
}
|
|
745
|
-
export function selectWindowsCommandCandidate(cmd, whereStdout) {
|
|
746
|
-
const candidates = whereStdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
747
|
-
return candidates.find((candidate) => /\.(?:cmd|bat)$/i.test(candidate)) ?? candidates[0] ?? cmd;
|
|
748
|
-
}
|
|
749
|
-
function resolveWindowsCommand(cmd, cwd) {
|
|
750
|
-
if (cmd.includes("\\") || cmd.includes("/") || extname(cmd))
|
|
751
|
-
return cmd;
|
|
752
|
-
const lookup = spawnSync("where.exe", [cmd], {
|
|
753
|
-
cwd,
|
|
754
|
-
encoding: "utf8",
|
|
755
|
-
timeout: 5_000,
|
|
756
|
-
});
|
|
757
|
-
if (lookup.status !== 0 || typeof lookup.stdout !== "string")
|
|
758
|
-
return cmd;
|
|
759
|
-
return selectWindowsCommandCandidate(cmd, lookup.stdout);
|
|
760
|
-
}
|
|
761
|
-
export function windowsShellEscapeArg(arg) {
|
|
762
|
-
let escaped = String(arg);
|
|
763
|
-
escaped = escaped.replace(/(\\*)"/g, "$1$1\\\"");
|
|
764
|
-
escaped = escaped.replace(/(\\*)$/g, "$1$1");
|
|
765
|
-
escaped = `"${escaped}"`;
|
|
766
|
-
return escaped.replace(/([()[\]{}^=;!'+,`~&|<>%" *?])/g, "^$1");
|
|
767
|
-
}
|
|
768
|
-
export function windowsCmdShimInvocation(cmdPath, args, comspec = "cmd.exe") {
|
|
769
|
-
return {
|
|
770
|
-
cmd: comspec,
|
|
771
|
-
args: ["/d", "/c", cmdPath, ...args],
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
function windowsCommandInvocation(cmd, args, cwd) {
|
|
775
|
-
const resolved = resolveWindowsCommand(cmd, cwd);
|
|
776
|
-
if (/\.(?:cmd|bat)$/i.test(resolved))
|
|
777
|
-
return windowsCmdShimInvocation(resolved, args);
|
|
778
|
-
return { cmd: resolved, args };
|
|
779
|
-
}
|
|
780
|
-
export function commandExists(cmd, opts = {}) {
|
|
781
|
-
const lookup = commandLookupInvocation(cmd, opts.platform);
|
|
782
|
-
const proc = spawnSync(lookup.cmd, lookup.args, {
|
|
783
|
-
cwd: opts.cwd,
|
|
784
|
-
encoding: "utf8",
|
|
785
|
-
timeout: 5_000,
|
|
786
|
-
shell: lookup.shell,
|
|
787
|
-
});
|
|
788
|
-
return !proc.error && proc.status === 0;
|
|
789
|
-
}
|
|
790
|
-
export function deepEqual(a, b) {
|
|
791
|
-
return stableJson(a) === stableJson(b);
|
|
792
|
-
}
|