@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/invariants.js
DELETED
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { reason, renderList, safe_within } from "./util.js";
|
|
3
|
-
import { sidecar_business_invariants_path, sidecar_test_contract_path } from "./paths.js";
|
|
4
|
-
import { live_pass } from "./evidence.js";
|
|
5
|
-
import { parse_test_contract_records, splitList } from "./tasks.js";
|
|
6
|
-
const INV_ID_RE = /\bINV-[A-Za-z0-9_-]+\b/g;
|
|
7
|
-
const REQUIRED_INVARIANT_FIELDS = [
|
|
8
|
-
"statement",
|
|
9
|
-
"scope",
|
|
10
|
-
"source_anchors",
|
|
11
|
-
"acceptance_refs",
|
|
12
|
-
"risk_refs",
|
|
13
|
-
"confidence",
|
|
14
|
-
"enforcement_level",
|
|
15
|
-
"test_refs_or_review_only_reason",
|
|
16
|
-
"verification",
|
|
17
|
-
];
|
|
18
|
-
const CONFIDENCE_VALUES = new Set(["confirmed", "source-backed", "inferred", "uncertain"]);
|
|
19
|
-
const ENFORCEMENT_VALUES = new Set(["automated-test", "review-checklist", "human-confirmation", "advisory"]);
|
|
20
|
-
function readTextIfFile(filePath) {
|
|
21
|
-
if (!existsSync(filePath) || !statSync(filePath).isFile())
|
|
22
|
-
return "";
|
|
23
|
-
return readFileSync(filePath, "utf8");
|
|
24
|
-
}
|
|
25
|
-
function parseMarkdownRow(line) {
|
|
26
|
-
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
27
|
-
}
|
|
28
|
-
function isSeparatorRow(cells) {
|
|
29
|
-
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
30
|
-
}
|
|
31
|
-
function normalizeHeader(header) {
|
|
32
|
-
return header.trim().toLowerCase().replace(/\s+/g, "_");
|
|
33
|
-
}
|
|
34
|
-
function tableLinesAfterHeading(text, heading) {
|
|
35
|
-
const lines = text.split(/\r?\n/);
|
|
36
|
-
const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, "i");
|
|
37
|
-
const start = lines.findIndex((line) => headingRe.test(line.trim()));
|
|
38
|
-
if (start < 0)
|
|
39
|
-
return [];
|
|
40
|
-
const table = [];
|
|
41
|
-
let started = false;
|
|
42
|
-
for (const line of lines.slice(start + 1)) {
|
|
43
|
-
const trimmed = line.trim();
|
|
44
|
-
if (!trimmed && !started)
|
|
45
|
-
continue;
|
|
46
|
-
if (!trimmed.startsWith("|")) {
|
|
47
|
-
if (started)
|
|
48
|
-
break;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
started = true;
|
|
52
|
-
table.push(line);
|
|
53
|
-
}
|
|
54
|
-
return table;
|
|
55
|
-
}
|
|
56
|
-
export function business_invariants_text(changeRoot) {
|
|
57
|
-
return readTextIfFile(sidecar_business_invariants_path(changeRoot));
|
|
58
|
-
}
|
|
59
|
-
export function parse_business_invariant_records(changeRoot) {
|
|
60
|
-
const text = business_invariants_text(changeRoot);
|
|
61
|
-
const table = tableLinesAfterHeading(text, "Invariants");
|
|
62
|
-
if (table.length < 3)
|
|
63
|
-
return [];
|
|
64
|
-
const headers = parseMarkdownRow(table[0]).map(normalizeHeader);
|
|
65
|
-
const records = [];
|
|
66
|
-
for (const line of table.slice(1)) {
|
|
67
|
-
const cells = parseMarkdownRow(line);
|
|
68
|
-
if (isSeparatorRow(cells))
|
|
69
|
-
continue;
|
|
70
|
-
const record = {};
|
|
71
|
-
headers.forEach((header, idx) => {
|
|
72
|
-
record[header] = cells[idx] ?? "";
|
|
73
|
-
});
|
|
74
|
-
records.push(record);
|
|
75
|
-
}
|
|
76
|
-
return records;
|
|
77
|
-
}
|
|
78
|
-
function invariantId(record) {
|
|
79
|
-
return String(record["inv-id"] ?? record.inv_id ?? record.id ?? "").trim();
|
|
80
|
-
}
|
|
81
|
-
function isCreatedAfterImplementation(record) {
|
|
82
|
-
return ["true", "yes", "1"].includes(String(record.created_after_implementation ?? "").trim().toLowerCase());
|
|
83
|
-
}
|
|
84
|
-
function isHardInvariant(record) {
|
|
85
|
-
const confidence = String(record.confidence ?? "").trim();
|
|
86
|
-
const enforcement = String(record.enforcement_level ?? "").trim();
|
|
87
|
-
return (confidence === "confirmed" || confidence === "source-backed") && enforcement !== "advisory";
|
|
88
|
-
}
|
|
89
|
-
function enforcementLevel(record) {
|
|
90
|
-
return String(record.enforcement_level ?? "").trim();
|
|
91
|
-
}
|
|
92
|
-
export function business_invariant_ids(changeRoot) {
|
|
93
|
-
const records = parse_business_invariant_records(changeRoot);
|
|
94
|
-
if (records.length > 0)
|
|
95
|
-
return new Set(records.map(invariantId).filter(Boolean));
|
|
96
|
-
return new Set([...business_invariants_text(changeRoot).matchAll(INV_ID_RE)].map((match) => match[0]));
|
|
97
|
-
}
|
|
98
|
-
export function hard_business_invariant_ids(changeRoot) {
|
|
99
|
-
const hard = new Set();
|
|
100
|
-
for (const record of parse_business_invariant_records(changeRoot)) {
|
|
101
|
-
const id = invariantId(record);
|
|
102
|
-
if (!id)
|
|
103
|
-
continue;
|
|
104
|
-
if (isHardInvariant(record) && !isCreatedAfterImplementation(record))
|
|
105
|
-
hard.add(id);
|
|
106
|
-
}
|
|
107
|
-
return hard;
|
|
108
|
-
}
|
|
109
|
-
export function automated_hard_business_invariant_ids(changeRoot) {
|
|
110
|
-
const hard = new Set();
|
|
111
|
-
for (const record of parse_business_invariant_records(changeRoot)) {
|
|
112
|
-
const id = invariantId(record);
|
|
113
|
-
if (!id)
|
|
114
|
-
continue;
|
|
115
|
-
if (isHardInvariant(record) && !isCreatedAfterImplementation(record) && enforcementLevel(record) === "automated-test")
|
|
116
|
-
hard.add(id);
|
|
117
|
-
}
|
|
118
|
-
return hard;
|
|
119
|
-
}
|
|
120
|
-
export function post_implementation_business_invariant_ids(changeRoot) {
|
|
121
|
-
const ids = new Set();
|
|
122
|
-
for (const record of parse_business_invariant_records(changeRoot)) {
|
|
123
|
-
const id = invariantId(record);
|
|
124
|
-
if (id && isCreatedAfterImplementation(record))
|
|
125
|
-
ids.add(id);
|
|
126
|
-
}
|
|
127
|
-
return ids;
|
|
128
|
-
}
|
|
129
|
-
export function human_confirmation_business_invariant_ids(changeRoot) {
|
|
130
|
-
const ids = new Set();
|
|
131
|
-
for (const record of parse_business_invariant_records(changeRoot)) {
|
|
132
|
-
const id = invariantId(record);
|
|
133
|
-
if (!id)
|
|
134
|
-
continue;
|
|
135
|
-
const enforcement = String(record.enforcement_level ?? "").trim();
|
|
136
|
-
if (enforcement === "human-confirmation")
|
|
137
|
-
ids.add(id);
|
|
138
|
-
}
|
|
139
|
-
return ids;
|
|
140
|
-
}
|
|
141
|
-
export function business_invariant_validation_reasons(changeRoot) {
|
|
142
|
-
const filePath = sidecar_business_invariants_path(changeRoot);
|
|
143
|
-
if (!existsSync(filePath) || !statSync(filePath).isFile()) {
|
|
144
|
-
return [reason("missing_business_invariants", "sidecar .superspec/artifacts/business-invariants.md missing")];
|
|
145
|
-
}
|
|
146
|
-
const text = business_invariants_text(changeRoot);
|
|
147
|
-
if (!text.trim())
|
|
148
|
-
return [reason("missing_business_invariants", "sidecar .superspec/artifacts/business-invariants.md empty")];
|
|
149
|
-
const records = parse_business_invariant_records(changeRoot);
|
|
150
|
-
const problems = [];
|
|
151
|
-
if (records.length === 0) {
|
|
152
|
-
problems.push(reason("invalid_business_invariants", "business-invariants.md has no parseable Invariants table"));
|
|
153
|
-
return problems;
|
|
154
|
-
}
|
|
155
|
-
const seen = new Set();
|
|
156
|
-
records.forEach((record, idx) => {
|
|
157
|
-
const row = idx + 1;
|
|
158
|
-
const id = invariantId(record);
|
|
159
|
-
if (!/^INV-[A-Za-z0-9_-]+$/.test(id)) {
|
|
160
|
-
problems.push(reason("invalid_invariant_ref", `business-invariants row ${row} has invalid INV-ID ${id || "<empty>"}`));
|
|
161
|
-
}
|
|
162
|
-
else if (seen.has(id)) {
|
|
163
|
-
problems.push(reason("invalid_invariant_ref", `duplicate business invariant id ${id}`));
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
seen.add(id);
|
|
167
|
-
}
|
|
168
|
-
for (const field of REQUIRED_INVARIANT_FIELDS) {
|
|
169
|
-
if (!String(record[field] ?? "").trim()) {
|
|
170
|
-
problems.push(reason("invalid_business_invariants", `${id || `row ${row}`}: missing ${field}`));
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
const confidence = String(record.confidence ?? "").trim();
|
|
174
|
-
if (confidence && !CONFIDENCE_VALUES.has(confidence)) {
|
|
175
|
-
problems.push(reason("invalid_business_invariants", `${id || `row ${row}`}: unsupported confidence ${confidence}`));
|
|
176
|
-
}
|
|
177
|
-
const enforcement = String(record.enforcement_level ?? "").trim();
|
|
178
|
-
if (enforcement && !ENFORCEMENT_VALUES.has(enforcement)) {
|
|
179
|
-
problems.push(reason("invalid_business_invariants", `${id || `row ${row}`}: unsupported enforcement_level ${enforcement}`));
|
|
180
|
-
}
|
|
181
|
-
if (isCreatedAfterImplementation(record) && isHardInvariant(record)) {
|
|
182
|
-
problems.push(reason("post_implementation_invariant_backfill", `${id || `row ${row}`}: created_after_implementation hard invariant cannot satisfy current RED/GREEN contract`));
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
return problems;
|
|
186
|
-
}
|
|
187
|
-
export function test_contract_invariant_ids(changeRoot) {
|
|
188
|
-
const refs = new Set();
|
|
189
|
-
for (const record of parse_test_contract_records(changeRoot)) {
|
|
190
|
-
for (const ref of record.invariant_refs)
|
|
191
|
-
refs.add(ref);
|
|
192
|
-
}
|
|
193
|
-
return refs;
|
|
194
|
-
}
|
|
195
|
-
export function task_invariant_refs(tasks) {
|
|
196
|
-
const refs = new Set();
|
|
197
|
-
for (const task of Object.values(tasks)) {
|
|
198
|
-
for (const ref of splitList(task.attrs.invariant_refs ?? ""))
|
|
199
|
-
refs.add(ref);
|
|
200
|
-
}
|
|
201
|
-
return refs;
|
|
202
|
-
}
|
|
203
|
-
export function evidence_invariant_refs(ev) {
|
|
204
|
-
const value = ev.invariant_refs;
|
|
205
|
-
if (Array.isArray(value))
|
|
206
|
-
return new Set(value.map((item) => String(item)).filter(Boolean));
|
|
207
|
-
if (typeof value === "string")
|
|
208
|
-
return new Set(splitList(value));
|
|
209
|
-
return new Set();
|
|
210
|
-
}
|
|
211
|
-
export function red_green_invariant_ids(evidences) {
|
|
212
|
-
const ids = new Set();
|
|
213
|
-
for (const ev of live_pass(evidences, { kind: "test_run" })) {
|
|
214
|
-
for (const id of evidence_invariant_refs(ev))
|
|
215
|
-
ids.add(id);
|
|
216
|
-
}
|
|
217
|
-
return ids;
|
|
218
|
-
}
|
|
219
|
-
export function evidence_invariant_ref_reasons(evidences, taskId, declared, validIds, semanticStatus) {
|
|
220
|
-
const problems = [];
|
|
221
|
-
for (const ev of evidences) {
|
|
222
|
-
const refs = evidence_invariant_refs(ev);
|
|
223
|
-
if (declared.size > 0 && refs.size === 0) {
|
|
224
|
-
problems.push(reason("missing_invariant_ref", `task ${taskId} ${semanticStatus} evidence requires invariant_refs`));
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
for (const inv of refs) {
|
|
228
|
-
if (declared.size > 0 && !declared.has(inv)) {
|
|
229
|
-
problems.push(reason("invariant_not_honored", `task ${taskId} ${semanticStatus} invariant_ref ${inv} not in declared invariant_refs ${renderList([...declared].sort())}`));
|
|
230
|
-
}
|
|
231
|
-
if (validIds.size > 0 && !validIds.has(inv)) {
|
|
232
|
-
problems.push(reason("invalid_invariant_ref", `task ${taskId} ${semanticStatus} references unknown invariant ${inv}`));
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return problems;
|
|
237
|
-
}
|
|
238
|
-
export function evidence_test_contract_invariant_reasons(evidences, taskId, byTest, semanticStatus) {
|
|
239
|
-
const problems = [];
|
|
240
|
-
for (const ev of evidences) {
|
|
241
|
-
const testId = typeof ev.test_id === "string" ? ev.test_id : "";
|
|
242
|
-
const expected = byTest.get(testId) ?? new Set();
|
|
243
|
-
if (expected.size === 0)
|
|
244
|
-
continue;
|
|
245
|
-
const actual = evidence_invariant_refs(ev);
|
|
246
|
-
if (actual.size === 0) {
|
|
247
|
-
problems.push(reason("missing_invariant_ref", `task ${taskId} ${semanticStatus} evidence for ${testId} requires invariant_refs ${renderList([...expected].sort())}`));
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
const missing = [...expected].filter((inv) => !actual.has(inv)).sort();
|
|
251
|
-
if (missing.length > 0) {
|
|
252
|
-
problems.push(reason("invariant_not_honored", `task ${taskId} ${semanticStatus} evidence for ${testId} missing invariant_refs ${renderList(missing)}`));
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return problems;
|
|
256
|
-
}
|
|
257
|
-
function firstMarkdownTableLines(text) {
|
|
258
|
-
const lines = text.split(/\r?\n/);
|
|
259
|
-
const table = [];
|
|
260
|
-
let started = false;
|
|
261
|
-
for (const line of lines) {
|
|
262
|
-
const trimmed = line.trim();
|
|
263
|
-
if (!trimmed.startsWith("|")) {
|
|
264
|
-
if (started)
|
|
265
|
-
break;
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
started = true;
|
|
269
|
-
table.push(line);
|
|
270
|
-
}
|
|
271
|
-
return table;
|
|
272
|
-
}
|
|
273
|
-
function parse_invariant_matrix_records(text) {
|
|
274
|
-
const table = firstMarkdownTableLines(text);
|
|
275
|
-
if (table.length < 3)
|
|
276
|
-
return { records: [], missing_columns: ["table"] };
|
|
277
|
-
const headers = parseMarkdownRow(table[0]).map(normalizeHeader);
|
|
278
|
-
const invIdx = headers.findIndex((header) => header === "inv-id" || header === "inv_id" || header.includes("invariant") || header.includes("inv"));
|
|
279
|
-
const statusIdx = headers.findIndex((header) => header === "status" || header.includes("状态"));
|
|
280
|
-
const evidenceIdx = headers.findIndex((header) => header.includes("evidence") || header.includes("proof") || header.includes("证据"));
|
|
281
|
-
const missingColumns = [];
|
|
282
|
-
if (invIdx < 0)
|
|
283
|
-
missingColumns.push("INV-ID");
|
|
284
|
-
if (statusIdx < 0)
|
|
285
|
-
missingColumns.push("status");
|
|
286
|
-
if (evidenceIdx < 0)
|
|
287
|
-
missingColumns.push("evidence");
|
|
288
|
-
if (missingColumns.length > 0)
|
|
289
|
-
return { records: [], missing_columns: missingColumns };
|
|
290
|
-
const records = [];
|
|
291
|
-
for (const line of table.slice(1)) {
|
|
292
|
-
const cells = parseMarkdownRow(line);
|
|
293
|
-
if (isSeparatorRow(cells))
|
|
294
|
-
continue;
|
|
295
|
-
const invId = (cells[invIdx] ?? "").match(INV_ID_RE)?.[0] ?? "";
|
|
296
|
-
if (!invId)
|
|
297
|
-
continue;
|
|
298
|
-
records.push({
|
|
299
|
-
inv_id: invId,
|
|
300
|
-
status: cells[statusIdx] ?? "",
|
|
301
|
-
evidence: cells[evidenceIdx] ?? "",
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
return { records, missing_columns: [] };
|
|
305
|
-
}
|
|
306
|
-
function escapeRegExp(text) {
|
|
307
|
-
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
308
|
-
}
|
|
309
|
-
function cellReferencesEvidenceId(cell, evidenceId) {
|
|
310
|
-
if (!evidenceId)
|
|
311
|
-
return false;
|
|
312
|
-
const tokenCandidates = cell
|
|
313
|
-
.split(/[\s,;|()[\]{}<>`'"]+/)
|
|
314
|
-
.map((item) => item.trim().replace(/^[.:]+|[.:]+$/g, ""))
|
|
315
|
-
.filter(Boolean);
|
|
316
|
-
if (tokenCandidates.includes(evidenceId))
|
|
317
|
-
return true;
|
|
318
|
-
const pattern = new RegExp(`(^|[^A-Za-z0-9_-])${escapeRegExp(evidenceId)}([^A-Za-z0-9_-]|$)`);
|
|
319
|
-
return pattern.test(cell);
|
|
320
|
-
}
|
|
321
|
-
function referencedLiveEvidenceIds(cell, liveEvidenceIds) {
|
|
322
|
-
return [...liveEvidenceIds].filter((evidenceId) => cellReferencesEvidenceId(cell, evidenceId)).sort();
|
|
323
|
-
}
|
|
324
|
-
export function invariant_matrix_coverage_reasons(changeRoot, ev, evidences) {
|
|
325
|
-
const matrixRef = ev.invariant_matrix_ref;
|
|
326
|
-
if (typeof matrixRef !== "string" || !matrixRef)
|
|
327
|
-
return [];
|
|
328
|
-
const filePath = safe_within(changeRoot, matrixRef);
|
|
329
|
-
if (filePath === null || !existsSync(filePath) || !statSync(filePath).isFile())
|
|
330
|
-
return [];
|
|
331
|
-
const required = hard_business_invariant_ids(changeRoot);
|
|
332
|
-
if (required.size === 0)
|
|
333
|
-
return [];
|
|
334
|
-
const { records, missing_columns: missingColumns } = parse_invariant_matrix_records(readFileSync(filePath, "utf8"));
|
|
335
|
-
const label = String(ev._path ?? ev.evidence_id ?? "verification_review");
|
|
336
|
-
const reasons = [];
|
|
337
|
-
if (missingColumns.length > 0) {
|
|
338
|
-
reasons.push(reason("invariant_matrix_incomplete", `${label}: invariant_matrix_ref ${matrixRef} missing parseable columns/table: ${renderList(missingColumns)}`, missingColumns));
|
|
339
|
-
return reasons;
|
|
340
|
-
}
|
|
341
|
-
const byId = new Map(records.map((record) => [record.inv_id, record]));
|
|
342
|
-
const missing = [...required].filter((id) => !byId.has(id)).sort();
|
|
343
|
-
if (missing.length > 0) {
|
|
344
|
-
reasons.push(reason("invariant_matrix_incomplete", `${label}: invariant matrix missing hard business invariants: ${renderList(missing)}`, missing));
|
|
345
|
-
}
|
|
346
|
-
const liveEvidenceIds = new Set(live_pass(evidences)
|
|
347
|
-
.map((item) => String(item.evidence_id ?? ""))
|
|
348
|
-
.filter(Boolean));
|
|
349
|
-
for (const id of [...required].sort()) {
|
|
350
|
-
const record = byId.get(id);
|
|
351
|
-
if (!record)
|
|
352
|
-
continue;
|
|
353
|
-
const status = record.status.trim().toLowerCase();
|
|
354
|
-
if (status !== "pass" && status !== "accepted") {
|
|
355
|
-
reasons.push(reason("invariant_matrix_incomplete", `${label}: invariant ${id} matrix status must be pass or accepted, got ${record.status || "<empty>"}`, [id]));
|
|
356
|
-
}
|
|
357
|
-
const evidenceIds = referencedLiveEvidenceIds(record.evidence, liveEvidenceIds);
|
|
358
|
-
if (evidenceIds.length === 0) {
|
|
359
|
-
reasons.push(reason("invariant_matrix_incomplete", `${label}: invariant ${id} matrix evidence must reference at least one live/pass evidence_id`, [id]));
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
return reasons;
|
|
363
|
-
}
|
package/dist/src/openspec.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { JsonMap, Reason } from "./util.ts";
|
|
2
|
-
import { runCommand } from "./util.ts";
|
|
3
|
-
export declare const REQUIRED_OPENSPEC_MIN_VERSION = "1.4.1";
|
|
4
|
-
export type OpenspecCliProbe = {
|
|
5
|
-
ok: boolean;
|
|
6
|
-
state: "ok" | "missing" | "invalid" | "too_old";
|
|
7
|
-
version: string | null;
|
|
8
|
-
message: string;
|
|
9
|
-
};
|
|
10
|
-
export declare function parse_openspec_version(raw: string): string | null;
|
|
11
|
-
export declare function compare_versions(actual: string, required: string): number;
|
|
12
|
-
export declare function openspec_cli_probe(opts?: {
|
|
13
|
-
cwd?: string;
|
|
14
|
-
commandExistsFn?: (cmd: string, meta?: {
|
|
15
|
-
cwd?: string;
|
|
16
|
-
}) => boolean;
|
|
17
|
-
run?: typeof runCommand;
|
|
18
|
-
}): OpenspecCliProbe;
|
|
19
|
-
export declare function openspec_status(change: string): JsonMap;
|
|
20
|
-
export declare function openspec_validate(change: string): [boolean, string];
|
|
21
|
-
export declare function openspec_version(): string;
|
|
22
|
-
export declare function openspec_status_shape_reasons(status: JsonMap): Reason[];
|
|
23
|
-
export declare function artifact_status_map(status: JsonMap): Record<string, string>;
|
|
24
|
-
export declare function is_ready_or_done(status: JsonMap, artifact: string): boolean;
|
|
25
|
-
export declare function is_done(status: JsonMap, artifact: string): boolean;
|
|
26
|
-
export declare function all_done(status: JsonMap): boolean;
|
|
27
|
-
export declare function openspec_floor_route(status: JsonMap): string;
|
|
28
|
-
export declare function normalize_route_phase(route: string): string;
|
|
29
|
-
export declare function normalize_gate(gate: string): string;
|
|
30
|
-
export declare function authorized_supersede_target_ids(evidences: JsonMap[]): Set<string>;
|
|
31
|
-
export declare function effective_superseded_ids(evidences: JsonMap[]): Set<string>;
|
|
32
|
-
export declare function gate_route_phase(gate: string): string;
|
|
33
|
-
export declare function effective_route_phase(status: JsonMap, requestedRoute: string, decision: JsonMap): string;
|
|
34
|
-
export declare function status_fingerprint(status: JsonMap): string;
|
|
35
|
-
export declare function get_change_root(status: JsonMap): string;
|
|
36
|
-
export declare function get_repo_root(status: JsonMap): string;
|
|
37
|
-
export declare function repo_root_from_cwd(start?: string): string;
|
package/dist/src/openspec.js
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { dirname, join, resolve, sep } from "node:path";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { REQUIRED_OPENSPEC_CLI_SURFACES, GATE_ALIASES, GATE_ROUTE, GuardError, ROUTE_ALIASES, ROUTE_ORDER, commandExists, fingerprint_obj, isObject, reason, repr, runCommand, } from "./util.js";
|
|
4
|
-
export const REQUIRED_OPENSPEC_MIN_VERSION = "1.4.1";
|
|
5
|
-
export function parse_openspec_version(raw) {
|
|
6
|
-
const match = raw.match(/\b(\d+)\.(\d+)\.(\d+)(?:[-+][0-9A-Za-z.-]+)?\b/);
|
|
7
|
-
return match ? `${match[1]}.${match[2]}.${match[3]}` : null;
|
|
8
|
-
}
|
|
9
|
-
export function compare_versions(actual, required) {
|
|
10
|
-
const actualParts = actual.split(".").map((part) => Number.parseInt(part, 10));
|
|
11
|
-
const requiredParts = required.split(".").map((part) => Number.parseInt(part, 10));
|
|
12
|
-
for (let index = 0; index < Math.max(actualParts.length, requiredParts.length); index += 1) {
|
|
13
|
-
const actualPart = actualParts[index] ?? 0;
|
|
14
|
-
const requiredPart = requiredParts[index] ?? 0;
|
|
15
|
-
if (!Number.isFinite(actualPart))
|
|
16
|
-
return -1;
|
|
17
|
-
if (!Number.isFinite(requiredPart))
|
|
18
|
-
return 1;
|
|
19
|
-
if (actualPart > requiredPart)
|
|
20
|
-
return 1;
|
|
21
|
-
if (actualPart < requiredPart)
|
|
22
|
-
return -1;
|
|
23
|
-
}
|
|
24
|
-
return 0;
|
|
25
|
-
}
|
|
26
|
-
export function openspec_cli_probe(opts = {}) {
|
|
27
|
-
const commandExistsFn = opts.commandExistsFn ?? ((cmd, meta) => commandExists(cmd, { cwd: meta?.cwd }));
|
|
28
|
-
const run = opts.run ?? runCommand;
|
|
29
|
-
if (!commandExistsFn("openspec", { cwd: opts.cwd })) {
|
|
30
|
-
return {
|
|
31
|
-
ok: false,
|
|
32
|
-
state: "missing",
|
|
33
|
-
version: null,
|
|
34
|
-
message: "PATH 中缺少 OpenSpec CLI(openspec)",
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
const versionProc = run("openspec", ["--version"], { cwd: opts.cwd, timeout: 15_000 });
|
|
38
|
-
if (versionProc.error || versionProc.status !== 0) {
|
|
39
|
-
const output = (versionProc.error?.message ?? (versionProc.stderr || versionProc.stdout)).trim();
|
|
40
|
-
return {
|
|
41
|
-
ok: false,
|
|
42
|
-
state: "invalid",
|
|
43
|
-
version: null,
|
|
44
|
-
message: `\`openspec --version\` 执行失败:${output}`,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const rawVersion = `${versionProc.stdout}${versionProc.stderr}`.trim();
|
|
48
|
-
if (/openspec[-_\s]*chinese/iu.test(rawVersion)) {
|
|
49
|
-
return {
|
|
50
|
-
ok: false,
|
|
51
|
-
state: "invalid",
|
|
52
|
-
version: null,
|
|
53
|
-
message: "PATH 上的 openspec 来自 openspec-chinese,不是受支持的 @fission-ai/openspec CLI",
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
const version = parse_openspec_version(rawVersion);
|
|
57
|
-
if (version === null) {
|
|
58
|
-
return {
|
|
59
|
-
ok: false,
|
|
60
|
-
state: "invalid",
|
|
61
|
-
version: null,
|
|
62
|
-
message: "PATH 上的 OpenSpec CLI(openspec)无法报告语义版本,可能不是受支持的 @fission-ai/openspec",
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
if (compare_versions(version, REQUIRED_OPENSPEC_MIN_VERSION) < 0) {
|
|
66
|
-
return {
|
|
67
|
-
ok: false,
|
|
68
|
-
state: "too_old",
|
|
69
|
-
version,
|
|
70
|
-
message: `openspec ${version} 低于 SuperSpec 要求的最低版本 ${REQUIRED_OPENSPEC_MIN_VERSION}`,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
for (const args of REQUIRED_OPENSPEC_CLI_SURFACES) {
|
|
74
|
-
const proc = run("openspec", [...args], { cwd: opts.cwd, timeout: 15_000 });
|
|
75
|
-
if (proc.error || proc.status !== 0) {
|
|
76
|
-
return {
|
|
77
|
-
ok: false,
|
|
78
|
-
state: "invalid",
|
|
79
|
-
version,
|
|
80
|
-
message: `openspec ${version} 缺少必需的原生能力:\`openspec ${args.join(" ")}\` 执行失败`,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return {
|
|
85
|
-
ok: true,
|
|
86
|
-
state: "ok",
|
|
87
|
-
version,
|
|
88
|
-
message: `openspec ${version} satisfies SuperSpec requirements`,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
export function openspec_status(change) {
|
|
92
|
-
const proc = runCommand("openspec", ["status", "--change", change, "--json"], { timeout: 30_000 });
|
|
93
|
-
if (proc.error) {
|
|
94
|
-
throw new GuardError(`openspec_unavailable: status failed: ${proc.error.message}`);
|
|
95
|
-
}
|
|
96
|
-
if (proc.status !== 0) {
|
|
97
|
-
throw new GuardError(`openspec_unavailable: status exit ${proc.status}: ${proc.stderr.trim()}`);
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
return JSON.parse(proc.stdout);
|
|
101
|
-
}
|
|
102
|
-
catch (err) {
|
|
103
|
-
throw new GuardError(`openspec_unavailable: status json parse failed`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
export function openspec_validate(change) {
|
|
107
|
-
const proc = runCommand("openspec", ["validate", change], { timeout: 60_000 });
|
|
108
|
-
if (proc.error) {
|
|
109
|
-
throw new GuardError(`openspec_unavailable: validate failed: ${proc.error.message}`);
|
|
110
|
-
}
|
|
111
|
-
return [proc.status === 0, `${proc.stdout}${proc.stderr}`.trim()];
|
|
112
|
-
}
|
|
113
|
-
export function openspec_version() {
|
|
114
|
-
const proc = runCommand("openspec", ["--version"], { timeout: 15_000 });
|
|
115
|
-
if (proc.error || proc.status !== 0)
|
|
116
|
-
return "unknown";
|
|
117
|
-
const raw = (proc.stdout || proc.stderr).trim();
|
|
118
|
-
return (parse_openspec_version(raw) ?? raw) || "unknown";
|
|
119
|
-
}
|
|
120
|
-
export function openspec_status_shape_reasons(status) {
|
|
121
|
-
const problems = [];
|
|
122
|
-
if (typeof status.changeRoot !== "string" || !status.changeRoot) {
|
|
123
|
-
problems.push(reason("openspec_status_incompatible", "status.changeRoot missing or not a string"));
|
|
124
|
-
}
|
|
125
|
-
const planningHome = status.planningHome;
|
|
126
|
-
if (!isObject(planningHome) || typeof planningHome.root !== "string") {
|
|
127
|
-
problems.push(reason("openspec_status_incompatible", "status.planningHome.root missing or not a string"));
|
|
128
|
-
}
|
|
129
|
-
const artifacts = status.artifacts;
|
|
130
|
-
if (!Array.isArray(artifacts)) {
|
|
131
|
-
problems.push(reason("openspec_status_incompatible", "status.artifacts missing or not a list"));
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
artifacts.forEach((artifact, idx) => {
|
|
135
|
-
if (!isObject(artifact)) {
|
|
136
|
-
problems.push(reason("openspec_status_incompatible", `status.artifacts[${idx}] is not an object`));
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (typeof artifact.id !== "string") {
|
|
140
|
-
problems.push(reason("openspec_status_incompatible", `status.artifacts[${idx}].id missing`));
|
|
141
|
-
}
|
|
142
|
-
if (!["done", "ready", "blocked"].includes(String(artifact.status))) {
|
|
143
|
-
problems.push(reason("openspec_status_incompatible", `status.artifacts[${idx}].status unsupported: ${repr(artifact.status)}`));
|
|
144
|
-
}
|
|
145
|
-
if ("missingDeps" in artifact && !Array.isArray(artifact.missingDeps)) {
|
|
146
|
-
problems.push(reason("openspec_status_incompatible", `status.artifacts[${idx}].missingDeps must be a list`));
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
if (!Array.isArray(status.applyRequires)) {
|
|
151
|
-
problems.push(reason("openspec_status_incompatible", "status.applyRequires missing or not a list"));
|
|
152
|
-
}
|
|
153
|
-
return problems;
|
|
154
|
-
}
|
|
155
|
-
export function artifact_status_map(status) {
|
|
156
|
-
const out = {};
|
|
157
|
-
for (const artifact of Array.isArray(status.artifacts) ? status.artifacts : []) {
|
|
158
|
-
if (isObject(artifact) && typeof artifact.id === "string") {
|
|
159
|
-
out[artifact.id] = String(artifact.status ?? "unknown");
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return out;
|
|
163
|
-
}
|
|
164
|
-
export function is_ready_or_done(status, artifact) {
|
|
165
|
-
return ["ready", "done"].includes(artifact_status_map(status)[artifact]);
|
|
166
|
-
}
|
|
167
|
-
export function is_done(status, artifact) {
|
|
168
|
-
return artifact_status_map(status)[artifact] === "done";
|
|
169
|
-
}
|
|
170
|
-
export function all_done(status) {
|
|
171
|
-
const values = Object.values(artifact_status_map(status));
|
|
172
|
-
return values.length > 0 && values.every((item) => item === "done");
|
|
173
|
-
}
|
|
174
|
-
export function openspec_floor_route(status) {
|
|
175
|
-
if (is_done(status, "tasks"))
|
|
176
|
-
return "apply";
|
|
177
|
-
if (is_done(status, "design"))
|
|
178
|
-
return "propose";
|
|
179
|
-
if (is_done(status, "proposal") && is_done(status, "specs"))
|
|
180
|
-
return "propose";
|
|
181
|
-
if (is_ready_or_done(status, "proposal") && is_ready_or_done(status, "specs"))
|
|
182
|
-
return "propose";
|
|
183
|
-
return "init";
|
|
184
|
-
}
|
|
185
|
-
export function normalize_route_phase(route) {
|
|
186
|
-
return ROUTE_ALIASES[route] ?? route;
|
|
187
|
-
}
|
|
188
|
-
export function normalize_gate(gate) {
|
|
189
|
-
return GATE_ALIASES[gate] ?? gate;
|
|
190
|
-
}
|
|
191
|
-
export function authorized_supersede_target_ids(evidences) {
|
|
192
|
-
const byId = new Map();
|
|
193
|
-
for (const ev of evidences) {
|
|
194
|
-
if (!isObject(ev) || ev._invalid)
|
|
195
|
-
continue;
|
|
196
|
-
const id = typeof ev.evidence_id === "string" ? ev.evidence_id : "";
|
|
197
|
-
if (id && !byId.has(id))
|
|
198
|
-
byId.set(id, ev);
|
|
199
|
-
}
|
|
200
|
-
const out = new Set();
|
|
201
|
-
for (const ev of evidences) {
|
|
202
|
-
if (!isObject(ev) || ev._invalid || ev.status !== "superseded")
|
|
203
|
-
continue;
|
|
204
|
-
const targetId = typeof ev.supersedes === "string" ? ev.supersedes : "";
|
|
205
|
-
if (!targetId)
|
|
206
|
-
continue;
|
|
207
|
-
const target = byId.get(targetId);
|
|
208
|
-
if (!target)
|
|
209
|
-
continue;
|
|
210
|
-
if (target.status !== "pass")
|
|
211
|
-
continue;
|
|
212
|
-
const sameGate = normalize_gate(String(ev.gate ?? "")) === normalize_gate(String(target.gate ?? ""));
|
|
213
|
-
const supersedeReason = typeof ev.supersede_reason === "string" ? ev.supersede_reason.trim() : "";
|
|
214
|
-
if (sameGate || supersedeReason)
|
|
215
|
-
out.add(targetId);
|
|
216
|
-
}
|
|
217
|
-
return out;
|
|
218
|
-
}
|
|
219
|
-
export function effective_superseded_ids(evidences) {
|
|
220
|
-
return authorized_supersede_target_ids(evidences);
|
|
221
|
-
}
|
|
222
|
-
export function gate_route_phase(gate) {
|
|
223
|
-
return GATE_ROUTE[normalize_gate(gate)] ?? "propose";
|
|
224
|
-
}
|
|
225
|
-
export function effective_route_phase(status, requestedRoute, decision) {
|
|
226
|
-
const requested = normalize_route_phase(requestedRoute);
|
|
227
|
-
if (decision.allowed)
|
|
228
|
-
return requested;
|
|
229
|
-
const floor = openspec_floor_route(status);
|
|
230
|
-
const requestedOrder = ROUTE_ORDER[requested];
|
|
231
|
-
if (requestedOrder === undefined)
|
|
232
|
-
return floor;
|
|
233
|
-
return requestedOrder <= ROUTE_ORDER[floor] ? requested : floor;
|
|
234
|
-
}
|
|
235
|
-
export function status_fingerprint(status) {
|
|
236
|
-
const artifacts = (Array.isArray(status.artifacts) ? status.artifacts : [])
|
|
237
|
-
.map((artifact) => ({
|
|
238
|
-
id: artifact.id,
|
|
239
|
-
status: artifact.status,
|
|
240
|
-
missingDeps: [...(Array.isArray(artifact.missingDeps) ? artifact.missingDeps : [])].sort(),
|
|
241
|
-
}))
|
|
242
|
-
.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
243
|
-
const core = {
|
|
244
|
-
artifacts,
|
|
245
|
-
applyRequires: [...(Array.isArray(status.applyRequires) ? status.applyRequires : [])].sort(),
|
|
246
|
-
};
|
|
247
|
-
return fingerprint_obj(core);
|
|
248
|
-
}
|
|
249
|
-
export function get_change_root(status) {
|
|
250
|
-
if (!status.changeRoot)
|
|
251
|
-
throw new GuardError("openspec_unavailable: status missing changeRoot");
|
|
252
|
-
return resolve(String(status.changeRoot));
|
|
253
|
-
}
|
|
254
|
-
export function get_repo_root(status) {
|
|
255
|
-
const root = isObject(status.planningHome) ? status.planningHome.root : undefined;
|
|
256
|
-
if (root)
|
|
257
|
-
return resolve(String(root));
|
|
258
|
-
const changeRoot = get_change_root(status);
|
|
259
|
-
const parts = changeRoot.split(sep);
|
|
260
|
-
if (parts.length < 3)
|
|
261
|
-
throw new GuardError("openspec_unavailable: cannot derive repo root");
|
|
262
|
-
return resolve(changeRoot, "..", "..", "..");
|
|
263
|
-
}
|
|
264
|
-
export function repo_root_from_cwd(start = process.cwd()) {
|
|
265
|
-
let dir = resolve(start);
|
|
266
|
-
while (true) {
|
|
267
|
-
if (existsSync(join(dir, "openspec", "changes")))
|
|
268
|
-
return dir;
|
|
269
|
-
const parent = dirname(dir);
|
|
270
|
-
if (parent === dir)
|
|
271
|
-
return resolve(start);
|
|
272
|
-
dir = parent;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { type ParsedArgs } from "./cli_args.ts";
|
|
2
|
-
import type { PacketDispatchResult } from "./packet_schema.ts";
|
|
3
|
-
export declare function read_discovery_template(packageRoot?: string): string | null;
|
|
4
|
-
export declare function dispatch_packet(args: ParsedArgs): PacketDispatchResult;
|
|
5
|
-
export declare function is_packet_command(command: string): boolean;
|