cadence-skill-installer 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 ADDED
@@ -0,0 +1,46 @@
1
+ # Cadence Skill Installer
2
+
3
+ Install this repository's `skill/` contents into one or more AI tool skill paths (`.../skills/cadence/`) using `npx`.
4
+
5
+ ## Run
6
+
7
+ ```bash
8
+ npx cadence-skill-installer
9
+ ```
10
+
11
+ The installer shows a multi-select prompt (comma-separated choices) so you can install into multiple tools in one run.
12
+
13
+ ## Non-interactive examples
14
+
15
+ ```bash
16
+ # Install to all supported tools
17
+ npx cadence-skill-installer --all --yes
18
+
19
+ # Install to specific tools
20
+ npx cadence-skill-installer --tools codex,claude,gemini --yes
21
+ ```
22
+
23
+ ## Supported tool keys
24
+
25
+ - `codex`
26
+ - `agents`
27
+ - `claude`
28
+ - `gemini`
29
+ - `copilot`
30
+ - `github-copilot`
31
+ - `windsurf`
32
+ - `opencode`
33
+
34
+ ## CI/CD Trusted Publishing (recommended)
35
+
36
+ This repo includes `/Users/sn0w/Documents/dev/cadence/.github/workflows/publish.yml` for npm trusted publishing (OIDC).
37
+
38
+ 1. Push this repo to GitHub.
39
+ 2. In npm package settings for `cadence-skill-installer`, add a Trusted Publisher:
40
+ - Provider: GitHub Actions
41
+ - Repository: your owner/repo
42
+ - Workflow file: `.github/workflows/publish.yml`
43
+ - Environment: leave empty unless you use one
44
+ 3. Trigger the workflow manually (`workflow_dispatch`) or push a tag like `v0.1.0`.
45
+
46
+ No `NPM_TOKEN` secret is required in GitHub Actions when trusted publishing is configured.
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "cadence-skill-installer",
3
+ "version": "0.1.0",
4
+ "description": "Install the Cadence skill into supported AI tool skill directories.",
5
+ "private": false,
6
+ "type": "module",
7
+ "bin": {
8
+ "cadence-skill-installer": "scripts/install-cadence-skill.mjs",
9
+ "cadence-install": "scripts/install-cadence-skill.mjs"
10
+ },
11
+ "engines": {
12
+ "node": ">=18.0.0"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "files": [
18
+ "skill/**",
19
+ "scripts/install-cadence-skill.mjs",
20
+ "README.md"
21
+ ]
22
+ }
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import readline from "node:readline/promises";
7
+ import { fileURLToPath } from "node:url";
8
+ import { stdin as input, stdout as output } from "node:process";
9
+
10
+ const CADENCE_SKILL_NAME = "cadence";
11
+
12
+ const TOOL_TARGETS = [
13
+ { key: "codex", label: "Codex", relPath: [".codex", "skills", CADENCE_SKILL_NAME] },
14
+ { key: "agents", label: "Agents", relPath: [".agents", "skills", CADENCE_SKILL_NAME] },
15
+ { key: "claude", label: "Claude", relPath: [".claude", "skills", CADENCE_SKILL_NAME] },
16
+ { key: "gemini", label: "Gemini", relPath: [".gemini", "skills", CADENCE_SKILL_NAME] },
17
+ { key: "copilot", label: "Copilot", relPath: [".copilot", "skills", CADENCE_SKILL_NAME] },
18
+ {
19
+ key: "github-copilot",
20
+ label: "GitHub Copilot",
21
+ relPath: [".config", "github-copilot", "skills", CADENCE_SKILL_NAME]
22
+ },
23
+ {
24
+ key: "windsurf",
25
+ label: "Codeium Windsurf",
26
+ relPath: [".codeium", "windsurf", "skills", CADENCE_SKILL_NAME]
27
+ },
28
+ {
29
+ key: "opencode",
30
+ label: "OpenCode",
31
+ relPath: [".config", "opencode", "skills", CADENCE_SKILL_NAME]
32
+ }
33
+ ];
34
+
35
+ const SKIP_NAMES = new Set([".DS_Store", "__pycache__"]);
36
+
37
+ function printHelp(binName) {
38
+ const validTools = TOOL_TARGETS.map((tool) => tool.key).join(",");
39
+ output.write(
40
+ [
41
+ `Usage: ${binName} [options]`,
42
+ "",
43
+ "Options:",
44
+ " --tools <comma-list> Install to specific tools (skips interactive selection).",
45
+ " Valid keys:",
46
+ ` ${validTools}`,
47
+ " --all Install to all supported tools.",
48
+ " --yes Skip confirmation prompt.",
49
+ " --home <path> Override home directory for destination paths.",
50
+ " --help Show this message."
51
+ ].join("\n") + "\n"
52
+ );
53
+ }
54
+
55
+ function parseArgs(argv) {
56
+ const parsed = {
57
+ all: false,
58
+ yes: false,
59
+ tools: null,
60
+ home: null,
61
+ help: false
62
+ };
63
+
64
+ for (let i = 0; i < argv.length; i += 1) {
65
+ const arg = argv[i];
66
+ if (arg === "--all") {
67
+ parsed.all = true;
68
+ continue;
69
+ }
70
+ if (arg === "--yes") {
71
+ parsed.yes = true;
72
+ continue;
73
+ }
74
+ if (arg === "--help" || arg === "-h") {
75
+ parsed.help = true;
76
+ continue;
77
+ }
78
+ if (arg === "--tools") {
79
+ parsed.tools = argv[i + 1] ?? "";
80
+ i += 1;
81
+ continue;
82
+ }
83
+ if (arg === "--home") {
84
+ parsed.home = argv[i + 1] ?? "";
85
+ i += 1;
86
+ continue;
87
+ }
88
+ throw new Error(`Unknown argument: ${arg}`);
89
+ }
90
+
91
+ if (parsed.all && parsed.tools) {
92
+ throw new Error("Use either --all or --tools, not both.");
93
+ }
94
+
95
+ return parsed;
96
+ }
97
+
98
+ function resolveSourceDir() {
99
+ const scriptPath = fileURLToPath(import.meta.url);
100
+ const scriptDir = path.dirname(scriptPath);
101
+ return path.resolve(scriptDir, "..", "skill");
102
+ }
103
+
104
+ async function ensureSourceDir(sourceDir) {
105
+ let stat;
106
+ try {
107
+ stat = await fs.stat(sourceDir);
108
+ } catch {
109
+ throw new Error(`Missing skill directory: ${sourceDir}`);
110
+ }
111
+
112
+ if (!stat.isDirectory()) {
113
+ throw new Error(`Expected a directory at: ${sourceDir}`);
114
+ }
115
+ }
116
+
117
+ function buildTargets(homeDir) {
118
+ return TOOL_TARGETS.map((tool) => ({
119
+ ...tool,
120
+ targetDir: path.join(homeDir, ...tool.relPath)
121
+ }));
122
+ }
123
+
124
+ function parseToolKeyList(toolList) {
125
+ const keys = String(toolList)
126
+ .split(",")
127
+ .map((part) => part.trim().toLowerCase())
128
+ .filter(Boolean);
129
+
130
+ if (keys.length === 0) {
131
+ throw new Error("No tool keys provided after --tools.");
132
+ }
133
+
134
+ const deduped = [...new Set(keys)];
135
+ const unknown = deduped.filter((key) => !TOOL_TARGETS.some((tool) => tool.key === key));
136
+ if (unknown.length > 0) {
137
+ throw new Error(`Unknown tool keys: ${unknown.join(", ")}`);
138
+ }
139
+
140
+ return deduped;
141
+ }
142
+
143
+ function parseInteractiveSelection(selection, targets) {
144
+ const raw = selection.trim().toLowerCase();
145
+ if (!raw) {
146
+ throw new Error("No selection received.");
147
+ }
148
+ if (raw === "all") {
149
+ return targets;
150
+ }
151
+
152
+ const values = raw
153
+ .split(",")
154
+ .map((part) => part.trim())
155
+ .filter(Boolean);
156
+
157
+ if (values.length === 0) {
158
+ throw new Error("No valid selection received.");
159
+ }
160
+
161
+ const selectedIndexes = [];
162
+ for (const value of values) {
163
+ const parsed = Number(value);
164
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > targets.length) {
165
+ throw new Error(`Invalid selection: ${value}`);
166
+ }
167
+ selectedIndexes.push(parsed - 1);
168
+ }
169
+
170
+ const uniqueIndexes = [...new Set(selectedIndexes)];
171
+ return uniqueIndexes.map((idx) => targets[idx]);
172
+ }
173
+
174
+ async function chooseTargets(parsed, targets) {
175
+ if (parsed.all) {
176
+ return targets;
177
+ }
178
+
179
+ if (parsed.tools) {
180
+ const selectedKeys = parseToolKeyList(parsed.tools);
181
+ return targets.filter((target) => selectedKeys.includes(target.key));
182
+ }
183
+
184
+ const rl = readline.createInterface({ input, output });
185
+ try {
186
+ output.write("Select tools to install Cadence skill into (multi-select).\n");
187
+ output.write("Enter numbers separated by commas, or type 'all'.\n\n");
188
+ targets.forEach((target, idx) => {
189
+ output.write(`${idx + 1}. ${target.label} (${target.targetDir})\n`);
190
+ });
191
+ const answer = await rl.question("\nSelection: ");
192
+ return parseInteractiveSelection(answer, targets);
193
+ } finally {
194
+ rl.close();
195
+ }
196
+ }
197
+
198
+ async function copyEntry(srcPath, destPath) {
199
+ const stat = await fs.lstat(srcPath);
200
+
201
+ if (stat.isDirectory()) {
202
+ await fs.mkdir(destPath, { recursive: true });
203
+ const entries = await fs.readdir(srcPath);
204
+ for (const entry of entries) {
205
+ if (SKIP_NAMES.has(entry)) {
206
+ continue;
207
+ }
208
+ await copyEntry(path.join(srcPath, entry), path.join(destPath, entry));
209
+ }
210
+ return;
211
+ }
212
+
213
+ if (stat.isSymbolicLink()) {
214
+ const linkTarget = await fs.readlink(srcPath);
215
+ try {
216
+ await fs.unlink(destPath);
217
+ } catch {
218
+ // Ignore if destination does not exist.
219
+ }
220
+ await fs.symlink(linkTarget, destPath);
221
+ return;
222
+ }
223
+
224
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
225
+ await fs.copyFile(srcPath, destPath);
226
+ }
227
+
228
+ async function copySkillContents(sourceDir, targetDir) {
229
+ await fs.mkdir(targetDir, { recursive: true });
230
+ const entries = await fs.readdir(sourceDir);
231
+ for (const entry of entries) {
232
+ if (SKIP_NAMES.has(entry)) {
233
+ continue;
234
+ }
235
+ await copyEntry(path.join(sourceDir, entry), path.join(targetDir, entry));
236
+ }
237
+ }
238
+
239
+ async function confirmInstall(parsed, selectedTargets) {
240
+ if (parsed.yes) {
241
+ return true;
242
+ }
243
+
244
+ const rl = readline.createInterface({ input, output });
245
+ try {
246
+ output.write("\nInstall Cadence skill into:\n");
247
+ selectedTargets.forEach((target) => output.write(`- ${target.targetDir}\n`));
248
+ const answer = await rl.question("Continue? [y/N]: ");
249
+ return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
250
+ } finally {
251
+ rl.close();
252
+ }
253
+ }
254
+
255
+ async function main() {
256
+ const binName = path.basename(process.argv[1] || "cadence-install");
257
+ let parsed;
258
+ try {
259
+ parsed = parseArgs(process.argv.slice(2));
260
+ } catch (error) {
261
+ output.write(`Error: ${error.message}\n\n`);
262
+ printHelp(binName);
263
+ process.exitCode = 1;
264
+ return;
265
+ }
266
+
267
+ if (parsed.help) {
268
+ printHelp(binName);
269
+ return;
270
+ }
271
+
272
+ const homeDir = parsed.home || os.homedir();
273
+ const sourceDir = resolveSourceDir();
274
+ await ensureSourceDir(sourceDir);
275
+ const targets = buildTargets(homeDir);
276
+
277
+ let selectedTargets;
278
+ try {
279
+ selectedTargets = await chooseTargets(parsed, targets);
280
+ } catch (error) {
281
+ output.write(`Error: ${error.message}\n`);
282
+ process.exitCode = 1;
283
+ return;
284
+ }
285
+
286
+ if (selectedTargets.length === 0) {
287
+ output.write("No tools selected. Nothing to install.\n");
288
+ return;
289
+ }
290
+
291
+ const confirmed = await confirmInstall(parsed, selectedTargets);
292
+ if (!confirmed) {
293
+ output.write("Installation cancelled.\n");
294
+ return;
295
+ }
296
+
297
+ for (const target of selectedTargets) {
298
+ await copySkillContents(sourceDir, target.targetDir);
299
+ output.write(`Installed to ${target.label}: ${target.targetDir}\n`);
300
+ }
301
+
302
+ output.write("\nCadence skill installation complete.\n");
303
+ }
304
+
305
+ main().catch((error) => {
306
+ output.write(`Installation failed: ${error.message}\n`);
307
+ process.exitCode = 1;
308
+ });
package/skill/SKILL.md ADDED
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: cadence
3
+ description: Structured project operating system for end-to-end greenfield or brownfield delivery. Use when users want the AI to guide the full lifecycle from initialization, requirements, roadmap, and phased execution through milestone audit and completion with deterministic gates, traceability, and rollback-safe execution.
4
+ ---
5
+
6
+ # Cadence
7
+
8
+ ## Overview
9
+ 1. Keep this root skill as an orchestrator.
10
+ 2. Delegate concrete execution to focused subskills.
11
+
12
+ ## Scaffold Gate (Mandatory On First Turn)
13
+ 1. Check for `.cadence` in the project root.
14
+ 2. If `.cadence` is missing, invoke `skills/scaffold/SKILL.md`.
15
+ 3. Scaffold initializes and persists `state.cadence-scripts-dir` for later subskill commands.
16
+ 4. If `.cadence` exists, skip scaffold.
17
+
18
+ ## Prerequisite Gate (Mandatory On First Turn)
19
+ 1. Invoke `skills/prerequisite-gate/SKILL.md`.
20
+ 2. Continue lifecycle and delivery execution only after prerequisite gate pass.
21
+
22
+ ## Ideation Flow
23
+ 1. Invoke `skills/ideator/SKILL.md` when the user is creating a new project idea from scratch and needs guided discovery to a finalized concept.
24
+ 2. Continue downstream planning only after ideation is persisted in `.cadence/cadence.json`.
25
+
26
+ ## Ideation Update Flow
27
+ 1. If the user wants to modify or discuss existing ideation, invoke `skills/ideation-updater/SKILL.md`.
28
+ 2. Use this update flow only for existing project ideation changes; use `skills/ideator/SKILL.md` for net-new ideation discovery.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Cadence"
3
+ short_description: "Lifecycle + delivery system for structured project execution"
4
+ default_prompt: "Use Cadence to guide this project from lifecycle setup through phased execution, traceability, audit, and milestone completion. Always read and apply the active SOUL persona from .cadence/SOUL.json (fallback: SOUL.json). If user intent indicates resuming/continuing work (in any natural-language phrasing), run CADENCE progress, determine the active milestone/phase, and continue with the routed workflow while explicitly notifying the user of current location and next step."
@@ -0,0 +1,52 @@
1
+ ## Workflow Orchestration
2
+
3
+ ### 1. Plan Node Default
4
+ - Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions)
5
+ - If something goes sideways, STOP and re-plan immediately - don't keep pushing
6
+ - Use plan mode for verification steps, not just building
7
+ - Write detailed specs upfront to reduce ambiguity
8
+
9
+ ### 2. Subagent Strategy
10
+ - Use subagents liberally to keep main context window clean
11
+ - Offload research, exploration, and parallel analysis to subagents
12
+ - For complex problems, throw more compute at it via subagents
13
+ - One tack per subagent for focused execution
14
+
15
+ ### 3. Self-Improvement Loop
16
+ - After ANY correction from the user: update `.cadence/tasks/lessons.md` with the pattern
17
+ - Write rules for yourself that prevent the same mistake
18
+ - Ruthlessly iterate on these lessons until mistake rate drops
19
+ - Review lessons at session start for relevant project
20
+
21
+ ### 4. Verification Before Done
22
+ - Never mark a task complete without proving it works
23
+ - Diff behavior between main and your changes when relevant
24
+ - Ask yourself: "Would a staff engineer approve this?"
25
+ - Run tests, check logs, demonstrate correctness
26
+
27
+ ### 5. Demand Elegance (Balanced)
28
+ - For non-trivial changes: pause and ask "is there a more elegant way?"
29
+ - If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
30
+ - Skip this for simple, obvious fixes - don't over-engineer
31
+ - Challenge your own work before presenting it
32
+
33
+ ### 6. Autonomous Bug Fixing
34
+ - When given a bug report: just fix it. Don't ask for hand-holding
35
+ - Point at logs, errors, failing tests - then resolve them
36
+ - Zero context switching required from the user
37
+ - Go fix failing CI tests without being told how
38
+
39
+ ## Task Management
40
+
41
+ 1. **Plan First**: Write plan to `.cadence/tasks/todo.md` with checkable items
42
+ 2. **Verify Plan**: Check in before starting implementation
43
+ 3. **Track Progress**: Mark items complete as you go
44
+ 4. **Explain Changes**: High-level summary at each step
45
+ 5. **Document Results**: Add review section to `.cadence/tasks/todo.md`
46
+ 6. **Capture Lessons**: Update `.cadence/tasks/lessons.md` after corrections
47
+
48
+ ## Core Principles
49
+
50
+ - **Simplicity First**: Make every change as simple as possible. Impact minimal code.
51
+ - **No Laziness**: Find root causes. No temporary fixes. Senior developer standards.
52
+ - **Minimat Impact**: Changes should only touch what's necessary. Avoid introducing bugs.
@@ -0,0 +1,9 @@
1
+ {
2
+ "prerequisites-pass": false,
3
+ "state": {
4
+ "ideation-completed": false,
5
+ "cadence-scripts-dir": ""
6
+ },
7
+ "project-details": {},
8
+ "ideation": {}
9
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env python3
2
+ """Expose .cadence/cadence.json ideation payload for AI consumption."""
3
+
4
+ import json
5
+ from pathlib import Path
6
+
7
+
8
+ CADENCE_JSON_PATH = Path(".cadence") / "cadence.json"
9
+
10
+
11
+ def load_ideation():
12
+ if not CADENCE_JSON_PATH.exists():
13
+ return {}
14
+
15
+ with CADENCE_JSON_PATH.open("r", encoding="utf-8") as file:
16
+ data = json.load(file)
17
+
18
+ ideation = data.get("ideation", {})
19
+ if isinstance(ideation, dict):
20
+ return ideation
21
+ return {}
22
+
23
+
24
+ print(json.dumps(load_ideation(), indent=4))
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env python3
2
+ """Read and print the ideation payload from .cadence/cadence.json."""
3
+
4
+ import json
5
+ from pathlib import Path
6
+
7
+
8
+ CADENCE_JSON_PATH = Path(".cadence") / "cadence.json"
9
+
10
+
11
+ if CADENCE_JSON_PATH.exists():
12
+ with CADENCE_JSON_PATH.open("r", encoding="utf-8") as file:
13
+ data = json.load(file)
14
+ else:
15
+ data = {"ideation": {}}
16
+
17
+ print(json.dumps(data.get("ideation", {}), indent=4))
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+ """Read or update prerequisite pass state in .cadence/cadence.json."""
3
+
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+
8
+
9
+ CADENCE_JSON_PATH = Path(".cadence") / "cadence.json"
10
+
11
+
12
+ def default_data():
13
+ return {
14
+ "prerequisites-pass": False,
15
+ "state": {
16
+ "ideation-completed": False,
17
+ "cadence-scripts-dir": "",
18
+ },
19
+ "project-details": {},
20
+ "ideation": {},
21
+ }
22
+
23
+
24
+ def load_data():
25
+ if not CADENCE_JSON_PATH.exists():
26
+ return default_data()
27
+ with CADENCE_JSON_PATH.open("r", encoding="utf-8") as file:
28
+ return json.load(file)
29
+
30
+
31
+ def save_data(data):
32
+ CADENCE_JSON_PATH.parent.mkdir(parents=True, exist_ok=True)
33
+ with CADENCE_JSON_PATH.open("w", encoding="utf-8") as file:
34
+ json.dump(data, file, indent=4)
35
+ file.write("\n")
36
+
37
+
38
+ def main():
39
+ if len(sys.argv) > 2:
40
+ print("Usage: handle-prerequisite-state.py [0|1]", file=sys.stderr)
41
+ return 2
42
+
43
+ if len(sys.argv) == 2 and sys.argv[1] not in {"0", "1"}:
44
+ print("Usage: handle-prerequisite-state.py [0|1]", file=sys.stderr)
45
+ return 2
46
+
47
+ data = load_data()
48
+
49
+ if len(sys.argv) == 1:
50
+ print("true" if bool(data.get("prerequisites-pass", False)) else "false")
51
+ return 0
52
+
53
+ data["prerequisites-pass"] = sys.argv[1] == "1"
54
+ save_data(data)
55
+ print("true" if data["prerequisites-pass"] else "false")
56
+ return 0
57
+
58
+
59
+ if __name__ == "__main__":
60
+ raise SystemExit(main())
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env python3
2
+ """Persist Cadence helper scripts directory in .cadence/cadence.json."""
3
+
4
+ import json
5
+ from pathlib import Path
6
+
7
+
8
+ CADENCE_DIR = Path(".cadence")
9
+ CADENCE_JSON_PATH = CADENCE_DIR / "cadence.json"
10
+ GETTER_PATH = CADENCE_DIR / "get-cadence-scripts-dir.py"
11
+
12
+
13
+ def default_data():
14
+ return {
15
+ "prerequisites-pass": False,
16
+ "state": {
17
+ "ideation-completed": False,
18
+ "cadence-scripts-dir": "",
19
+ },
20
+ "project-details": {},
21
+ "ideation": {},
22
+ }
23
+
24
+
25
+ def load_data():
26
+ if not CADENCE_JSON_PATH.exists():
27
+ return default_data()
28
+ with CADENCE_JSON_PATH.open("r", encoding="utf-8") as file:
29
+ return json.load(file)
30
+
31
+
32
+ def save_data(data):
33
+ CADENCE_DIR.mkdir(parents=True, exist_ok=True)
34
+ with CADENCE_JSON_PATH.open("w", encoding="utf-8") as file:
35
+ json.dump(data, file, indent=4)
36
+ file.write("\n")
37
+
38
+
39
+ def write_getter_script():
40
+ content = """#!/usr/bin/env python3
41
+ import json
42
+ import sys
43
+ from pathlib import Path
44
+
45
+ cadence_json_path = Path(".cadence") / "cadence.json"
46
+ if not cadence_json_path.exists():
47
+ print("MISSING_CADENCE_JSON", file=sys.stderr)
48
+ raise SystemExit(1)
49
+
50
+ try:
51
+ with cadence_json_path.open("r", encoding="utf-8") as file:
52
+ data = json.load(file)
53
+ except json.JSONDecodeError as exc:
54
+ print(f"INVALID_CADENCE_JSON: {exc}", file=sys.stderr)
55
+ raise SystemExit(1)
56
+
57
+ scripts_dir = data.get("state", {}).get("cadence-scripts-dir", "")
58
+ if not scripts_dir:
59
+ print("MISSING_CADENCE_SCRIPTS_DIR", file=sys.stderr)
60
+ raise SystemExit(1)
61
+
62
+ print(scripts_dir)
63
+ """
64
+ GETTER_PATH.write_text(content, encoding="utf-8")
65
+
66
+
67
+ def main():
68
+ if not CADENCE_DIR.exists():
69
+ print("MISSING_CADENCE_DIR")
70
+ return 1
71
+
72
+ scripts_dir = str(Path(__file__).resolve().parent)
73
+
74
+ data = load_data()
75
+ state = data.setdefault("state", {})
76
+ state["cadence-scripts-dir"] = scripts_dir
77
+ save_data(data)
78
+ write_getter_script()
79
+
80
+ print(json.dumps({"status": "ok", "cadence_scripts_dir": scripts_dir, "getter_path": str(GETTER_PATH)}))
81
+ return 0
82
+
83
+
84
+ if __name__ == "__main__":
85
+ raise SystemExit(main())
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ """Inject finalized ideation payload into .cadence/cadence.json."""
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+
10
+ CADENCE_JSON_PATH = Path(".cadence") / "cadence.json"
11
+
12
+
13
+ def default_data():
14
+ return {
15
+ "prerequisites-pass": False,
16
+ "state": {
17
+ "ideation-completed": False,
18
+ "cadence-scripts-dir": "",
19
+ },
20
+ "project-details": {},
21
+ "ideation": {},
22
+ }
23
+
24
+
25
+ def load_cadence():
26
+ if not CADENCE_JSON_PATH.exists():
27
+ return default_data()
28
+ try:
29
+ with CADENCE_JSON_PATH.open("r", encoding="utf-8") as file:
30
+ return json.load(file)
31
+ except json.JSONDecodeError as exc:
32
+ raise ValueError(f"Invalid JSON in {CADENCE_JSON_PATH}: {exc}") from exc
33
+
34
+
35
+ def save_cadence(data):
36
+ CADENCE_JSON_PATH.parent.mkdir(parents=True, exist_ok=True)
37
+ with CADENCE_JSON_PATH.open("w", encoding="utf-8") as file:
38
+ json.dump(data, file, indent=4)
39
+ file.write("\n")
40
+
41
+
42
+ def deep_merge(base, patch):
43
+ merged = dict(base)
44
+ for key, value in patch.items():
45
+ if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
46
+ merged[key] = deep_merge(merged[key], value)
47
+ else:
48
+ merged[key] = value
49
+ return merged
50
+
51
+
52
+ def parse_payload(args):
53
+ payload_file_path = None
54
+ if args.file:
55
+ payload_file_path = Path(args.file)
56
+ try:
57
+ payload_text = payload_file_path.read_text(encoding="utf-8")
58
+ except OSError as exc:
59
+ raise ValueError(f"Unable to read payload file {args.file}: {exc}") from exc
60
+ elif args.json:
61
+ payload_text = args.json
62
+ elif args.stdin:
63
+ payload_text = sys.stdin.read()
64
+ else:
65
+ raise ValueError("One payload input source is required.")
66
+
67
+ try:
68
+ payload = json.loads(payload_text)
69
+ except json.JSONDecodeError as exc:
70
+ raise ValueError(f"Invalid JSON payload: {exc}") from exc
71
+
72
+ if not isinstance(payload, dict):
73
+ raise ValueError("Payload must be a JSON object.")
74
+ return payload, payload_file_path
75
+
76
+
77
+ def apply_completion_state(data, completion_state):
78
+ state = data.setdefault("state", {})
79
+ if completion_state == "complete":
80
+ state["ideation-completed"] = True
81
+ elif completion_state == "incomplete":
82
+ state["ideation-completed"] = False
83
+
84
+
85
+ def parse_args():
86
+ parser = argparse.ArgumentParser(
87
+ description="Inject finalized ideation payload into .cadence/cadence.json."
88
+ )
89
+ group = parser.add_mutually_exclusive_group(required=True)
90
+ group.add_argument("--file", help="Read ideation payload JSON from file path")
91
+ group.add_argument("--json", help="Read ideation payload JSON from inline string")
92
+ group.add_argument("--stdin", action="store_true", help="Read ideation payload JSON from stdin")
93
+ parser.add_argument(
94
+ "--completion-state",
95
+ choices=["complete", "incomplete", "keep"],
96
+ default="complete",
97
+ help="How to update state.ideation-completed",
98
+ )
99
+ parser.add_argument(
100
+ "--merge",
101
+ action="store_true",
102
+ help="Merge payload into existing ideation object instead of replacing it",
103
+ )
104
+ return parser.parse_args()
105
+
106
+
107
+ def main():
108
+ args = parse_args()
109
+
110
+ try:
111
+ data = load_cadence()
112
+ payload, payload_file_path = parse_payload(args)
113
+ except ValueError as exc:
114
+ print(str(exc), file=sys.stderr)
115
+ return 2
116
+
117
+ existing_ideation = data.get("ideation", {})
118
+
119
+ if args.merge and isinstance(existing_ideation, dict):
120
+ data["ideation"] = deep_merge(existing_ideation, payload)
121
+ else:
122
+ data["ideation"] = payload
123
+
124
+ apply_completion_state(data, args.completion_state)
125
+ save_cadence(data)
126
+
127
+ payload_deleted = False
128
+ if payload_file_path is not None:
129
+ try:
130
+ payload_file_path.unlink()
131
+ payload_deleted = True
132
+ except OSError as exc:
133
+ print(f"Unable to delete payload file {payload_file_path}: {exc}", file=sys.stderr)
134
+ return 3
135
+
136
+ print(
137
+ json.dumps(
138
+ {
139
+ "status": "ok",
140
+ "path": str(CADENCE_JSON_PATH),
141
+ "completion_state": args.completion_state,
142
+ "payload_deleted": payload_deleted,
143
+ }
144
+ )
145
+ )
146
+ return 0
147
+
148
+
149
+ if __name__ == "__main__":
150
+ raise SystemExit(main())
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env python3
2
+ """Render .cadence/cadence.json ideation payload in human-readable text."""
3
+
4
+ import json
5
+ from pathlib import Path
6
+
7
+
8
+ CADENCE_JSON_PATH = Path(".cadence") / "cadence.json"
9
+
10
+
11
+ def humanize_key(key):
12
+ return str(key).replace("_", " ").replace("-", " ").strip().title()
13
+
14
+
15
+ def scalar_to_text(value):
16
+ if isinstance(value, bool):
17
+ return "Yes" if value else "No"
18
+ if value is None:
19
+ return "None"
20
+ return str(value)
21
+
22
+
23
+ def render_value(value, indent=0):
24
+ space = " " * indent
25
+ lines = []
26
+
27
+ if isinstance(value, dict):
28
+ if not value:
29
+ return [f"{space}(empty)"]
30
+ for key, inner in value.items():
31
+ label = humanize_key(key)
32
+ if isinstance(inner, (dict, list)):
33
+ lines.append(f"{space}- {label}:")
34
+ lines.extend(render_value(inner, indent + 2))
35
+ else:
36
+ lines.append(f"{space}- {label}: {scalar_to_text(inner)}")
37
+ return lines
38
+
39
+ if isinstance(value, list):
40
+ if not value:
41
+ return [f"{space}(empty list)"]
42
+ for idx, item in enumerate(value, start=1):
43
+ if isinstance(item, (dict, list)):
44
+ lines.append(f"{space}- Item {idx}:")
45
+ lines.extend(render_value(item, indent + 2))
46
+ else:
47
+ lines.append(f"{space}- {scalar_to_text(item)}")
48
+ return lines
49
+
50
+ return [f"{space}{scalar_to_text(value)}"]
51
+
52
+
53
+ def load_ideation():
54
+ if not CADENCE_JSON_PATH.exists():
55
+ return {}
56
+ with CADENCE_JSON_PATH.open("r", encoding="utf-8") as file:
57
+ data = json.load(file)
58
+ ideation = data.get("ideation", {})
59
+ return ideation if isinstance(ideation, dict) else {}
60
+
61
+
62
+ def main():
63
+ ideation = load_ideation()
64
+ print("Current Project Ideation")
65
+ print("========================")
66
+ if not ideation:
67
+ print("No ideation is currently saved.")
68
+ return
69
+
70
+ for line in render_value(ideation):
71
+ print(line)
72
+
73
+
74
+ if __name__ == "__main__":
75
+ main()
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env python3
2
+ """Resolve Cadence helper scripts dir for the current project.
3
+
4
+ Behavior:
5
+ - If .cadence/get-cadence-scripts-dir.py exists, use it.
6
+ - If missing but .cadence exists, regenerate project path state by running
7
+ init-cadence-scripts-dir.py from this skill's scripts directory.
8
+ - Print the resolved scripts directory to stdout.
9
+ """
10
+
11
+ import subprocess
12
+ import sys
13
+ from pathlib import Path
14
+
15
+
16
+ CADENCE_DIR = Path(".cadence")
17
+ GETTER_PATH = CADENCE_DIR / "get-cadence-scripts-dir.py"
18
+ SCRIPT_DIR = Path(__file__).resolve().parent
19
+ INIT_SCRIPT_PATH = SCRIPT_DIR / "init-cadence-scripts-dir.py"
20
+
21
+
22
+ def run_command(command):
23
+ return subprocess.run(command, capture_output=True, text=True, check=False)
24
+
25
+
26
+ def ensure_getter():
27
+ if GETTER_PATH.exists():
28
+ return
29
+
30
+ if not CADENCE_DIR.exists():
31
+ print("MISSING_CADENCE_DIR", file=sys.stderr)
32
+ raise SystemExit(1)
33
+
34
+ result = run_command([sys.executable, str(INIT_SCRIPT_PATH)])
35
+ if result.returncode != 0:
36
+ stderr = result.stderr.strip() or "FAILED_TO_INIT_CADENCE_SCRIPTS_DIR"
37
+ print(stderr, file=sys.stderr)
38
+ raise SystemExit(result.returncode)
39
+
40
+
41
+ def load_scripts_dir():
42
+ result = run_command([sys.executable, str(GETTER_PATH)])
43
+ if result.returncode != 0:
44
+ stderr = result.stderr.strip() or "MISSING_CADENCE_SCRIPTS_DIR"
45
+ print(stderr, file=sys.stderr)
46
+ raise SystemExit(result.returncode)
47
+
48
+ scripts_dir = result.stdout.strip()
49
+ if not scripts_dir:
50
+ print("MISSING_CADENCE_SCRIPTS_DIR", file=sys.stderr)
51
+ raise SystemExit(1)
52
+
53
+ print(scripts_dir)
54
+
55
+
56
+ def main():
57
+ ensure_getter()
58
+ load_scripts_dir()
59
+
60
+
61
+ if __name__ == "__main__":
62
+ main()
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env python3
2
+ """Run Cadence prerequisite gate and persist pass state."""
3
+
4
+ import json
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+
10
+
11
+ SCRIPT_DIR = Path(__file__).resolve().parent
12
+ RESOLVE_SCRIPT = SCRIPT_DIR / "resolve-project-scripts-dir.py"
13
+
14
+
15
+ def run_command(command):
16
+ return subprocess.run(command, capture_output=True, text=True, check=False)
17
+
18
+
19
+ def resolve_scripts_dir():
20
+ result = run_command([sys.executable, str(RESOLVE_SCRIPT)])
21
+ if result.returncode != 0:
22
+ stderr = result.stderr.strip() or "MISSING_CADENCE_SCRIPTS_DIR"
23
+ print(stderr, file=sys.stderr)
24
+ raise SystemExit(result.returncode)
25
+
26
+ scripts_dir = result.stdout.strip()
27
+ if not scripts_dir:
28
+ print("MISSING_CADENCE_SCRIPTS_DIR", file=sys.stderr)
29
+ raise SystemExit(1)
30
+
31
+ return scripts_dir
32
+
33
+
34
+ def read_prerequisite_state(scripts_dir):
35
+ script_path = Path(scripts_dir) / "handle-prerequisite-state.py"
36
+ result = run_command([sys.executable, str(script_path)])
37
+ if result.returncode != 0:
38
+ stderr = result.stderr.strip() or "PREREQUISITE_STATE_READ_FAILED"
39
+ print(stderr, file=sys.stderr)
40
+ raise SystemExit(result.returncode)
41
+ return result.stdout.strip()
42
+
43
+
44
+ def write_prerequisite_state(scripts_dir, pass_state):
45
+ script_path = Path(scripts_dir) / "handle-prerequisite-state.py"
46
+ result = run_command([sys.executable, str(script_path), pass_state])
47
+ if result.returncode != 0:
48
+ stderr = result.stderr.strip() or "PREREQUISITE_STATE_WRITE_FAILED"
49
+ print(stderr, file=sys.stderr)
50
+ raise SystemExit(result.returncode)
51
+
52
+
53
+ def main():
54
+ scripts_dir = resolve_scripts_dir()
55
+ state = read_prerequisite_state(scripts_dir)
56
+
57
+ if state == "true":
58
+ print(json.dumps({"status": "ok", "prerequisites_pass": True, "source": "cache"}))
59
+ return
60
+
61
+ if shutil.which("python3") is None:
62
+ print("MISSING_PYTHON3", file=sys.stderr)
63
+ raise SystemExit(1)
64
+
65
+ write_prerequisite_state(scripts_dir, "1")
66
+ print(json.dumps({"status": "ok", "prerequisites_pass": True, "source": "fresh-check"}))
67
+
68
+
69
+ if __name__ == "__main__":
70
+ main()
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ """Run Cadence scaffold gate in one deterministic command."""
3
+
4
+ import json
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+
10
+ CADENCE_JSON_PATH = Path(".cadence") / "cadence.json"
11
+ GETTER_PATH = Path(".cadence") / "get-cadence-scripts-dir.py"
12
+ SCRIPT_DIR = Path(__file__).resolve().parent
13
+ SCAFFOLD_SCRIPT = SCRIPT_DIR / "scaffold-project.sh"
14
+ INIT_SCRIPT = SCRIPT_DIR / "init-cadence-scripts-dir.py"
15
+
16
+
17
+ def run_command(command):
18
+ return subprocess.run(command, capture_output=True, text=True, check=False)
19
+
20
+
21
+ def run_scaffold():
22
+ result = run_command(["bash", str(SCAFFOLD_SCRIPT)])
23
+ if result.returncode != 0:
24
+ stderr = result.stderr.strip() or "SCAFFOLD_FAILED"
25
+ print(stderr, file=sys.stderr)
26
+ raise SystemExit(result.returncode)
27
+
28
+ return result.stdout.strip() or "scaffold-created"
29
+
30
+
31
+ def initialize_scripts_dir():
32
+ result = run_command([sys.executable, str(INIT_SCRIPT)])
33
+ if result.returncode != 0:
34
+ stderr = result.stderr.strip() or "INIT_CADENCE_SCRIPTS_DIR_FAILED"
35
+ print(stderr, file=sys.stderr)
36
+ raise SystemExit(result.returncode)
37
+
38
+
39
+ def verify_expected_files():
40
+ if not CADENCE_JSON_PATH.exists():
41
+ print("CADENCE_JSON_MISSING", file=sys.stderr)
42
+ raise SystemExit(1)
43
+
44
+ if not GETTER_PATH.exists():
45
+ print("MISSING_CADENCE_GETTER", file=sys.stderr)
46
+ raise SystemExit(1)
47
+
48
+
49
+ def main():
50
+ scaffold_status = run_scaffold()
51
+
52
+ if scaffold_status == "scaffold-created" or not GETTER_PATH.exists():
53
+ initialize_scripts_dir()
54
+
55
+ verify_expected_files()
56
+
57
+ print(json.dumps({"status": "ok", "scaffold_status": scaffold_status}))
58
+
59
+
60
+ if __name__ == "__main__":
61
+ main()
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ -d ".cadence" ]; then
5
+ echo "scaffold-skipped"
6
+ exit 0
7
+ fi
8
+
9
+ mkdir -p ".cadence"
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ TEMPLATE_PATH="${SCRIPT_DIR}/../assets/cadence.json"
13
+ TARGET_PATH=".cadence/cadence.json"
14
+
15
+ if [ -f "${TEMPLATE_PATH}" ]; then
16
+ cp "${TEMPLATE_PATH}" "${TARGET_PATH}"
17
+ else
18
+ cat > "${TARGET_PATH}" <<'JSON'
19
+ {
20
+ "prerequisites-pass": false,
21
+ "state": {
22
+ "ideation-completed": false,
23
+ "cadence-scripts-dir": ""
24
+ },
25
+ "project-details": {},
26
+ "ideation": {}
27
+ }
28
+ JSON
29
+ fi
30
+
31
+ echo "scaffold-created"
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: ideation-updater
3
+ description: Discuss, audit, and update existing project ideation in .cadence/cadence.json while preserving full-project context. Use when the user wants to add missing aspects, revise assumptions, remove scope, or explore tradeoffs within an already defined project idea.
4
+ ---
5
+
6
+ # Ideation Updater
7
+
8
+ 1. Invoke this skill only when user intent is to discuss or modify already-saved ideation.
9
+ 2. Route first-time concept discovery to `skills/ideator/SKILL.md`.
10
+ 3. Resolve helper scripts dir by running `python3 ../../scripts/resolve-project-scripts-dir.py` and store stdout in `CADENCE_SCRIPTS_DIR`.
11
+ 4. First message behavior in this skill conversation:
12
+ - Run `python3 "$CADENCE_SCRIPTS_DIR/expose-ideation.py"` and use the JSON output as active AI context.
13
+ - Run `python3 "$CADENCE_SCRIPTS_DIR/render-ideation-summary.py"` and show that human-readable summary to the user.
14
+ - Ask one intent-setting question: discussion only, add, change, or remove.
15
+ 5. Run the conversation one step at a time:
16
+ - Ask exactly one question per turn.
17
+ - Keep the full current project idea in mind while focusing on the selected area.
18
+ - After each user answer, restate what changed and what remains unchanged.
19
+ 6. Support deep-dive updates for missing aspects:
20
+ - If user says they forgot a topic, zoom into that topic and drill until clear.
21
+ - Adapt topic depth to domain context (for example mechanics/systems, audience, scope boundaries, tech/process, risks, success criteria).
22
+ - Avoid hard-coded question trees; derive next question from current context.
23
+ 7. Distinguish interaction modes clearly:
24
+ - Discussion mode: analyze options and tradeoffs, do not persist.
25
+ - Add or modify mode: prepare a minimal patch payload and merge.
26
+ - Remove mode: rebuild the full ideation object without removed fields and replace.
27
+ 8. Before any write, present a short change plan with:
28
+ - Fields to add
29
+ - Fields to update
30
+ - Fields to remove
31
+ - Fields unchanged
32
+ 9. Persist only after user confirmation.
33
+ 10. For add or modify mode:
34
+ - Write changed fields to `.cadence/ideation_payload.json`.
35
+ - Run `python3 "$CADENCE_SCRIPTS_DIR/inject-ideation.py" --file .cadence/ideation_payload.json --merge --completion-state keep`.
36
+ 11. For remove mode or structural rewrites:
37
+ - Write the complete updated ideation object to `.cadence/ideation_payload.json`.
38
+ - Run `python3 "$CADENCE_SCRIPTS_DIR/inject-ideation.py" --file .cadence/ideation_payload.json --completion-state keep`.
39
+ 12. After persistence, confirm result by running `python3 "$CADENCE_SCRIPTS_DIR/render-ideation-summary.py"`.
40
+ 13. Ask whether to continue refining another aspect or stop.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Ideation Updater"
3
+ short_description: "Discuss and update existing project ideation"
4
+ default_prompt: "Review current project ideation, discuss changes, and persist approved updates."
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: ideator
3
+ description: Guide users from a rough concept to a fully defined project idea through adaptive, one-question-at-a-time discovery. Use when users want to shape or refine what they want to build, write, or create across any domain, then persist final ideation into .cadence/cadence.json.
4
+ ---
5
+
6
+ # Ideator
7
+
8
+ 1. Start from the user's seed idea and briefly restate your understanding.
9
+ 2. Ask exactly one question at a time. Never ask a batch of questions in a single turn.
10
+ 3. After each user answer:
11
+ - Summarize what changed in one short sentence.
12
+ - Decide the next highest-leverage unknown.
13
+ - Ask one natural follow-up question.
14
+ 4. Keep discovery domain-agnostic and adaptive:
15
+ - Derive the question path from the user's domain and prior answers.
16
+ - Do not force fixed templates or hard-coded checklists during discovery.
17
+ - Drill deep where ambiguity remains; move on when the topic is clear.
18
+ 5. Build understanding until the idea is execution-ready. Cover the relevant dimensions for the domain, including:
19
+ - objective and core outcome
20
+ - target audience or user
21
+ - core experience or structure (for example mechanics, flow, chapters, systems)
22
+ - scope boundaries (in-scope vs out-of-scope)
23
+ - implementation approach (for example tools, tech stack, process, platforms)
24
+ - delivery shape (milestones, sequencing, constraints, risks, success signals)
25
+ 6. Do not hard-code assumptions. If you infer something, label it explicitly and ask for confirmation.
26
+ 7. When coverage is deep enough, present a final ideation summary and ask for confirmation.
27
+ 8. Resolve helper scripts dir by running `python3 ../../scripts/resolve-project-scripts-dir.py` and store stdout in `CADENCE_SCRIPTS_DIR`.
28
+ 9. After confirmation, persist ideation programmatically:
29
+ - Create a JSON payload file at `.cadence/ideation_payload.json`.
30
+ - Write the full finalized ideation object to that file.
31
+ - Run `python3 "$CADENCE_SCRIPTS_DIR/inject-ideation.py" --file .cadence/ideation_payload.json --completion-state complete` (this injects ideation and deletes `.cadence/ideation_payload.json` on success).
32
+ 10. Verify persistence by running `python3 "$CADENCE_SCRIPTS_DIR/get-ideation.py"`.
33
+ 11. If the user requests revisions later, regenerate the payload and rerun `inject-ideation.py`.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Ideator"
3
+ short_description: "Step-by-step domain-agnostic ideation"
4
+ default_prompt: "Guide the user from a rough concept to a fully defined idea with one question at a time, then persist the final ideation payload."
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: prerequisite-gate
3
+ description: Run and persist Cadence prerequisite checks for Python availability. Use when Cadence starts work after scaffolding and must confirm prerequisites before lifecycle or delivery commands.
4
+ ---
5
+
6
+ # Prerequisite Gate
7
+
8
+ 1. Run this only after scaffold routing from `skills/scaffold/SKILL.md`.
9
+ 2. Run `python3 ../../scripts/run-prerequisite-gate.py` (resolve this relative path from this sub-skill directory).
10
+ 3. If the script reports `MISSING_PYTHON3`, stop and ask the user for confirmation to install prerequisites.
11
+ 4. Do not continue Cadence lifecycle or delivery execution while prerequisites are missing.
12
+ 5. Surface script failures verbatim instead of adding custom fallback logic.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Prerequisite Gate"
3
+ short_description: "Run and persist Cadence prerequisite checks for Python availability"
4
+ default_prompt: "Run Cadence prerequisite checks, verify Python availability, and persist pass state before lifecycle or delivery execution."
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: scaffold
3
+ description: Initialize Cadence project scaffolding for first-time setup. Use when the target project root does not yet contain a .cadence directory and Cadence must create initial state files before other workflow gates run.
4
+ ---
5
+
6
+ # Scaffold
7
+
8
+ 1. Run this only from the target project root.
9
+ 2. Run `python3 ../../scripts/run-scaffold-gate.py` (resolve this relative path from this sub-skill directory).
10
+ 3. If the script reports `scaffold-skipped`, treat scaffold as already satisfied and continue.
11
+ 4. If it errors, stop and surface the exact error to the user.
12
+ 5. Execute scaffold gate serially. Do not run it in parallel with other setup gates.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Scaffold"
3
+ short_description: "Initialize Cadence project scaffolding for first-time setup"
4
+ default_prompt: "Initialize Cadence project scaffolding in the target project root, creating .cadence state files only when they do not already exist."