@peterxiaoyang/superspec 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.
- package/README.md +47 -0
- package/adapters/codex/agents/architect.toml +157 -0
- package/adapters/codex/agents/code-reviewer.toml +175 -0
- package/adapters/codex/agents/critic.toml +114 -0
- package/adapters/codex/agents/test-engineer.toml +163 -0
- package/adapters/codex/agents/verifier.toml +119 -0
- package/adapters/codex/install-map.json +81 -0
- package/bin/launch.js +37 -0
- package/bin/superspec-guard.js +4 -0
- package/bin/superspec-init.js +4 -0
- package/bin/superspec.js +4 -0
- package/dist/src/archive.d.ts +23 -0
- package/dist/src/archive.js +428 -0
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +20 -0
- package/dist/src/cli_args.d.ts +12 -0
- package/dist/src/cli_args.js +146 -0
- package/dist/src/core.d.ts +19 -0
- package/dist/src/core.js +357 -0
- package/dist/src/disclosure.d.ts +35 -0
- package/dist/src/disclosure.js +671 -0
- package/dist/src/evidence.d.ts +28 -0
- package/dist/src/evidence.js +849 -0
- package/dist/src/gates.d.ts +16 -0
- package/dist/src/gates.js +1470 -0
- package/dist/src/git.d.ts +8 -0
- package/dist/src/git.js +112 -0
- package/dist/src/init_cli.d.ts +2 -0
- package/dist/src/init_cli.js +145 -0
- package/dist/src/install_engine.d.ts +54 -0
- package/dist/src/install_engine.js +351 -0
- package/dist/src/invariants.d.ts +16 -0
- package/dist/src/invariants.js +363 -0
- package/dist/src/openspec.d.ts +18 -0
- package/dist/src/openspec.js +157 -0
- package/dist/src/paths.d.ts +22 -0
- package/dist/src/paths.js +203 -0
- package/dist/src/project_init.d.ts +4 -0
- package/dist/src/project_init.js +161 -0
- package/dist/src/state.d.ts +37 -0
- package/dist/src/state.js +464 -0
- package/dist/src/tasks.d.ts +23 -0
- package/dist/src/tasks.js +225 -0
- package/dist/src/util.d.ts +120 -0
- package/dist/src/util.js +442 -0
- package/dist/superspec.d.ts +4 -0
- package/dist/superspec.js +57 -0
- package/dist/superspec_guard.d.ts +4 -0
- package/dist/superspec_guard.js +19 -0
- package/dist/superspec_init.d.ts +2 -0
- package/dist/superspec_init.js +17 -0
- package/package.json +63 -0
- package/schemas/install-manifest.schema.json +80 -0
- package/templates/sidecar/archive-preservation.json +11 -0
- package/templates/sidecar/business-invariants.md +38 -0
- package/templates/sidecar/config.yaml +13 -0
- package/templates/sidecar/discovery.md +24 -0
- package/templates/sidecar/test-contract.md +26 -0
- package/templates/workflow/prompts/architect.md +113 -0
- package/templates/workflow/prompts/code-reviewer.md +141 -0
- package/templates/workflow/prompts/critic.md +80 -0
- package/templates/workflow/prompts/test-engineer.md +130 -0
- package/templates/workflow/prompts/verifier.md +85 -0
- package/templates/workflow/skills/superspec-apply/SKILL.md +72 -0
- package/templates/workflow/skills/superspec-archive/SKILL.md +41 -0
- package/templates/workflow/skills/superspec-explore/SKILL.md +70 -0
- package/templates/workflow/skills/superspec-propose/SKILL.md +79 -0
- package/templates/workflow/skills/superspec-review/SKILL.md +237 -0
package/dist/src/util.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
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
|
+
export const SCHEMA_VERSION = 1;
|
|
6
|
+
export const GUARD_VERSION = "superspec-guard@1";
|
|
7
|
+
export const CONFIG_FILENAME = "config.yaml";
|
|
8
|
+
export const STATE_FILENAME = "superspec-state.json";
|
|
9
|
+
export const STATE_LOCK_FILENAME = "superspec-state.lock";
|
|
10
|
+
export const PROJECT_CONFIG_ALIASES = [
|
|
11
|
+
".superspec.yaml",
|
|
12
|
+
".superspec.yml",
|
|
13
|
+
"superspec.yaml",
|
|
14
|
+
"superspec.yml",
|
|
15
|
+
"superspec.json",
|
|
16
|
+
".superspecrc",
|
|
17
|
+
".superspec/config.json",
|
|
18
|
+
];
|
|
19
|
+
export const CHANGE_CONFIG_ALIASES = PROJECT_CONFIG_ALIASES;
|
|
20
|
+
export const STATE_ALIASES = [
|
|
21
|
+
".superspec/state.json",
|
|
22
|
+
".superspec/state.lock",
|
|
23
|
+
".superspec/superspec_state.json",
|
|
24
|
+
".superspec/superspec-state.yaml",
|
|
25
|
+
];
|
|
26
|
+
export const REQUIRED_SIDECAR_DIRS = [
|
|
27
|
+
"artifacts",
|
|
28
|
+
"evidence/discovery",
|
|
29
|
+
"evidence/design",
|
|
30
|
+
"evidence/invariants",
|
|
31
|
+
"evidence/test-contract",
|
|
32
|
+
"evidence/tasks",
|
|
33
|
+
"evidence/red",
|
|
34
|
+
"evidence/green",
|
|
35
|
+
"evidence/reviews",
|
|
36
|
+
"evidence/verification",
|
|
37
|
+
"evidence/archive",
|
|
38
|
+
"handoffs",
|
|
39
|
+
"reports",
|
|
40
|
+
"raw",
|
|
41
|
+
];
|
|
42
|
+
export const BUILTIN_CONFIG = {
|
|
43
|
+
preset: "full",
|
|
44
|
+
commands: {},
|
|
45
|
+
roles: {},
|
|
46
|
+
rules: {},
|
|
47
|
+
trust: { v1_evidence: "audit-only" },
|
|
48
|
+
archive: {},
|
|
49
|
+
};
|
|
50
|
+
export const ALLOWED_CONFIG_KEYS = new Set(Object.keys(BUILTIN_CONFIG));
|
|
51
|
+
export const EVIDENCE_STATUSES = new Set(["pass", "fail", "blocked", "superseded"]);
|
|
52
|
+
// FIX-7 (audit C-3): kind whitelist assembled from every kind the guard recognizes plus the
|
|
53
|
+
// conventional role-report kinds. A typo'd kind must fail loudly (evidence_unknown_kind)
|
|
54
|
+
// instead of silently surfacing as "missing evidence" at some downstream gate.
|
|
55
|
+
export const EVIDENCE_KINDS = new Set([
|
|
56
|
+
"review",
|
|
57
|
+
"subagent_report",
|
|
58
|
+
"workflow_review",
|
|
59
|
+
"source_guidance",
|
|
60
|
+
"main_adjudication",
|
|
61
|
+
"verification_review",
|
|
62
|
+
"final_test",
|
|
63
|
+
"test_run",
|
|
64
|
+
"alternative_verification",
|
|
65
|
+
"manual_verification",
|
|
66
|
+
"task_reopen",
|
|
67
|
+
"task_reopen_resolved",
|
|
68
|
+
"human_confirmation",
|
|
69
|
+
"superseded",
|
|
70
|
+
// Disclosure fixed-point loop (DISC Phase 1): user-visible review disclosure evidence.
|
|
71
|
+
"main_review_digest",
|
|
72
|
+
"user_review_decision",
|
|
73
|
+
"review_standing_authorization",
|
|
74
|
+
]);
|
|
75
|
+
// FIX-7 (audit C-3): gates where the guard consumes human_confirmation evidence.
|
|
76
|
+
// A confirmation recorded against any other gate is unreachable and therefore a mistake.
|
|
77
|
+
// FIX-8 (audit A-5) adds the previously anchor-less human pause points:
|
|
78
|
+
// apply isolation choice, apply-phase scope expansion, and verify-failure disposition.
|
|
79
|
+
export const HUMAN_CONFIRMATION_GATES = new Set([
|
|
80
|
+
"design_complete",
|
|
81
|
+
"invariants_reviewed",
|
|
82
|
+
"archive_ready",
|
|
83
|
+
"preset_upgrade",
|
|
84
|
+
"branch_handling",
|
|
85
|
+
"apply_isolation",
|
|
86
|
+
"scope_expansion",
|
|
87
|
+
"verify_failure_handling",
|
|
88
|
+
]);
|
|
89
|
+
export const NO_TDD_REASONS = new Set([
|
|
90
|
+
"documentation-only",
|
|
91
|
+
"configuration-only",
|
|
92
|
+
"test-only-refactor",
|
|
93
|
+
"mechanical-rename",
|
|
94
|
+
"generated-artifact-only",
|
|
95
|
+
"non-executable-spec-change",
|
|
96
|
+
]);
|
|
97
|
+
export const TDD_MODES = new Set(["new-behavior", "behavior-preserving-refactor", "hotfix"]);
|
|
98
|
+
export const ROLE_EVIDENCE_FIELDS = ["agent_role", "agent_id", "prompt_ref", "output_ref", "source_anchors", "target_refs"];
|
|
99
|
+
export const SELF_REVIEW_MARKERS = new Set(["main", "main-thread", "current-agent", "self", "lead", "leader", "orchestrator", "codex_exec"]);
|
|
100
|
+
export const REVIEW_EVIDENCE_REQUIRED_FIELDS = ["base_ref", "head_ref", "reviewed_files"];
|
|
101
|
+
export const VERIFY_EVIDENCE_REQUIRED_FIELDS = ["openspec_validate_ref", "task_matrix_ref", "invariant_matrix_ref", "scope_drift_ref", "test_evidence_refs"];
|
|
102
|
+
export const REVIEW_GUIDANCE_ROLES = ["code-reviewer", "architect", "critic"];
|
|
103
|
+
export const FINAL_VERIFICATION_ROLES = ["verifier", "critic"];
|
|
104
|
+
export const SOURCE_GUIDANCE_REQUIRED_FIELDS = ["source_refs", "required_load_refs", "required_claim_ids"];
|
|
105
|
+
export const MAIN_ADJUDICATION_REQUIRED_FIELDS = ["output_ref", "source_evidence_refs", "verification_evidence_refs", "loaded_refs", "claim_adjudications", "finding_adjudications"];
|
|
106
|
+
export const MAIN_ADJUDICATION_DECISIONS = ["allow", "request_changes"];
|
|
107
|
+
export const REQUEST_CHANGES_ROUTES = ["reopen_tasks", "change_update"];
|
|
108
|
+
export const CLAIM_ADJUDICATION_DECISIONS = ["accept", "reject", "needs_fix"];
|
|
109
|
+
export const FINDING_ADJUDICATION_DECISIONS = ["dismissed", "accepted_fixed", "accepted_deviation", "needs_fix"];
|
|
110
|
+
export const FINAL_TEST_REQUIRED_FIELDS = ["test_command", "output_ref"];
|
|
111
|
+
export const TASK_REOPEN_REQUIRED_FIELDS = [
|
|
112
|
+
"task_id",
|
|
113
|
+
"reopen_id",
|
|
114
|
+
"source_adjudication_evidence_id",
|
|
115
|
+
"source_guidance_evidence_id",
|
|
116
|
+
"violated_test_ids",
|
|
117
|
+
"violated_requirement_refs",
|
|
118
|
+
"invalidated_completion_evidence_ids",
|
|
119
|
+
"required_supersede_evidence_ids",
|
|
120
|
+
"completion_invalidity_class",
|
|
121
|
+
"scope_expansion",
|
|
122
|
+
"why_completion_invalid",
|
|
123
|
+
"required_fix",
|
|
124
|
+
"before_tasks_sha256",
|
|
125
|
+
"after_tasks_sha256",
|
|
126
|
+
];
|
|
127
|
+
export const TASK_REOPEN_RESOLVED_REQUIRED_FIELDS = [
|
|
128
|
+
"reopen_evidence_id",
|
|
129
|
+
"reopen_id",
|
|
130
|
+
"task_id",
|
|
131
|
+
"successor_completion_evidence_ids",
|
|
132
|
+
"after_tasks_sha256",
|
|
133
|
+
];
|
|
134
|
+
export const TASK_REOPEN_INVALIDITY_CLASSES = ["insufficient_completion_evidence"];
|
|
135
|
+
export const FORBIDDEN_FIELDS = new Set([
|
|
136
|
+
"current",
|
|
137
|
+
"current_phase",
|
|
138
|
+
"current_stage",
|
|
139
|
+
"stage",
|
|
140
|
+
"stage_order",
|
|
141
|
+
"allowed_next",
|
|
142
|
+
"completed_phases",
|
|
143
|
+
"openspec_artifact_done",
|
|
144
|
+
"artifact_status",
|
|
145
|
+
]);
|
|
146
|
+
export const OPENSPEC_ARTIFACTS = new Set(["proposal", "specs", "design", "tasks"]);
|
|
147
|
+
export const REQUIRED_OPENSPEC_CODEX_SKILLS = [
|
|
148
|
+
"openspec-explore",
|
|
149
|
+
"openspec-propose",
|
|
150
|
+
"openspec-apply-change",
|
|
151
|
+
"openspec-archive-change",
|
|
152
|
+
];
|
|
153
|
+
// D4 (audit G-2): SuperSpec's own workflow skills are part of the init health surface — a deleted
|
|
154
|
+
// or renamed superspec-* skill must be visible at check-init, not discovered mid-workflow.
|
|
155
|
+
export const REQUIRED_SUPERSPEC_WORKFLOW_SKILLS = [
|
|
156
|
+
"superspec-explore",
|
|
157
|
+
"superspec-propose",
|
|
158
|
+
"superspec-apply",
|
|
159
|
+
"superspec-review",
|
|
160
|
+
"superspec-archive",
|
|
161
|
+
];
|
|
162
|
+
export const REQUIRED_SUPERSPEC_AGENT_ROLES = [
|
|
163
|
+
"architect",
|
|
164
|
+
"critic",
|
|
165
|
+
"test-engineer",
|
|
166
|
+
"code-reviewer",
|
|
167
|
+
"verifier",
|
|
168
|
+
];
|
|
169
|
+
export const REQUIRED_OPENSPEC_CLI_SURFACES = [
|
|
170
|
+
["instructions", "--help"],
|
|
171
|
+
["archive", "--help"],
|
|
172
|
+
["validate", "--help"],
|
|
173
|
+
["status", "--help"],
|
|
174
|
+
];
|
|
175
|
+
export const ARTIFACT_ENTER_GATE = {
|
|
176
|
+
proposal: "explore_complete",
|
|
177
|
+
// DISC Phase 2: specs/design authoring cannot start before the proposal critic review
|
|
178
|
+
// has been run and its findings disclosed.
|
|
179
|
+
specs: "proposal_reviewed",
|
|
180
|
+
design: "proposal_reviewed",
|
|
181
|
+
tasks: "test_contract_drafted",
|
|
182
|
+
};
|
|
183
|
+
export const ROUTE_ORDER = {
|
|
184
|
+
init: 0,
|
|
185
|
+
explore: 1,
|
|
186
|
+
propose: 2,
|
|
187
|
+
apply: 3,
|
|
188
|
+
review: 4,
|
|
189
|
+
archive: 5,
|
|
190
|
+
};
|
|
191
|
+
export const ROUTE_ALIASES = {
|
|
192
|
+
start: "init",
|
|
193
|
+
proposal: "propose",
|
|
194
|
+
specs: "propose",
|
|
195
|
+
design: "propose",
|
|
196
|
+
tasks: "propose",
|
|
197
|
+
"test-contract": "propose",
|
|
198
|
+
"test-contract-drafted": "propose",
|
|
199
|
+
"test-contract-honored": "propose",
|
|
200
|
+
"business-invariants": "propose",
|
|
201
|
+
"invariants-reviewed": "propose",
|
|
202
|
+
"tasks-complete": "propose",
|
|
203
|
+
"apply-ready": "propose",
|
|
204
|
+
verify: "review",
|
|
205
|
+
verification: "review",
|
|
206
|
+
"archive-ready": "archive",
|
|
207
|
+
archived: "archive",
|
|
208
|
+
recompute: "init",
|
|
209
|
+
};
|
|
210
|
+
export const GATE_ALIASES = {
|
|
211
|
+
"propose.explore_linked": "explore_complete",
|
|
212
|
+
"propose.proposal_reviewed": "proposal_reviewed",
|
|
213
|
+
"propose.design_reviewed": "design_complete",
|
|
214
|
+
"propose.invariants_reviewed": "invariants_reviewed",
|
|
215
|
+
"propose.test_plan_drafted": "test_contract_drafted",
|
|
216
|
+
"propose.tasks_mapped": "tasks_complete",
|
|
217
|
+
"propose.apply_ready": "propose_complete",
|
|
218
|
+
apply_ready: "propose_complete",
|
|
219
|
+
};
|
|
220
|
+
export const GATE_ROUTE = {
|
|
221
|
+
explore_complete: "explore",
|
|
222
|
+
proposal_reviewed: "propose",
|
|
223
|
+
design_complete: "propose",
|
|
224
|
+
invariants_reviewed: "propose",
|
|
225
|
+
test_contract_drafted: "propose",
|
|
226
|
+
test_contract_honored: "propose",
|
|
227
|
+
tasks_complete: "propose",
|
|
228
|
+
propose_complete: "propose",
|
|
229
|
+
review_complete: "review",
|
|
230
|
+
verify_complete: "review",
|
|
231
|
+
archive_ready: "archive",
|
|
232
|
+
};
|
|
233
|
+
export class GuardError extends Error {
|
|
234
|
+
}
|
|
235
|
+
export const runtime = {};
|
|
236
|
+
export function reason(code, message, refs = null) {
|
|
237
|
+
return { code, message, refs: refs ?? [] };
|
|
238
|
+
}
|
|
239
|
+
export function pinned_ref_key(item) {
|
|
240
|
+
return `${String(item.path)}\u0000${String(item.blob_sha)}`;
|
|
241
|
+
}
|
|
242
|
+
export function trustWarnings() {
|
|
243
|
+
return [
|
|
244
|
+
"v1 evidence is audit-only/self-reported unless explicitly backed by OpenSpec facts",
|
|
245
|
+
"v1 role evidence cannot mechanically prove a native subagent ran; it only checks schema, freshness, output refs, and non-direct authorship markers",
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
export function allow(change, gate, opts = {}) {
|
|
249
|
+
return {
|
|
250
|
+
allowed: true,
|
|
251
|
+
decision: "allow",
|
|
252
|
+
change_id: change,
|
|
253
|
+
gate,
|
|
254
|
+
task_id: opts.task_id ?? null,
|
|
255
|
+
openspec_status_summary: opts.openspec_summary ?? {},
|
|
256
|
+
superspec_gate_summary: opts.gate_summary ?? {},
|
|
257
|
+
block_reasons: [],
|
|
258
|
+
next_allowed_actions: [],
|
|
259
|
+
trust_warnings: trustWarnings(),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
export function block(change, gate, reasons, opts = {}) {
|
|
263
|
+
return {
|
|
264
|
+
allowed: false,
|
|
265
|
+
decision: "block",
|
|
266
|
+
change_id: change,
|
|
267
|
+
gate,
|
|
268
|
+
task_id: opts.task_id ?? null,
|
|
269
|
+
openspec_status_summary: opts.openspec_summary ?? {},
|
|
270
|
+
superspec_gate_summary: opts.gate_summary ?? {},
|
|
271
|
+
block_reasons: reasons,
|
|
272
|
+
next_allowed_actions: opts.next_actions ?? [],
|
|
273
|
+
trust_warnings: trustWarnings(),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
export function printDecision(decision) {
|
|
277
|
+
process.stdout.write(`${JSON.stringify(decision, null, 2)}\n`);
|
|
278
|
+
}
|
|
279
|
+
export function runCommand(cmd, args, opts = {}) {
|
|
280
|
+
const platform = opts.platform ?? process.platform;
|
|
281
|
+
const invocation = platform === "win32" ? windowsCommandInvocation(cmd, args, opts.cwd) : { cmd, args };
|
|
282
|
+
const result = spawnSync(invocation.cmd, invocation.args, {
|
|
283
|
+
cwd: opts.cwd,
|
|
284
|
+
encoding: "utf8",
|
|
285
|
+
timeout: opts.timeout,
|
|
286
|
+
});
|
|
287
|
+
return {
|
|
288
|
+
status: result.status,
|
|
289
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
290
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
291
|
+
error: result.error,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
export function sha256_text(text) {
|
|
295
|
+
return `sha256:${createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex")}`;
|
|
296
|
+
}
|
|
297
|
+
export function sha256_file(filePath) {
|
|
298
|
+
if (!existsSync(filePath) || !statSync(filePath).isFile())
|
|
299
|
+
return null;
|
|
300
|
+
return `sha256:${createHash("sha256").update(readFileSync(filePath)).digest("hex")}`;
|
|
301
|
+
}
|
|
302
|
+
export function fingerprint_obj(obj) {
|
|
303
|
+
return sha256_text(stableJson(obj));
|
|
304
|
+
}
|
|
305
|
+
export function safe_within(base, candidate) {
|
|
306
|
+
if (isAbsolute(candidate) || candidate.split(/[\\/]+/).includes(".."))
|
|
307
|
+
return null;
|
|
308
|
+
const baseReal = realpathMaybe(base);
|
|
309
|
+
const resolved = resolve(base, candidate);
|
|
310
|
+
const candidateReal = resolveExistingPrefix(resolved);
|
|
311
|
+
const rel = relative(baseReal, candidateReal);
|
|
312
|
+
if (rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)))
|
|
313
|
+
return candidateReal;
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
export function now() {
|
|
317
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
318
|
+
}
|
|
319
|
+
export function isObject(value) {
|
|
320
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
321
|
+
}
|
|
322
|
+
export function repr(value) {
|
|
323
|
+
if (typeof value === "string")
|
|
324
|
+
return `'${value}'`;
|
|
325
|
+
if (value === null)
|
|
326
|
+
return "None";
|
|
327
|
+
if (value === undefined)
|
|
328
|
+
return "None";
|
|
329
|
+
return JSON.stringify(value);
|
|
330
|
+
}
|
|
331
|
+
export function renderList(items) {
|
|
332
|
+
return `[${items.map((item) => (typeof item === "string" ? `'${item}'` : String(item))).join(", ")}]`;
|
|
333
|
+
}
|
|
334
|
+
function stableJson(value) {
|
|
335
|
+
if (value === null)
|
|
336
|
+
return "null";
|
|
337
|
+
if (typeof value === "boolean")
|
|
338
|
+
return value ? "true" : "false";
|
|
339
|
+
if (typeof value === "number")
|
|
340
|
+
return JSON.stringify(value);
|
|
341
|
+
if (typeof value === "string")
|
|
342
|
+
return JSON.stringify(value);
|
|
343
|
+
if (Array.isArray(value))
|
|
344
|
+
return `[${value.map((item) => stableJson(item)).join(", ")}]`;
|
|
345
|
+
if (isObject(value)) {
|
|
346
|
+
const parts = Object.keys(value).sort().map((key) => `${JSON.stringify(key)}: ${stableJson(value[key])}`);
|
|
347
|
+
return `{${parts.join(", ")}}`;
|
|
348
|
+
}
|
|
349
|
+
return JSON.stringify(value);
|
|
350
|
+
}
|
|
351
|
+
function realpathMaybe(filePath) {
|
|
352
|
+
try {
|
|
353
|
+
return realpathSync(filePath);
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
return resolve(filePath);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function resolveExistingPrefix(filePath) {
|
|
360
|
+
const abs = resolve(filePath);
|
|
361
|
+
if (existsSync(abs))
|
|
362
|
+
return realpathMaybe(abs);
|
|
363
|
+
const tail = [];
|
|
364
|
+
let cur = abs;
|
|
365
|
+
while (!existsSync(cur)) {
|
|
366
|
+
const parent = dirname(cur);
|
|
367
|
+
if (parent === cur)
|
|
368
|
+
return abs;
|
|
369
|
+
tail.unshift(cur.split(sep).at(-1) ?? "");
|
|
370
|
+
cur = parent;
|
|
371
|
+
}
|
|
372
|
+
return resolve(realpathMaybe(cur), ...tail);
|
|
373
|
+
}
|
|
374
|
+
export function walkFiles(root) {
|
|
375
|
+
const out = [];
|
|
376
|
+
if (!existsSync(root))
|
|
377
|
+
return out;
|
|
378
|
+
for (const name of readdirSync(root)) {
|
|
379
|
+
const item = join(root, name);
|
|
380
|
+
const stat = lstatSync(item);
|
|
381
|
+
if (stat.isSymbolicLink())
|
|
382
|
+
continue;
|
|
383
|
+
if (stat.isDirectory())
|
|
384
|
+
out.push(...walkFiles(item));
|
|
385
|
+
// Hardlinked files can alias content outside the change root; skip them like symlinks.
|
|
386
|
+
else if (stat.isFile() && stat.nlink <= 1)
|
|
387
|
+
out.push(item);
|
|
388
|
+
}
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
export function toPosix(pathValue) {
|
|
392
|
+
return pathValue.split(sep).join("/");
|
|
393
|
+
}
|
|
394
|
+
export function commandLookupInvocation(cmd, platform = process.platform) {
|
|
395
|
+
if (platform === "win32")
|
|
396
|
+
return { cmd: "where.exe", args: [cmd], shell: false };
|
|
397
|
+
return { cmd: "sh", args: ["-c", `command -v ${cmd}`], shell: false };
|
|
398
|
+
}
|
|
399
|
+
function resolveWindowsCommand(cmd, cwd) {
|
|
400
|
+
if (cmd.includes("\\") || cmd.includes("/") || extname(cmd))
|
|
401
|
+
return cmd;
|
|
402
|
+
const lookup = spawnSync("where.exe", [cmd], {
|
|
403
|
+
cwd,
|
|
404
|
+
encoding: "utf8",
|
|
405
|
+
timeout: 5_000,
|
|
406
|
+
});
|
|
407
|
+
if (lookup.status !== 0 || typeof lookup.stdout !== "string")
|
|
408
|
+
return cmd;
|
|
409
|
+
return lookup.stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? cmd;
|
|
410
|
+
}
|
|
411
|
+
export function windowsShellEscapeArg(arg) {
|
|
412
|
+
let escaped = String(arg);
|
|
413
|
+
escaped = escaped.replace(/(\\*)"/g, "$1$1\\\"");
|
|
414
|
+
escaped = escaped.replace(/(\\*)$/g, "$1$1");
|
|
415
|
+
escaped = `"${escaped}"`;
|
|
416
|
+
return escaped.replace(/([()[\]{}^=;!'+,`~&|<>%" *?])/g, "^$1");
|
|
417
|
+
}
|
|
418
|
+
export function windowsCmdShimInvocation(cmdPath, args, comspec = "cmd.exe") {
|
|
419
|
+
return {
|
|
420
|
+
cmd: comspec,
|
|
421
|
+
args: ["/d", "/s", "/c", [windowsShellEscapeArg(cmdPath), ...args.map(windowsShellEscapeArg)].join(" ")],
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function windowsCommandInvocation(cmd, args, cwd) {
|
|
425
|
+
const resolved = resolveWindowsCommand(cmd, cwd);
|
|
426
|
+
if (/\.(?:cmd|bat)$/i.test(resolved))
|
|
427
|
+
return windowsCmdShimInvocation(resolved, args);
|
|
428
|
+
return { cmd: resolved, args };
|
|
429
|
+
}
|
|
430
|
+
export function commandExists(cmd, opts = {}) {
|
|
431
|
+
const lookup = commandLookupInvocation(cmd, opts.platform);
|
|
432
|
+
const proc = spawnSync(lookup.cmd, lookup.args, {
|
|
433
|
+
cwd: opts.cwd,
|
|
434
|
+
encoding: "utf8",
|
|
435
|
+
timeout: 5_000,
|
|
436
|
+
shell: lookup.shell,
|
|
437
|
+
});
|
|
438
|
+
return !proc.error && proc.status === 0;
|
|
439
|
+
}
|
|
440
|
+
export function deepEqual(a, b) {
|
|
441
|
+
return stableJson(a) === stableJson(b);
|
|
442
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
export * from "./src/init_cli.js";
|
|
5
|
+
export * from "./src/cli.js";
|
|
6
|
+
import { main } from "./src/cli.js";
|
|
7
|
+
import { main_init_async } from "./src/init_cli.js";
|
|
8
|
+
function realpathMaybe(filePath) {
|
|
9
|
+
try {
|
|
10
|
+
return realpathSync(filePath);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return resolve(filePath);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function help() {
|
|
17
|
+
return [
|
|
18
|
+
"usage: superspec <command> [options]",
|
|
19
|
+
"",
|
|
20
|
+
"commands:",
|
|
21
|
+
" init install SuperSpec Codex surfaces (asks project/user; default project)",
|
|
22
|
+
" update update manifest-managed SuperSpec surfaces",
|
|
23
|
+
" uninstall remove manifest-managed SuperSpec surfaces",
|
|
24
|
+
" guard run the SuperSpec guard command surface",
|
|
25
|
+
"",
|
|
26
|
+
"examples:",
|
|
27
|
+
" superspec init --scope project",
|
|
28
|
+
" superspec init --scope user",
|
|
29
|
+
" superspec guard check-init --change <change>",
|
|
30
|
+
"",
|
|
31
|
+
].join("\n");
|
|
32
|
+
}
|
|
33
|
+
export async function main_superspec(argv = process.argv.slice(2)) {
|
|
34
|
+
const [command, ...rest] = argv;
|
|
35
|
+
if (command === undefined || command === "-h" || command === "--help") {
|
|
36
|
+
process.stdout.write(help());
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
if (command === "init")
|
|
40
|
+
return main_init_async(rest);
|
|
41
|
+
if (command === "update")
|
|
42
|
+
return main_init_async([...rest, "--update"]);
|
|
43
|
+
if (command === "uninstall")
|
|
44
|
+
return main_init_async([...rest, "--uninstall"]);
|
|
45
|
+
if (command === "guard")
|
|
46
|
+
return main(rest);
|
|
47
|
+
// Convenience fallback: `superspec check-init ...` behaves like `superspec guard check-init ...`.
|
|
48
|
+
if (command.startsWith("check-") || command === "status" || command === "recompute" || command === "init") {
|
|
49
|
+
return main(argv);
|
|
50
|
+
}
|
|
51
|
+
process.stderr.write(`superspec: unknown command ${JSON.stringify(command)}\n\n${help()}`);
|
|
52
|
+
return 2;
|
|
53
|
+
}
|
|
54
|
+
const currentFile = realpathMaybe(process.argv[1] ?? "");
|
|
55
|
+
if (currentFile === realpathMaybe(new URL(import.meta.url).pathname)) {
|
|
56
|
+
process.exitCode = await main_superspec();
|
|
57
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
export * from "./src/core.js";
|
|
5
|
+
export * from "./src/cli.js";
|
|
6
|
+
export * from "./src/cli_args.js";
|
|
7
|
+
import { main } from "./src/cli.js";
|
|
8
|
+
function realpathMaybe(filePath) {
|
|
9
|
+
try {
|
|
10
|
+
return realpathSync(filePath);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return resolve(filePath);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const currentFile = realpathMaybe(process.argv[1] ?? "");
|
|
17
|
+
if (currentFile === realpathMaybe(new URL(import.meta.url).pathname)) {
|
|
18
|
+
process.exitCode = main();
|
|
19
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
export * from "./src/init_cli.js";
|
|
5
|
+
import { main_init_async } from "./src/init_cli.js";
|
|
6
|
+
function realpathMaybe(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
return realpathSync(filePath);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return resolve(filePath);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const currentFile = realpathMaybe(process.argv[1] ?? "");
|
|
15
|
+
if (currentFile === realpathMaybe(new URL(import.meta.url).pathname)) {
|
|
16
|
+
process.exitCode = await main_init_async();
|
|
17
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@peterxiaoyang/superspec",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SuperSpec workflow package: guard runtime, generic workflow templates, and Codex adapter payload.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/PeterYaoYang/SuperSpec.git"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/PeterYaoYang/SuperSpec/issues"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/PeterYaoYang/SuperSpec#readme",
|
|
13
|
+
"private": false,
|
|
14
|
+
"type": "module",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20.19.0"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"superspec": "bin/superspec.js",
|
|
23
|
+
"superspec-guard": "bin/superspec-guard.js",
|
|
24
|
+
"superspec-init": "bin/superspec-init.js"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/superspec.d.ts",
|
|
29
|
+
"default": "./dist/superspec.js"
|
|
30
|
+
},
|
|
31
|
+
"./superspec_guard": {
|
|
32
|
+
"types": "./dist/superspec_guard.d.ts",
|
|
33
|
+
"default": "./dist/superspec_guard.js"
|
|
34
|
+
},
|
|
35
|
+
"./superspec_init": {
|
|
36
|
+
"types": "./dist/superspec_init.d.ts",
|
|
37
|
+
"default": "./dist/superspec_init.js"
|
|
38
|
+
},
|
|
39
|
+
"./templates/*": "./templates/*",
|
|
40
|
+
"./adapters/*": "./adapters/*",
|
|
41
|
+
"./schemas/*": "./schemas/*"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"README.md",
|
|
45
|
+
"bin",
|
|
46
|
+
"dist",
|
|
47
|
+
"templates",
|
|
48
|
+
"adapters",
|
|
49
|
+
"schemas"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "node build.js",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"test": "node --test tests/test_install_engine.test.ts tests/test_real_openspec_smoke.test.ts tests/test_superspec_guard.test.ts tests/test_superspec_skills.test.ts",
|
|
55
|
+
"prepack": "npm run build",
|
|
56
|
+
"prepublishOnly": "npm run build",
|
|
57
|
+
"pack:dry-run": "npm pack --dry-run"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^20.19.0",
|
|
61
|
+
"typescript": "^6.0.3"
|
|
62
|
+
}
|
|
63
|
+
}
|