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 +46 -0
- package/package.json +22 -0
- package/scripts/install-cadence-skill.mjs +308 -0
- package/skill/SKILL.md +28 -0
- package/skill/agents/openai.yaml +4 -0
- package/skill/assets/AGENTS.md +52 -0
- package/skill/assets/cadence.json +9 -0
- package/skill/scripts/expose-ideation.py +24 -0
- package/skill/scripts/get-ideation.py +17 -0
- package/skill/scripts/handle-prerequisite-state.py +60 -0
- package/skill/scripts/init-cadence-scripts-dir.py +85 -0
- package/skill/scripts/inject-ideation.py +150 -0
- package/skill/scripts/render-ideation-summary.py +75 -0
- package/skill/scripts/resolve-project-scripts-dir.py +62 -0
- package/skill/scripts/run-prerequisite-gate.py +70 -0
- package/skill/scripts/run-scaffold-gate.py +61 -0
- package/skill/scripts/scaffold-project.sh +31 -0
- package/skill/skills/ideation-updater/SKILL.md +40 -0
- package/skill/skills/ideation-updater/agents/openai.yaml +4 -0
- package/skill/skills/ideator/SKILL.md +33 -0
- package/skill/skills/ideator/agents/openai.yaml +4 -0
- package/skill/skills/prerequisite-gate/SKILL.md +12 -0
- package/skill/skills/prerequisite-gate/agents/openai.yaml +4 -0
- package/skill/skills/scaffold/SKILL.md +12 -0
- package/skill/skills/scaffold/agents/openai.yaml +4 -0
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,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,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,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."
|