@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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { ALLOWED_CONFIG_KEYS, BUILTIN_CONFIG, CHANGE_CONFIG_ALIASES, CONFIG_FILENAME, GuardError, PROJECT_CONFIG_ALIASES, REQUIRED_SIDECAR_DIRS, STATE_ALIASES, isObject, reason, repr, safe_within, } from "./util.js";
|
|
4
|
+
export function superspec_dir(changeRoot) {
|
|
5
|
+
return join(changeRoot, ".superspec");
|
|
6
|
+
}
|
|
7
|
+
export function project_superspec_dir(repoRoot) {
|
|
8
|
+
return join(repoRoot, ".superspec");
|
|
9
|
+
}
|
|
10
|
+
export function sidecar_artifacts_dir(changeRoot) {
|
|
11
|
+
return join(superspec_dir(changeRoot), "artifacts");
|
|
12
|
+
}
|
|
13
|
+
export function sidecar_test_contract_path(changeRoot) {
|
|
14
|
+
return join(sidecar_artifacts_dir(changeRoot), "test-contract.md");
|
|
15
|
+
}
|
|
16
|
+
export function sidecar_business_invariants_path(changeRoot) {
|
|
17
|
+
return join(sidecar_artifacts_dir(changeRoot), "business-invariants.md");
|
|
18
|
+
}
|
|
19
|
+
export function sidecar_discovery_path(changeRoot) {
|
|
20
|
+
return join(sidecar_artifacts_dir(changeRoot), "discovery.md");
|
|
21
|
+
}
|
|
22
|
+
export function sidecar_output_path(changeRoot, relPath) {
|
|
23
|
+
const target = safe_within(superspec_dir(changeRoot), relPath);
|
|
24
|
+
if (target === null)
|
|
25
|
+
throw new GuardError(`sidecar_path_invalid: ${relPath}`);
|
|
26
|
+
return target;
|
|
27
|
+
}
|
|
28
|
+
export function write_sidecar_text(changeRoot, relPath, text) {
|
|
29
|
+
const filePath = sidecar_output_path(changeRoot, relPath);
|
|
30
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
31
|
+
writeFileSync(filePath, text, "utf8");
|
|
32
|
+
return filePath;
|
|
33
|
+
}
|
|
34
|
+
export function write_sidecar_json(changeRoot, relPath, data) {
|
|
35
|
+
return write_sidecar_text(changeRoot, relPath, `${JSON.stringify(data, null, 2)}\n`);
|
|
36
|
+
}
|
|
37
|
+
export function config_file(changeRoot) {
|
|
38
|
+
return join(superspec_dir(changeRoot), CONFIG_FILENAME);
|
|
39
|
+
}
|
|
40
|
+
export function project_config_file(repoRoot) {
|
|
41
|
+
return join(project_superspec_dir(repoRoot), CONFIG_FILENAME);
|
|
42
|
+
}
|
|
43
|
+
export function required_sidecar_paths(changeRoot) {
|
|
44
|
+
return REQUIRED_SIDECAR_DIRS.map((relPath) => join(superspec_dir(changeRoot), relPath));
|
|
45
|
+
}
|
|
46
|
+
export function ensure_sidecar_layout(changeRoot) {
|
|
47
|
+
ensure_state_layout(changeRoot);
|
|
48
|
+
for (const dirPath of required_sidecar_paths(changeRoot))
|
|
49
|
+
mkdirSync(dirPath, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
export function ensure_state_layout(changeRoot) {
|
|
52
|
+
const base = superspec_dir(changeRoot);
|
|
53
|
+
mkdirSync(base, { recursive: true });
|
|
54
|
+
const ledger = join(base, "ledger.jsonl");
|
|
55
|
+
if (!existsSync(ledger))
|
|
56
|
+
writeFileSync(ledger, "");
|
|
57
|
+
}
|
|
58
|
+
export function find_forbidden_aliases(repoRoot, changeRoot) {
|
|
59
|
+
const aliases = [];
|
|
60
|
+
for (const relPath of PROJECT_CONFIG_ALIASES) {
|
|
61
|
+
const candidate = join(repoRoot, relPath);
|
|
62
|
+
if (existsSync(candidate))
|
|
63
|
+
aliases.push(candidate);
|
|
64
|
+
}
|
|
65
|
+
for (const relPath of CHANGE_CONFIG_ALIASES) {
|
|
66
|
+
const candidate = join(changeRoot, relPath);
|
|
67
|
+
if (existsSync(candidate))
|
|
68
|
+
aliases.push(candidate);
|
|
69
|
+
}
|
|
70
|
+
for (const relPath of STATE_ALIASES) {
|
|
71
|
+
const candidate = join(changeRoot, relPath);
|
|
72
|
+
if (existsSync(candidate))
|
|
73
|
+
aliases.push(candidate);
|
|
74
|
+
}
|
|
75
|
+
return aliases;
|
|
76
|
+
}
|
|
77
|
+
export function read_skill_frontmatter_name(skillPath) {
|
|
78
|
+
let lines;
|
|
79
|
+
try {
|
|
80
|
+
lines = readFileSync(skillPath, "utf8").split(/\r?\n/);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (lines.length === 0 || lines[0].trim() !== "---")
|
|
86
|
+
return null;
|
|
87
|
+
for (const raw of lines.slice(1)) {
|
|
88
|
+
const stripped = raw.trim();
|
|
89
|
+
if (stripped === "---")
|
|
90
|
+
return null;
|
|
91
|
+
if (stripped.startsWith("name:"))
|
|
92
|
+
return stripped.split(":", 2)[1].trim().replace(/^['"]|['"]$/g, "");
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
export function read_agent_toml_name(agentPath) {
|
|
97
|
+
let lines;
|
|
98
|
+
try {
|
|
99
|
+
lines = readFileSync(agentPath, "utf8").split(/\r?\n/);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
for (const raw of lines) {
|
|
105
|
+
const match = raw.match(/^\s*name\s*=\s*["']([^"']+)["']\s*$/);
|
|
106
|
+
if (match)
|
|
107
|
+
return match[1];
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
function parseScalar(raw) {
|
|
112
|
+
const value = raw.trim();
|
|
113
|
+
if (!value)
|
|
114
|
+
return "";
|
|
115
|
+
if (value === "true")
|
|
116
|
+
return true;
|
|
117
|
+
if (value === "false")
|
|
118
|
+
return false;
|
|
119
|
+
if (value === "null" || value === "None")
|
|
120
|
+
return null;
|
|
121
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
122
|
+
return value.slice(1, -1);
|
|
123
|
+
if (/^-?\d+$/.test(value))
|
|
124
|
+
return Number.parseInt(value, 10);
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
export function parse_simple_yaml(filePath) {
|
|
128
|
+
const data = {};
|
|
129
|
+
let currentKey = null;
|
|
130
|
+
const lines = readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
131
|
+
lines.forEach((raw, idx) => {
|
|
132
|
+
const stripped = raw.trim();
|
|
133
|
+
if (!stripped || stripped.startsWith("#"))
|
|
134
|
+
return;
|
|
135
|
+
const indent = raw.length - raw.trimStart().length;
|
|
136
|
+
if (!stripped.includes(":"))
|
|
137
|
+
throw new GuardError(`config_parse_error: ${filePath}:${idx + 1}: expected key: value`);
|
|
138
|
+
const [keyRaw, ...rest] = stripped.split(":");
|
|
139
|
+
const key = keyRaw.trim();
|
|
140
|
+
const value = rest.join(":");
|
|
141
|
+
if (!key)
|
|
142
|
+
throw new GuardError(`config_parse_error: ${filePath}:${idx + 1}: empty key`);
|
|
143
|
+
if (indent === 0) {
|
|
144
|
+
if (value.trim()) {
|
|
145
|
+
data[key] = parseScalar(value);
|
|
146
|
+
currentKey = null;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
data[key] = {};
|
|
150
|
+
currentKey = key;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (indent === 2 && currentKey) {
|
|
154
|
+
const parent = data[currentKey];
|
|
155
|
+
if (!isObject(parent))
|
|
156
|
+
throw new GuardError(`config_parse_error: ${filePath}:${idx + 1}: parent is not mapping`);
|
|
157
|
+
parent[key] = parseScalar(value);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
throw new GuardError(`config_parse_error: ${filePath}:${idx + 1}: unsupported indentation`);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return data;
|
|
164
|
+
}
|
|
165
|
+
export function merge_config(base, override) {
|
|
166
|
+
const merged = JSON.parse(JSON.stringify(base));
|
|
167
|
+
for (const [key, value] of Object.entries(override)) {
|
|
168
|
+
if (isObject(value) && isObject(merged[key]))
|
|
169
|
+
merged[key] = { ...merged[key], ...value };
|
|
170
|
+
else
|
|
171
|
+
merged[key] = value;
|
|
172
|
+
}
|
|
173
|
+
return merged;
|
|
174
|
+
}
|
|
175
|
+
export function validate_config_keys(config, source) {
|
|
176
|
+
const problems = [];
|
|
177
|
+
for (const key of Object.keys(config)) {
|
|
178
|
+
if (ALLOWED_CONFIG_KEYS.has(key) || key.startsWith("x-"))
|
|
179
|
+
continue;
|
|
180
|
+
problems.push(reason("unknown_config_key", `${source}: unknown config key ${repr(key)}`));
|
|
181
|
+
}
|
|
182
|
+
return problems;
|
|
183
|
+
}
|
|
184
|
+
export function load_config(repoRoot, changeRoot) {
|
|
185
|
+
const problems = [];
|
|
186
|
+
for (const aliasPath of find_forbidden_aliases(repoRoot, changeRoot)) {
|
|
187
|
+
problems.push(reason("forbidden_alias_path", `forbidden superspec alias exists: ${aliasPath}`));
|
|
188
|
+
}
|
|
189
|
+
let config = JSON.parse(JSON.stringify(BUILTIN_CONFIG));
|
|
190
|
+
const projectCfg = project_config_file(repoRoot);
|
|
191
|
+
if (existsSync(projectCfg) && statSync(projectCfg).isFile()) {
|
|
192
|
+
const parsed = parse_simple_yaml(projectCfg);
|
|
193
|
+
problems.push(...validate_config_keys(parsed, projectCfg));
|
|
194
|
+
config = merge_config(config, parsed);
|
|
195
|
+
}
|
|
196
|
+
const changeCfg = config_file(changeRoot);
|
|
197
|
+
if (existsSync(changeCfg) && statSync(changeCfg).isFile()) {
|
|
198
|
+
const parsed = parse_simple_yaml(changeCfg);
|
|
199
|
+
problems.push(...validate_config_keys(parsed, changeCfg));
|
|
200
|
+
config = merge_config(config, parsed);
|
|
201
|
+
}
|
|
202
|
+
return [config, problems];
|
|
203
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { REQUIRED_SUPERSPEC_AGENT_ROLES, REQUIRED_OPENSPEC_CODEX_SKILLS, block, reason, read_agent_toml_name, read_skill_frontmatter_name, commandExists, runCommand, } from "./core.js";
|
|
4
|
+
import { install_workflow } from "./install_engine.js";
|
|
5
|
+
const ROLE_DESCRIPTIONS = {
|
|
6
|
+
architect: "System design, boundaries, interfaces, and long-horizon tradeoffs",
|
|
7
|
+
critic: "Critical review of plans, evidence, assumptions, and scope drift",
|
|
8
|
+
"test-engineer": "Test strategy, coverage, and RED/GREEN evidence review",
|
|
9
|
+
"code-reviewer": "Code/spec/security review lane for the code-review workflow",
|
|
10
|
+
verifier: "Final completion evidence and verification review",
|
|
11
|
+
};
|
|
12
|
+
function commandFailure(proc) {
|
|
13
|
+
return (proc.error?.message ?? (proc.stderr || proc.stdout)).trim();
|
|
14
|
+
}
|
|
15
|
+
function openspecSkillProblems(repoRoot) {
|
|
16
|
+
const skillsRoot = join(repoRoot, ".codex", "skills");
|
|
17
|
+
const problems = [];
|
|
18
|
+
for (const name of REQUIRED_OPENSPEC_CODEX_SKILLS) {
|
|
19
|
+
const skillPath = join(skillsRoot, name, "SKILL.md");
|
|
20
|
+
if (!existsSync(skillPath) || !statSync(skillPath).isFile()) {
|
|
21
|
+
problems.push(name);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (read_skill_frontmatter_name(skillPath) !== name)
|
|
25
|
+
problems.push(name);
|
|
26
|
+
}
|
|
27
|
+
return problems.sort();
|
|
28
|
+
}
|
|
29
|
+
function writeSuperSpecAgent(repoRoot, name) {
|
|
30
|
+
const filePath = join(repoRoot, ".codex", "agents", `${name}.toml`);
|
|
31
|
+
mkdirSync(join(repoRoot, ".codex", "agents"), { recursive: true });
|
|
32
|
+
writeFileSync(filePath, [
|
|
33
|
+
`name = "${name}"`,
|
|
34
|
+
`description = "${ROLE_DESCRIPTIONS[name] ?? `superspec ${name} role`}"`,
|
|
35
|
+
'model_reasoning_effort = "high"',
|
|
36
|
+
'developer_instructions = """',
|
|
37
|
+
`You are the repo-local superspec ${name} native subagent.`,
|
|
38
|
+
"Follow the assigned superspec gate evidence task, cite concrete files, and report blockers upward.",
|
|
39
|
+
"Do not substitute main-thread self-review for required role evidence.",
|
|
40
|
+
'"""',
|
|
41
|
+
"",
|
|
42
|
+
].join("\n"), "utf8");
|
|
43
|
+
return filePath;
|
|
44
|
+
}
|
|
45
|
+
function writeSuperSpecPrompt(repoRoot, name) {
|
|
46
|
+
const filePath = join(repoRoot, ".codex", "prompts", `${name}.md`);
|
|
47
|
+
mkdirSync(join(repoRoot, ".codex", "prompts"), { recursive: true });
|
|
48
|
+
writeFileSync(filePath, [
|
|
49
|
+
"---",
|
|
50
|
+
`description: "${ROLE_DESCRIPTIONS[name] ?? `superspec ${name} role`}"`,
|
|
51
|
+
'argument-hint: "superspec gate evidence task"',
|
|
52
|
+
"---",
|
|
53
|
+
"",
|
|
54
|
+
`You are the repo-local superspec ${name} role.`,
|
|
55
|
+
"",
|
|
56
|
+
"Review the provided superspec gate context with concrete file-backed evidence. Produce a concise pass/block report, cite source anchors and target refs, and do not replace required native-subagent evidence with main-thread self-review.",
|
|
57
|
+
"",
|
|
58
|
+
].join("\n"), "utf8");
|
|
59
|
+
return filePath;
|
|
60
|
+
}
|
|
61
|
+
function ensureOpenSpecCodex(repoRoot, actions) {
|
|
62
|
+
if (!commandExists("openspec", { cwd: repoRoot }))
|
|
63
|
+
return ["openspec CLI is not available in PATH"];
|
|
64
|
+
let problems = openspecSkillProblems(repoRoot);
|
|
65
|
+
if (problems.length === 0) {
|
|
66
|
+
actions.push({ action: "openspec_codex_skills", status: "ok" });
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const init = runCommand("openspec", ["init", "--tools", "codex", "."], { cwd: repoRoot, timeout: 60_000 });
|
|
70
|
+
actions.push({
|
|
71
|
+
action: "openspec init --tools codex .",
|
|
72
|
+
status: init.status === 0 ? "updated" : "failed",
|
|
73
|
+
refs: problems,
|
|
74
|
+
detail: init.status === 0 ? undefined : (init.stderr || init.stdout).trim(),
|
|
75
|
+
});
|
|
76
|
+
if (init.error || init.status !== 0)
|
|
77
|
+
return [`openspec init failed: ${commandFailure(init)}`];
|
|
78
|
+
problems = openspecSkillProblems(repoRoot);
|
|
79
|
+
if (problems.length === 0)
|
|
80
|
+
return [];
|
|
81
|
+
const update = runCommand("openspec", ["update", "--force", "."], { cwd: repoRoot, timeout: 60_000 });
|
|
82
|
+
actions.push({
|
|
83
|
+
action: "openspec update --force .",
|
|
84
|
+
status: update.status === 0 ? "updated" : "failed",
|
|
85
|
+
refs: problems,
|
|
86
|
+
detail: update.status === 0 ? undefined : (update.stderr || update.stdout).trim(),
|
|
87
|
+
});
|
|
88
|
+
if (update.error || update.status !== 0)
|
|
89
|
+
return [`openspec update failed: ${commandFailure(update)}`];
|
|
90
|
+
problems = openspecSkillProblems(repoRoot);
|
|
91
|
+
return problems.length === 0 ? [] : [`OpenSpec Codex skills remain missing/invalid: ${problems.join(", ")}`];
|
|
92
|
+
}
|
|
93
|
+
function ensureSuperSpecRoles(repoRoot, actions) {
|
|
94
|
+
const problems = [];
|
|
95
|
+
const created = [];
|
|
96
|
+
for (const name of REQUIRED_SUPERSPEC_AGENT_ROLES) {
|
|
97
|
+
const agentPath = join(repoRoot, ".codex", "agents", `${name}.toml`);
|
|
98
|
+
if (!existsSync(agentPath) || !statSync(agentPath).isFile()) {
|
|
99
|
+
created.push(writeSuperSpecAgent(repoRoot, name));
|
|
100
|
+
}
|
|
101
|
+
else if (read_agent_toml_name(agentPath) !== name) {
|
|
102
|
+
problems.push(`invalid agent ${agentPath}`);
|
|
103
|
+
}
|
|
104
|
+
const promptPath = join(repoRoot, ".codex", "prompts", `${name}.md`);
|
|
105
|
+
if (!existsSync(promptPath) || !statSync(promptPath).isFile()) {
|
|
106
|
+
created.push(writeSuperSpecPrompt(repoRoot, name));
|
|
107
|
+
}
|
|
108
|
+
else if (!readFileSync(promptPath, "utf8").trim()) {
|
|
109
|
+
problems.push(`empty prompt ${promptPath}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
actions.push({
|
|
113
|
+
action: "superspec_repo_local_roles",
|
|
114
|
+
status: problems.length > 0 ? "failed" : created.length > 0 ? "created" : "ok",
|
|
115
|
+
refs: created.length > 0 ? created : undefined,
|
|
116
|
+
});
|
|
117
|
+
return problems;
|
|
118
|
+
}
|
|
119
|
+
// D4 (audit G-1): SuperSpec's own surfaces are installed manifest-driven from the install map;
|
|
120
|
+
// it runs before the role fallback generator so canonical templates win on fresh installs.
|
|
121
|
+
function ensureSuperSpecWorkflow(repoRoot, actions, force) {
|
|
122
|
+
const result = install_workflow(repoRoot, { force, scope: "project" });
|
|
123
|
+
for (const item of result.actions) {
|
|
124
|
+
actions.push({
|
|
125
|
+
action: item.action,
|
|
126
|
+
status: item.status === "removed" || item.status === "would_remove" ? "updated" : item.status,
|
|
127
|
+
refs: item.refs,
|
|
128
|
+
detail: item.detail,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return result.problems;
|
|
132
|
+
}
|
|
133
|
+
export function project_init(repoRootRaw = process.cwd(), opts = {}) {
|
|
134
|
+
const repoRoot = resolve(repoRootRaw);
|
|
135
|
+
const actions = [];
|
|
136
|
+
const problems = [
|
|
137
|
+
...ensureOpenSpecCodex(repoRoot, actions),
|
|
138
|
+
...ensureSuperSpecWorkflow(repoRoot, actions, opts.force === true),
|
|
139
|
+
...ensureSuperSpecRoles(repoRoot, actions),
|
|
140
|
+
];
|
|
141
|
+
if (problems.length > 0) {
|
|
142
|
+
const decision = block("project", "project_init", problems.map((item) => reason("project_init_failed", item)));
|
|
143
|
+
return {
|
|
144
|
+
...decision,
|
|
145
|
+
project_root: repoRoot,
|
|
146
|
+
actions,
|
|
147
|
+
next_allowed_actions: ["fix project_init_failed reasons, then rerun superspec init --scope project"],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
allowed: true,
|
|
152
|
+
decision: "allow",
|
|
153
|
+
change_id: null,
|
|
154
|
+
gate: "project_init",
|
|
155
|
+
project_root: repoRoot,
|
|
156
|
+
actions,
|
|
157
|
+
block_reasons: [],
|
|
158
|
+
next_allowed_actions: ["create/select a change during superspec-explore, then run change-scoped guard checks"],
|
|
159
|
+
trust_warnings: ["project init installs project surfaces only; change sidecars are created lazily by later superspec phases"],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { JsonMap, Reason } from "./util.ts";
|
|
2
|
+
export declare function state_file(changeRoot: string): string;
|
|
3
|
+
export declare function ledger_file(changeRoot: string): string;
|
|
4
|
+
export declare function load_state(changeRoot: string): JsonMap | null;
|
|
5
|
+
export declare function state_file_corrupt(changeRoot: string): boolean;
|
|
6
|
+
export declare function state_corrupt_reasons(changeRoot: string): Reason[];
|
|
7
|
+
export declare function read_ledger_text(changeRoot: string): string;
|
|
8
|
+
export declare function compute_fingerprints(changeRoot: string, status: JsonMap): JsonMap;
|
|
9
|
+
export declare function state_stale_reasons(changeRoot: string, status: JsonMap): Reason[];
|
|
10
|
+
export declare function materialize_ledger_event(event: JsonMap): JsonMap;
|
|
11
|
+
export declare function ledger_event_line(event: JsonMap): string;
|
|
12
|
+
export declare function with_state_lock<T>(changeRoot: string, fn: () => T): T;
|
|
13
|
+
export declare function force_unlock_state(changeRoot: string): boolean;
|
|
14
|
+
export declare function prepare_recomputed_state_write(change: string, changeRoot: string, status: JsonMap, guardRoutePhase: string, activeGate: string, decision: JsonMap, opts?: {
|
|
15
|
+
config?: JsonMap | null;
|
|
16
|
+
preset_upgrade_required?: boolean;
|
|
17
|
+
fingerprints?: JsonMap | null;
|
|
18
|
+
}): JsonMap;
|
|
19
|
+
export declare function write_prepared_state_locked(changeRoot: string, prepared: JsonMap): void;
|
|
20
|
+
export declare function restore_state_snapshot_locked(changeRoot: string, snapshot: {
|
|
21
|
+
state_text: string | null;
|
|
22
|
+
ledger_text: string;
|
|
23
|
+
}): void;
|
|
24
|
+
export declare function write_state_atomic(changeRoot: string, state: JsonMap, opts?: {
|
|
25
|
+
expected_state_fingerprints?: JsonMap | null;
|
|
26
|
+
ledger_event?: JsonMap | null;
|
|
27
|
+
}): void;
|
|
28
|
+
export declare function append_ledger(changeRoot: string, event: JsonMap): void;
|
|
29
|
+
export declare function record_supersede_ledger_events(change: string, changeRoot: string, evidences: JsonMap[]): void;
|
|
30
|
+
export declare function recompute_and_write_state_locked(change: string, changeRoot: string, status: JsonMap, guardRoutePhase: string, activeGate: string, decision: JsonMap, opts?: {
|
|
31
|
+
config?: JsonMap | null;
|
|
32
|
+
preset_upgrade_required?: boolean;
|
|
33
|
+
}): JsonMap;
|
|
34
|
+
export declare function recompute_and_write_state(change: string, changeRoot: string, status: JsonMap, guardRoutePhase: string, activeGate: string, decision: JsonMap, opts?: {
|
|
35
|
+
config?: JsonMap | null;
|
|
36
|
+
preset_upgrade_required?: boolean;
|
|
37
|
+
}): JsonMap;
|