@deniscodp/skills 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +89 -2
- package/bin/deniscodp-skills.mjs +4 -0
- package/package.json +23 -4
- package/scripts/catalog.mjs +68 -0
- package/scripts/cli.mjs +264 -0
- package/scripts/install.mjs +184 -0
- package/scripts/recommended.mjs +102 -0
- package/skills/denisco-handoff-implementation/SKILL.md +254 -0
- package/skills/denisco-handoff-implementation/references/blocker-checklist.md +71 -0
- package/skills/denisco-handoff-implementation/references/delegated-implementation.md +168 -0
- package/skills/denisco-handoff-implementation/references/ownership.md +105 -0
- package/skills/denisco-handoff-implementation/references/session-plan.md +195 -0
- package/skills/denisco-handoff-implementation/scripts/ownership.mjs +153 -0
- package/skills/denisco-implementation-coordinator/SKILL.md +237 -0
- package/skills/denisco-implementation-coordinator/agents/openai.yaml +2 -0
- package/skills/denisco-implementation-coordinator/references/documents.md +175 -0
- package/skills/denisco-implementation-coordinator/references/runtimes.md +105 -0
- package/skills/denisco-interactive-spec-definition/SKILL.md +126 -0
- package/skills/denisco-interactive-spec-definition/references/blocker-checklist.md +71 -0
- package/skills/denisco-interactive-spec-definition/references/handoff-template.md +124 -0
- package/skills/denisco-project-context/SKILL.md +60 -0
- package/skills/denisco-project-context/references/project-documents.md +70 -0
- package/skills/denisco-visual-check/SKILL.md +136 -0
- package/skills/denisco-visual-check/references/judge-prompt.md +55 -0
- package/skills/denisco-visual-check/references/project-config.md +118 -0
- package/skills/denisco-workflow-benchmark/SKILL.md +113 -0
- package/skills/denisco-workflow-benchmark/agents/openai.yaml +2 -0
- package/skills/denisco-workflow-benchmark/references/measurement.md +166 -0
- package/skills/denisco-workflow-benchmark/references/workflow-contract.md +371 -0
- package/skills/denisco-workflow-benchmark/scripts/bench.mjs +156 -0
- package/skills/denisco-workflow-benchmark/scripts/cases.mjs +46 -0
- package/skills/denisco-workflow-benchmark/scripts/configuration.mjs +71 -0
- package/skills/denisco-workflow-benchmark/scripts/discover.mjs +88 -0
- package/skills/denisco-workflow-benchmark/scripts/eval-worker.mjs +58 -0
- package/skills/denisco-workflow-benchmark/scripts/io.mjs +82 -0
- package/skills/denisco-workflow-benchmark/scripts/prepare.mjs +69 -0
- package/skills/denisco-workflow-benchmark/scripts/runs.mjs +235 -0
- package/skills/denisco-workflow-benchmark/scripts/state-prompt.mjs +96 -0
- package/skills/denisco-workflow-benchmark/scripts/tracking.mjs +91 -0
- package/skills/denisco-workflow-benchmark/scripts/usage.mjs +175 -0
- package/skills/denisco-workflow-benchmark/scripts/workflow.mjs +334 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { hash, inside, readJSON, writeJSON, rejectSymlinkPath } from '../skills/denisco-workflow-benchmark/scripts/io.mjs';
|
|
5
|
+
import { ownership } from '../skills/denisco-handoff-implementation/scripts/ownership.mjs';
|
|
6
|
+
import { discoverSkills, repository } from './catalog.mjs';
|
|
7
|
+
|
|
8
|
+
export { repository } from './catalog.mjs';
|
|
9
|
+
|
|
10
|
+
// Managed skills installed under an earlier name. An unmodified old copy is removed when the
|
|
11
|
+
// renamed skill is installed in the same root; a modified one stops the installation.
|
|
12
|
+
export const renamedSkills = Object.freeze({
|
|
13
|
+
'project-context': 'denisco-project-context',
|
|
14
|
+
'interactive-spec-definition': 'denisco-interactive-spec-definition',
|
|
15
|
+
'handoff-implementation': 'denisco-handoff-implementation',
|
|
16
|
+
'implementation-coordinator': 'denisco-implementation-coordinator',
|
|
17
|
+
'visual-check': 'denisco-visual-check',
|
|
18
|
+
'workflow-benchmark': 'denisco-workflow-benchmark'
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Git with core.autocrlf rewrites line endings on checkout, stash or stage. A copy that differs from
|
|
22
|
+
// a recorded digest only in CRLF/LF is the same managed content, not a local modification.
|
|
23
|
+
const toLF = content => Buffer.from(content.toString('utf8').replace(/\r\n/g, '\n'));
|
|
24
|
+
const toCRLF = content => Buffer.from(toLF(content).toString('utf8').replace(/\n/g, '\r\n'));
|
|
25
|
+
function sameContent(content, digest) {
|
|
26
|
+
return typeof digest === 'string' && [content, toLF(content), toCRLF(content)].some(variant => hash(variant) === digest);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Skills root inside the project for each provider. Codex, Antigravity (agy) and Gemini CLI all read
|
|
30
|
+
// <project>/.agents/skills, so they share one installation and one manifest.
|
|
31
|
+
export const providerRoots = Object.freeze({
|
|
32
|
+
claude: path.join('.claude', 'skills'),
|
|
33
|
+
codex: path.join('.agents', 'skills'),
|
|
34
|
+
agy: path.join('.agents', 'skills'),
|
|
35
|
+
gemini: path.join('.agents', 'skills')
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// `external` holds skills already downloaded into memory ({ name, files: Map<relative, Buffer> }).
|
|
39
|
+
// They share the root's manifest and its update/conflict rules; in addition, an existing folder of
|
|
40
|
+
// the same name that the manifest does not manage is a conflict instead of being adopted.
|
|
41
|
+
export function planInstall({ target, provider, source = repository, withoutBenchmark = false, exclude = [], external = [] }) {
|
|
42
|
+
if (!Object.hasOwn(providerRoots, provider)) throw new Error(`provider must be one of: ${Object.keys(providerRoots).join(', ')}`);
|
|
43
|
+
const excluded = [...new Set([...(withoutBenchmark ? ['denisco-workflow-benchmark'] : []), ...exclude])];
|
|
44
|
+
const packaged = discoverSkills(source).filter(skill => !excluded.includes(skill.name));
|
|
45
|
+
for (const skill of external) {
|
|
46
|
+
if (packaged.some(p => p.name === skill.name)) throw new Error(`Downloaded skill ${skill.name} clashes with a packaged skill`);
|
|
47
|
+
}
|
|
48
|
+
const skills = [...packaged, ...external.map(({ name, files }) => ({ name, files: [...files.keys()], contents: files }))];
|
|
49
|
+
if (!fs.statSync(target).isDirectory()) throw new Error('Target project must exist');
|
|
50
|
+
const root = path.join(path.resolve(target), providerRoots[provider]);
|
|
51
|
+
rejectSymlinkPath(root);
|
|
52
|
+
const manifestPath = path.join(root, '.workflow-install.json');
|
|
53
|
+
rejectSymlinkPath(manifestPath);
|
|
54
|
+
const previous = fs.existsSync(manifestPath) ? readJSON(manifestPath) : { schemaVersion: 1, files: {} };
|
|
55
|
+
if (previous.schemaVersion !== 1 || !previous.files || typeof previous.files !== 'object') throw new Error('Invalid installation manifest');
|
|
56
|
+
const entries = [], unmanaged = [];
|
|
57
|
+
for (const { name, directory: sourceDir, files, contents } of skills) {
|
|
58
|
+
if (contents) {
|
|
59
|
+
const folder = inside(root, name);
|
|
60
|
+
rejectSymlinkPath(folder);
|
|
61
|
+
if (fs.existsSync(folder) && !Object.keys(previous.files).some(key => key.startsWith(`${name}/`))) unmanaged.push(name);
|
|
62
|
+
}
|
|
63
|
+
for (const relative of files) {
|
|
64
|
+
const key = `${name}/${relative}`, destination = inside(root, key);
|
|
65
|
+
rejectSymlinkPath(destination);
|
|
66
|
+
const content = contents ? contents.get(relative) : fs.readFileSync(path.join(sourceDir, relative)), wanted = hash(content);
|
|
67
|
+
const existing = fs.existsSync(destination) ? fs.readFileSync(destination) : null;
|
|
68
|
+
const current = existing ? hash(existing) : null;
|
|
69
|
+
const action = existing === null ? 'create' : sameContent(existing, wanted) ? 'unchanged'
|
|
70
|
+
: sameContent(existing, previous.files[key]) ? 'update' : 'conflict';
|
|
71
|
+
entries.push({ key, destination, wanted, current, action, content });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const installed = new Set(skills.map(skill => skill.name));
|
|
75
|
+
const removals = [], stale = [];
|
|
76
|
+
for (const key of Object.keys(previous.files)) {
|
|
77
|
+
if (entries.some(e => e.key === key)) continue;
|
|
78
|
+
const oldName = key.split('/')[0];
|
|
79
|
+
if (!installed.has(renamedSkills[oldName])) { stale.push(key); continue; }
|
|
80
|
+
const destination = inside(root, key);
|
|
81
|
+
rejectSymlinkPath(destination);
|
|
82
|
+
const existing = fs.existsSync(destination) ? fs.readFileSync(destination) : null;
|
|
83
|
+
removals.push({ key, destination, action: existing === null ? 'gone' : sameContent(existing, previous.files[key]) ? 'remove' : 'conflict' });
|
|
84
|
+
}
|
|
85
|
+
return { root, manifestPath, entries, removals, stale, excluded, unmanaged };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// A writer holding the workflow claim in the target or in any of its linked worktrees may still
|
|
89
|
+
// load the old package paths, because linked worktrees use the skills installed at the target.
|
|
90
|
+
function worktrees(target) {
|
|
91
|
+
const list = [path.resolve(target)], linked = path.join(target, '.git', 'worktrees');
|
|
92
|
+
if (fs.existsSync(linked) && fs.statSync(linked).isDirectory()) {
|
|
93
|
+
for (const entry of fs.readdirSync(linked)) {
|
|
94
|
+
const gitdir = path.join(linked, entry, 'gitdir');
|
|
95
|
+
if (!fs.existsSync(gitdir)) continue;
|
|
96
|
+
const directory = path.dirname(path.resolve(linked, entry, fs.readFileSync(gitdir, 'utf8').trim()));
|
|
97
|
+
if (fs.existsSync(directory)) list.push(directory);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return list;
|
|
101
|
+
}
|
|
102
|
+
function activeWriter(target) {
|
|
103
|
+
for (const worktree of worktrees(target)) {
|
|
104
|
+
let status;
|
|
105
|
+
try { status = ownership({ action: 'status', worktree }); }
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (/initialized Git worktree/.test(error.message)) continue; // no worktree, no claim
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
if (status.status === 'owned') return { worktree, owner: status.owner };
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Skill names recorded in a root's manifest, with pre-prefix names mapped to their current name.
|
|
116
|
+
export function manifestSkills(root) {
|
|
117
|
+
const manifestPath = path.join(root, '.workflow-install.json');
|
|
118
|
+
rejectSymlinkPath(manifestPath);
|
|
119
|
+
if (!fs.existsSync(manifestPath)) return new Set();
|
|
120
|
+
const manifest = readJSON(manifestPath);
|
|
121
|
+
if (manifest.schemaVersion !== 1 || !manifest.files || typeof manifest.files !== 'object') throw new Error('Invalid installation manifest');
|
|
122
|
+
return new Set(Object.keys(manifest.files).map(key => key.split('/')[0]).map(name => renamedSkills[name] ?? name));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function summarizePlan(plan) {
|
|
126
|
+
return {
|
|
127
|
+
root: plan.root,
|
|
128
|
+
changes: [...plan.entries, ...plan.removals].map(({ key, action }) => ({ key, action })),
|
|
129
|
+
renamed: [...new Set(plan.removals.map(r => r.key.split('/')[0]))].map(from => ({ from, to: renamedSkills[from] })),
|
|
130
|
+
stale: plan.stale, excluded: plan.excluded, applied: false
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Every safety stop happens here, before any write. Callers installing several roots check all of
|
|
135
|
+
// their plans first, so a stop in one root cannot leave another root partially written.
|
|
136
|
+
export function checkPlan(plan, target) {
|
|
137
|
+
if ([...plan.entries, ...plan.removals].some(e => e.action === 'conflict') || plan.stale.length || plan.unmanaged?.length) {
|
|
138
|
+
throw new Error('Installation conflicts or stale managed files: review the dry-run; no files changed');
|
|
139
|
+
}
|
|
140
|
+
const writer = plan.removals.length ? activeWriter(target) : null;
|
|
141
|
+
if (writer) {
|
|
142
|
+
throw new Error(`A workflow writer holds the claim in ${writer.worktree}: rename migration deferred until it releases; no files changed`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function install(options) {
|
|
147
|
+
const plan = planInstall(options);
|
|
148
|
+
const summary = summarizePlan(plan);
|
|
149
|
+
if (!options.apply) return summary;
|
|
150
|
+
checkPlan(plan, options.target);
|
|
151
|
+
applyPlan(plan);
|
|
152
|
+
return { ...summary, applied: true };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function applyPlan(plan) {
|
|
156
|
+
for (const entry of plan.entries) {
|
|
157
|
+
if (entry.action === 'unchanged') continue;
|
|
158
|
+
fs.mkdirSync(path.dirname(entry.destination), { recursive: true });
|
|
159
|
+
fs.writeFileSync(entry.destination, entry.content);
|
|
160
|
+
}
|
|
161
|
+
for (const removal of plan.removals) {
|
|
162
|
+
if (removal.action !== 'remove') continue;
|
|
163
|
+
fs.unlinkSync(removal.destination);
|
|
164
|
+
// Prune directories emptied by the removal, never the skills root or unmanaged content.
|
|
165
|
+
for (let dir = path.dirname(removal.destination); dir !== plan.root && dir.startsWith(plan.root); dir = path.dirname(dir)) {
|
|
166
|
+
if (fs.readdirSync(dir).length) break;
|
|
167
|
+
fs.rmdirSync(dir);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
writeJSON(plan.manifestPath, { schemaVersion: 1, files: Object.fromEntries(plan.entries.map(e => [e.key, e.wanted])) });
|
|
171
|
+
}
|
|
172
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
173
|
+
try {
|
|
174
|
+
const args = process.argv.slice(2), options = {};
|
|
175
|
+
for (let i = 0; i < args.length; i++) {
|
|
176
|
+
if (args[i] === '--apply') options.apply = true;
|
|
177
|
+
else if (args[i] === '--without-benchmark') options.withoutBenchmark = true;
|
|
178
|
+
else if (['--target', '--provider'].includes(args[i])) options[args[i].slice(2)] = args[++i];
|
|
179
|
+
else throw new Error('Usage: node scripts/install.mjs --target <project> --provider codex|claude [--without-benchmark] [--apply]');
|
|
180
|
+
}
|
|
181
|
+
if (!options.target) throw new Error('Explicit --target required');
|
|
182
|
+
console.log(JSON.stringify(install(options), null, 2));
|
|
183
|
+
} catch (e) { console.error(e.message); process.exitCode = 1; }
|
|
184
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { skillFrontmatter } from './catalog.mjs';
|
|
2
|
+
|
|
3
|
+
// Recommended third-party skills, downloaded at install time from the latest commit of `ref`.
|
|
4
|
+
// Adding one needs only a new entry here: `path` is the skill folder inside the repository and its
|
|
5
|
+
// last segment must equal `name`; `requires` lists catalog entries installed with it.
|
|
6
|
+
export const recommendedSkills = Object.freeze([
|
|
7
|
+
{
|
|
8
|
+
name: 'grill-me',
|
|
9
|
+
description: 'relentless interview about a plan or decision',
|
|
10
|
+
repo: 'mattpocock/skills', ref: 'main', path: 'skills/productivity/grill-me',
|
|
11
|
+
requires: ['grilling'], license: 'MIT', author: 'Matt Pocock'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'grilling',
|
|
15
|
+
description: 'the grilling procedure used by grill-me',
|
|
16
|
+
repo: 'mattpocock/skills', ref: 'main', path: 'skills/productivity/grilling',
|
|
17
|
+
requires: [], license: 'MIT', author: 'Matt Pocock'
|
|
18
|
+
}
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const byName = new Map(recommendedSkills.map(skill => [skill.name, skill]));
|
|
22
|
+
export const isRecommended = name => byName.has(name);
|
|
23
|
+
|
|
24
|
+
// The chosen names plus every recommended skill they require, transitively.
|
|
25
|
+
export function withDependencies(names) {
|
|
26
|
+
const result = new Set();
|
|
27
|
+
const visit = name => {
|
|
28
|
+
if (result.has(name)) return;
|
|
29
|
+
result.add(name);
|
|
30
|
+
for (const dependency of byName.get(name)?.requires ?? []) visit(dependency);
|
|
31
|
+
};
|
|
32
|
+
names.forEach(visit);
|
|
33
|
+
return [...result];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Catalog entries that require `name` and are among `chosen`.
|
|
37
|
+
export const requiredBy = (name, chosen) => recommendedSkills.filter(s => chosen.includes(s.name) && s.requires.includes(name)).map(s => s.name);
|
|
38
|
+
|
|
39
|
+
export class DownloadError extends Error {}
|
|
40
|
+
|
|
41
|
+
const API = 'https://api.github.com', RAW = 'https://raw.githubusercontent.com';
|
|
42
|
+
const encodePath = value => value.split('/').map(encodeURIComponent).join('/');
|
|
43
|
+
|
|
44
|
+
async function request(fetchImpl, url, { token, accept } = {}) {
|
|
45
|
+
const headers = { 'User-Agent': 'deniscodp-skills-installer' };
|
|
46
|
+
if (accept) headers.Accept = accept;
|
|
47
|
+
// The token only goes to the GitHub API and never appears in messages.
|
|
48
|
+
if (token && url.startsWith(API)) headers.Authorization = `Bearer ${token}`;
|
|
49
|
+
let response;
|
|
50
|
+
try {
|
|
51
|
+
response = await fetchImpl(url, { headers, redirect: 'follow', signal: AbortSignal.timeout(30000) });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new DownloadError(`network error for ${url}: ${error.message}`);
|
|
54
|
+
}
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const limited = [403, 429].includes(response.status) && response.headers?.get?.('x-ratelimit-remaining') === '0';
|
|
57
|
+
throw new DownloadError(`HTTP ${response.status} for ${url}${limited ? ' (GitHub rate limit reached; wait or set GITHUB_TOKEN)' : ''}`);
|
|
58
|
+
}
|
|
59
|
+
return response;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Downloads every file of the named catalog skills into memory before any plan is computed, so a
|
|
63
|
+
// failure leaves the target untouched. Two GitHub API calls per repository (commit, recursive tree),
|
|
64
|
+
// then one raw download per file at that exact commit.
|
|
65
|
+
export async function downloadRecommended(names, { fetch: fetchImpl = globalThis.fetch, token = process.env.GITHUB_TOKEN } = {}) {
|
|
66
|
+
const wanted = names.map(name => {
|
|
67
|
+
const skill = byName.get(name);
|
|
68
|
+
if (!skill) throw new DownloadError(`not a recommended skill: ${name}`);
|
|
69
|
+
return skill;
|
|
70
|
+
});
|
|
71
|
+
const commits = new Map(), trees = new Map(), downloaded = [];
|
|
72
|
+
for (const skill of wanted) {
|
|
73
|
+
const origin = `${skill.repo}@${skill.ref}`;
|
|
74
|
+
if (!commits.has(origin)) {
|
|
75
|
+
const response = await request(fetchImpl, `${API}/repos/${skill.repo}/commits/${encodeURIComponent(skill.ref)}`, { token, accept: 'application/vnd.github.sha' });
|
|
76
|
+
const commit = (await response.text()).trim();
|
|
77
|
+
if (!/^[0-9a-f]{40}$/.test(commit)) throw new DownloadError(`unexpected commit id for ${origin}`);
|
|
78
|
+
const tree = await (await request(fetchImpl, `${API}/repos/${skill.repo}/git/trees/${commit}?recursive=1`, { token, accept: 'application/vnd.github+json' })).json();
|
|
79
|
+
if (!Array.isArray(tree?.tree)) throw new DownloadError(`unexpected tree listing for ${origin}`);
|
|
80
|
+
if (tree.truncated) throw new DownloadError(`tree listing of ${origin} is truncated`);
|
|
81
|
+
commits.set(origin, commit);
|
|
82
|
+
trees.set(origin, tree.tree);
|
|
83
|
+
}
|
|
84
|
+
const commit = commits.get(origin), prefix = `${skill.path}/`;
|
|
85
|
+
const items = trees.get(origin).filter(item => item.path.startsWith(prefix) && item.type !== 'tree');
|
|
86
|
+
if (!items.length) throw new DownloadError(`${skill.path} not found in ${origin} (commit ${commit})`);
|
|
87
|
+
const files = new Map();
|
|
88
|
+
for (const item of items.sort((a, b) => a.path < b.path ? -1 : 1)) {
|
|
89
|
+
const relative = item.path.slice(prefix.length);
|
|
90
|
+
if (item.mode === '120000') throw new DownloadError(`symlink refused: ${item.path} in ${origin}`);
|
|
91
|
+
if (item.type !== 'blob') throw new DownloadError(`unsupported entry ${item.type}: ${item.path} in ${origin}`);
|
|
92
|
+
if (relative.split('/').some(segment => !segment || segment === '.' || segment === '..')) throw new DownloadError(`unsafe path: ${item.path}`);
|
|
93
|
+
const response = await request(fetchImpl, `${RAW}/${skill.repo}/${commit}/${encodePath(item.path)}`);
|
|
94
|
+
files.set(relative, Buffer.from(await response.arrayBuffer()));
|
|
95
|
+
}
|
|
96
|
+
if (!files.has('SKILL.md')) throw new DownloadError(`${skill.path}/SKILL.md missing in ${origin} (commit ${commit})`);
|
|
97
|
+
try { skillFrontmatter(files.get('SKILL.md').toString('utf8'), skill.name, `${origin}:${skill.path}/SKILL.md`); }
|
|
98
|
+
catch (error) { throw new DownloadError(error.message); }
|
|
99
|
+
downloaded.push({ name: skill.name, files, source: { repo: skill.repo, ref: skill.ref, commit, license: skill.license, author: skill.author } });
|
|
100
|
+
}
|
|
101
|
+
return downloaded;
|
|
102
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: denisco-handoff-implementation
|
|
3
|
+
description: "Plan and execute a decided handoff block by block until a real stop, verify acceptance and keep task state current. Use for planning a decided bug fix or feature and implementing or resuming its blocks; not for open-ended product design or repository onboarding."
|
|
4
|
+
metadata:
|
|
5
|
+
version: "8.3.0"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Handoff implementation
|
|
9
|
+
|
|
10
|
+
Input: handoff path, optional point/block assignment and existing plan/state paths.
|
|
11
|
+
Legacy `punto 3` inputs remain valid. Preserve approved behavior and report discrepancies.
|
|
12
|
+
For an explicit bounded assignment or a programmer dispatched by denisco-implementation-coordinator,
|
|
13
|
+
read [bounded implementation](references/delegated-implementation.md). Its explicit block list and
|
|
14
|
+
return boundary constrain the normal continue-until-stop loop; a starting block alone does not.
|
|
15
|
+
The programmer owns technical document updates through this skill's existing procedures.
|
|
16
|
+
COORDINATOR.md and COORDINATOR-HISTORY.md are read-only inputs owned by the coordinator;
|
|
17
|
+
their grouping proposals never replace HANDOFF, PLAN, STATE or the operational block map.
|
|
18
|
+
Even with only a handoff path, look for `<handoff-path>.workflow.json` and read
|
|
19
|
+
[the operational context contract](references/session-plan.md) before entering or resuming.
|
|
20
|
+
Before any task writes or measurement registration, follow [worktree ownership](references/ownership.md):
|
|
21
|
+
inspect peers and acquire the single-writer claim with the installed helper. A second top-level
|
|
22
|
+
instance stays read-only even when it shares this conversation ID. A delegated visual child has
|
|
23
|
+
only its assigned artifact scope. This gate also applies when measurement is disabled; a claim
|
|
24
|
+
coordinates execution and never supplies missing user authorization.
|
|
25
|
+
Recover measurement only from this context or explicitly referenced task documents, never from
|
|
26
|
+
unrelated registers. Measurement is disabled by default and `disabled` is authoritative across
|
|
27
|
+
sessions. Do not load, install or use denisco-workflow-benchmark, search for a run, or change that state
|
|
28
|
+
unless the current user request or applicable project instructions explicitly enable measurement
|
|
29
|
+
for this task. `Unknown` is inert legacy/interrupted state, not permission to enable; preserve or
|
|
30
|
+
resolve it to disabled unless explicit activation evidence exists. An unavailable measurement
|
|
31
|
+
helper never blocks independent planning or implementation.
|
|
32
|
+
|
|
33
|
+
In writable mode with measurement enabled and the helper available, at first entry (including
|
|
34
|
+
planning) load denisco-workflow-benchmark and run `track --handoff <file>
|
|
35
|
+
--provider <actual-provider> --phase <planning|implementation>`, then execute its returned
|
|
36
|
+
nextCall yourself in a separate tool call. This works before PLAN/STATE and the block map exist.
|
|
37
|
+
Keep technical identifiers internal; do not ask the user to assemble registration commands.
|
|
38
|
+
If registration remains pending after one later retry, record the gap and continue independent work.
|
|
39
|
+
Keep the returned ticket in this conversation. At closure pass that same ticket to
|
|
40
|
+
`close --ticket <ticket>` when available; this reuses the resolved identity and prevents an
|
|
41
|
+
`identity-pending` checkpoint. Do not copy it into the next session's prompt.
|
|
42
|
+
|
|
43
|
+
## Enter at the right stage
|
|
44
|
+
|
|
45
|
+
The user's request selects the phase, or the user's yes to a phase question the agent asked; file
|
|
46
|
+
existence and tool mode do not. Read project instructions, handoff, operational context and
|
|
47
|
+
existing PLAN/STATE first. Read assigned points and dependencies in full; preserve settled product
|
|
48
|
+
decisions.
|
|
49
|
+
|
|
50
|
+
Require scoped evidence before classifying a request as implementation. Bare native transition
|
|
51
|
+
text such as `Implement the plan.`, `Start coding` or `Proceed` names neither a saved execution
|
|
52
|
+
prompt nor a handoff/block assignment, is not the user's answer to a phase question, and is not
|
|
53
|
+
implementation authorization. If it follows a specification or planning proposal whose artifacts
|
|
54
|
+
are still pending, finish that existing phase and ask its phase question; do not reinterpret the
|
|
55
|
+
verb "implement" as a new assignment.
|
|
56
|
+
|
|
57
|
+
- **Planning only:** the prompt from HANDOFF, the user's yes to the specification's planning
|
|
58
|
+
question, or a handoff path without an execution assignment creates/reconciles PLAN, STATE and
|
|
59
|
+
the complete planned block map. First check the handoff's blocker table and permission list.
|
|
60
|
+
A handoff without them, or with an unresolved item, gets the blocker hunt from
|
|
61
|
+
[the blocker checklist](references/blocker-checklist.md) now, before blocks are chosen. Select
|
|
62
|
+
the first dependency-ready block and save/check its execution prompt in STATE. Then choose the
|
|
63
|
+
execution path. **Coordinated** is the default when the plan has two or more implementation
|
|
64
|
+
blocks (final integration excluded), denisco-implementation-coordinator is installed and the runtime can
|
|
65
|
+
create fresh-context workers, unless the user asked for direct execution: release the planning
|
|
66
|
+
claim, read the installed
|
|
67
|
+
denisco-implementation-coordinator `SKILL.md` by its path and continue with its preparation in this
|
|
68
|
+
conversation. Preparation writes only coordination documents, launches nothing and ends with its
|
|
69
|
+
own start question instead of the one below. Otherwise the path is **direct**: ask "Plan ready.
|
|
70
|
+
Start <block>?", naming the reason when the coordinator or fresh workers are unavailable. The user's yes starts
|
|
71
|
+
implementation in this conversation; any other answer ends planning without application-code
|
|
72
|
+
edits, also in automatic mode. With the question, mention that the saved prompt can instead
|
|
73
|
+
start it in a fresh conversation with less context.
|
|
74
|
+
- **Implementation:** the user's yes to the start question, a user-submitted execution prompt or an
|
|
75
|
+
explicit scoped execution request authorizes implementing the plan from the named block. Record
|
|
76
|
+
that instruction, date and scope in STATE and proceed without another plan-approval question.
|
|
77
|
+
Generating or reading a prompt in a file is not a user request. Respect newer restrictions.
|
|
78
|
+
An authorized coordinator may dispatch a bounded assignment under the recorded user request;
|
|
79
|
+
preserve both that authorization and the narrower assignment in STATE.
|
|
80
|
+
- **Resume:** establish ownership, inspect the worktree, STATE, results and attributable logs.
|
|
81
|
+
Distinguish absent, unregistered, stale and failed evidence before repeating checks. Documents
|
|
82
|
+
left missing by older sessions are repaired without replanning; a STATE in an older format is
|
|
83
|
+
migrated as the session plan describes.
|
|
84
|
+
|
|
85
|
+
When a new human session starts on a task that already has STATE, go through **Your actions** first: ask
|
|
86
|
+
which items are done and, in the same message, whether the user changed anything else since the
|
|
87
|
+
last session (deploys, portals, configuration). Record the answers in STATE before continuing.
|
|
88
|
+
An internal programmer rotation uses the coordinator's recorded answers and routes new questions
|
|
89
|
+
through it, as the bounded implementation contract specifies.
|
|
90
|
+
|
|
91
|
+
Use [session planning](references/session-plan.md) for the split and document contracts. Small
|
|
92
|
+
tasks can have one block. A prepared block map does not claim that implementation was authorized.
|
|
93
|
+
|
|
94
|
+
In Plan Mode propose only the current phase's deliverables and name pending writes. Once writing
|
|
95
|
+
is permitted, materialize them and ask the phase question. Native plan approval or switching to
|
|
96
|
+
automatic mode never advances from specification to planning or from planning to implementation.
|
|
97
|
+
A private CLI plan does not replace the documents. Before asking the start question, verify actual
|
|
98
|
+
PLAN, STATE, the complete block/criterion map and the saved/checked first execution prompt. Record
|
|
99
|
+
the plan as prepared, preserving any historical approvals rather than inventing new ones.
|
|
100
|
+
|
|
101
|
+
## Build the block
|
|
102
|
+
|
|
103
|
+
Recheck anchors by path and symbol before editing. Record meaningful drift or invalidated assumptions.
|
|
104
|
+
Reuse components, rules and endpoints. Respect rejected alternatives, exclusions and shared invariants.
|
|
105
|
+
Make routine technical decisions within agreed behavior.
|
|
106
|
+
Iterate with the fastest feedback loop the operating guide documents (incremental watch/serve, an
|
|
107
|
+
open browser session) and keep it running between edits. Reserve full builds and full probe runs
|
|
108
|
+
for the evidence the criteria require, once the code is stable. Send the full output of test suites
|
|
109
|
+
and builds to a log file and read only the summary and the failures; open the log around a failure
|
|
110
|
+
when its context is needed. A full log read into the conversation stays in context and is paid
|
|
111
|
+
again at every later step.
|
|
112
|
+
|
|
113
|
+
When something needs the user, ask at once in chat, in the format of the blocker checklist's Asking
|
|
114
|
+
section, and keep working on the parts it does not block. Once answered, record the answer and
|
|
115
|
+
continue in this session. If nothing else can proceed and the turn ends waiting, first checkpoint
|
|
116
|
+
STATE with progress, unrecorded evidence, running services and the open question, so a cleared or
|
|
117
|
+
interrupted conversation can resume from disk. Every change in scope stays visible in PLAN and STATE:
|
|
118
|
+
|
|
119
|
+
- **Blocker found late:** ask as above; record the answer where it applies and in STATE.
|
|
120
|
+
- **Split:** dividing a point needs no question when it adds no user action and no permission;
|
|
121
|
+
record it in PLAN. A part that needs either is a new point.
|
|
122
|
+
- **New point for the same goal:** run the blocker hunt for that point only, in chat. Edit the
|
|
123
|
+
handoff point in place, moving the replaced text to STATE-HISTORY.md, update PLAN and the block
|
|
124
|
+
map, then continue.
|
|
125
|
+
- **New goal:** a request that adds a goal instead of finishing a point belongs to a new task.
|
|
126
|
+
Propose closing or pausing this task, then use denisco-interactive-spec-definition for the new one.
|
|
127
|
+
- **Permissions:** act within the handoff's permission list; ask before any other action and before
|
|
128
|
+
every always-ask action in the blocker checklist.
|
|
129
|
+
- **Recurring tool permission:** the first time a command needs a tool permission or sandbox
|
|
130
|
+
escalation that the project does not grant yet, propose a permanent project entry: command
|
|
131
|
+
pattern, settings file and reason. After the user's yes, add it and record it in the operating
|
|
132
|
+
guide. Permission settings change only with that yes.
|
|
133
|
+
|
|
134
|
+
An excluded point elsewhere stops this block only if it is a dependency.
|
|
135
|
+
|
|
136
|
+
## Verify by requirement
|
|
137
|
+
|
|
138
|
+
Run the mandatory project cycle and assigned acceptance criteria. Use the operating guide for
|
|
139
|
+
commands, environment, login, roles and data. Exercise the motivating edge case and related consumers.
|
|
140
|
+
|
|
141
|
+
- Technical behavior: focused tests, type/build checks or API checks.
|
|
142
|
+
- UI interaction: walk the flow and inspect effects and console after interaction. Explore
|
|
143
|
+
interactively when a browser tool is available and script only the final evidence. Before
|
|
144
|
+
rerunning a failed browser check, read its captured console and network errors; once a backend
|
|
145
|
+
failure is confirmed, use an authorized fallback or report the outage instead of retrying.
|
|
146
|
+
- Numeric visual requirements and parity with an existing element: measure resolved values and
|
|
147
|
+
rendered geometry in the browser.
|
|
148
|
+
- Perceptual requirements: use `denisco-visual-check` for the stated comparisons when available.
|
|
149
|
+
|
|
150
|
+
When criteria use denisco-visual-check, the implementation owner loads the skill and performs measurements/captures.
|
|
151
|
+
For a coordinated programmer, apply the bounded contract's visual roles and native measurement
|
|
152
|
+
attribution; the following default describes direct execution and separately delegated visual checks.
|
|
153
|
+
New sidecar entries use executor "principal". Delegate the whole check only when the user explicitly
|
|
154
|
+
requests it, recording executor "subagent". Keep existing historical attempts; an old delegated map
|
|
155
|
+
is not fresh user authorization to launch a child. Reconcile the future assignment with the current
|
|
156
|
+
request and preserve previous results. The only automatic visual delegation is a fresh blind judge
|
|
157
|
+
for perceptual criteria when available and permitted by the environment.
|
|
158
|
+
With measurement enabled, register visual-start with the explicit executor before the attempt,
|
|
159
|
+
refresh/register that executor's native log, then visual-finish with results and evidence. Register
|
|
160
|
+
the blind judge separately for usage; it does not replace the denisco-visual-check executor or load its skill.
|
|
161
|
+
For an explicitly delegated check, give the child an exclusive attempt directory, prohibit source,
|
|
162
|
+
PLAN/STATE, sidecar, register and shared-tooling writes, and merge returned index rows serially.
|
|
163
|
+
Finish pre-edit capture before source edits and keep source/tooling stable during post-edit capture.
|
|
164
|
+
Apply fixes after capture, then recheck affected criteria. Follow denisco-visual-check's resource cleanup
|
|
165
|
+
contract on success, failure and cancellation; return/close children before releasing ownership.
|
|
166
|
+
|
|
167
|
+
A UI file change alone does not require a blind judge. Mockups never verify implementation.
|
|
168
|
+
A criterion whose verifier is the user passes on the user's report, recorded as such; a waived
|
|
169
|
+
criterion stays listed with its reason. If a required check cannot run, complete the independent
|
|
170
|
+
checks and ask once: waive it with a reason, rewrite the criterion, or let the user verify it.
|
|
171
|
+
A perceptual criterion still failing after denisco-visual-check's single correction round gets the same
|
|
172
|
+
question, with the images and measurements.
|
|
173
|
+
Until answered the point is **implemented-unverified**. Rerun a failed check in a later session
|
|
174
|
+
only when a relevant fact has changed. Do not remove the criterion or claim weaker evidence proves it.
|
|
175
|
+
Failed measurements prevent visual sign-off, not reporting or independent diagnostics.
|
|
176
|
+
|
|
177
|
+
## Run until a real stop
|
|
178
|
+
|
|
179
|
+
When a block is finished (verified, or implemented-unverified with its gaps named), update STATE and
|
|
180
|
+
continue with the next dependency-ready block in this conversation within the explicit assignment.
|
|
181
|
+
Stop when:
|
|
182
|
+
|
|
183
|
+
1. the task's **Done when** is met;
|
|
184
|
+
2. the remaining work waits on something outside this conversation: a your-action item not yet
|
|
185
|
+
done, a third-party review, propagation, or a check that needs time to pass;
|
|
186
|
+
3. the conversation has grown too long to keep track of reliably;
|
|
187
|
+
4. the user says stop.
|
|
188
|
+
5. the explicitly bounded assignment ends, or its programmer checkpoints early for context pressure,
|
|
189
|
+
a blocker or an explicit limit and returns control under the bounded implementation contract.
|
|
190
|
+
|
|
191
|
+
"From B1" remains a starting point, not a one-block boundary. "Only B1-B2, then return" is bounded.
|
|
192
|
+
Do not start B3 after that boundary, even if it is ready. A worker does not enlarge its own assignment.
|
|
193
|
+
|
|
194
|
+
A question the user answers in this conversation is not a stop. Before stopping, persist and hand over.
|
|
195
|
+
|
|
196
|
+
## Persist and hand over
|
|
197
|
+
|
|
198
|
+
Follow [the plan and state contract](references/session-plan.md): STATE is the current picture,
|
|
199
|
+
STATE-HISTORY.md receives each session's entry, PLAN is the block map. Record implementation status,
|
|
200
|
+
criterion results/evidence, blockers, structural changes and meaningful drift per point.
|
|
201
|
+
Preserve failures and reopenings. A verification gap is not a completed point.
|
|
202
|
+
Never label a point/block `verified except ...` or `complete and verified` while a required
|
|
203
|
+
criterion is unavailable. With finished code use `implemented-unverified` and name the gaps.
|
|
204
|
+
Report dependency readiness separately: explain why an unresolved check does or does not prevent
|
|
205
|
+
the next block; neither silently declare it ready nor block unrelated work.
|
|
206
|
+
|
|
207
|
+
End every session's final message with **Your actions**, the same list as in STATE, or "Nothing
|
|
208
|
+
needed from you."
|
|
209
|
+
|
|
210
|
+
When the session stops with task work remaining, save the short next prompt in STATE.md for the
|
|
211
|
+
next dependency-ready block or the unfinished one; a later user request or an already authorized
|
|
212
|
+
coordinator launches the next assignment. The current programmer never launches its successor. A
|
|
213
|
+
user stop with work remaining is a pause: save the prompt as for any other stop and record the stop
|
|
214
|
+
in STATE; the stop holds because only a later user request sends the prompt. Omit or clear it only
|
|
215
|
+
when the user ends the task or asks for no prompt, recording the reason. Keep environment facts in the
|
|
216
|
+
operating guide and evidence paths in STATE. Prompts relay decisions, never invent them.
|
|
217
|
+
Include native session registration when tracking is active.
|
|
218
|
+
When enabled, use denisco-workflow-benchmark to record actual evidence and an identifiable native session.
|
|
219
|
+
After agent-managed registration and once the block map exists, use `open --handoff <file>
|
|
220
|
+
--block <id>` to refresh registered usage and inspect the assignment. Its legacy identity hints
|
|
221
|
+
are internal; `track` handles identification and registration together.
|
|
222
|
+
Planning and implementation in one session count once. After each block's final validation and
|
|
223
|
+
evidence registration, update STATE and run the read-only `checkpoint` for that block. When the
|
|
224
|
+
session stops, use `next-prompt --save` for the proposed next assignment and run `checkpoint` for
|
|
225
|
+
the last block worked with `--next-block` naming that continuation; reconcile both technical status
|
|
226
|
+
and prompt delivery with STATE and the final response. A resume may assign the same unfinished
|
|
227
|
+
block. Missing identity stays pending, never guessed or counted as zero.
|
|
228
|
+
With measurement disabled/unknown, save and reread the prompt manually without loading or installing
|
|
229
|
+
denisco-workflow-benchmark. Automatic benchmark prompt checking is intentionally unavailable in that mode.
|
|
230
|
+
In Plan Mode propose the prompt and leave persistence pending; never claim it was saved.
|
|
231
|
+
Link the saved STATE section in the response; any prompt repeated in chat must be the saved text.
|
|
232
|
+
Further code/evidence changes require refreshed results and another checkpoint before closing.
|
|
233
|
+
`close --handoff <file> --block <id> --implementation <state> (--next-block <id>|--final)` performs
|
|
234
|
+
the save and the checkpoint in one step and refreshes usage; the separate commands stay equivalent.
|
|
235
|
+
Prefer `--ticket <ticket>` from the same conversation over repeating a native session key.
|
|
236
|
+
Live usage remains provisional. The agent refreshes it with `collect` and generates `report` at
|
|
237
|
+
closure; the next entry refreshes again. The last conversation's trailing usage remains pending
|
|
238
|
+
until an observer can refresh its closed log. Never require user-managed IDs or attest closure
|
|
239
|
+
just because another conversation has started.
|
|
240
|
+
|
|
241
|
+
When all blocks are implemented, check cross-block integration and complete acceptance.
|
|
242
|
+
In coordinated mode the coordinator assigns this to a fresh integration worker; an implementation
|
|
243
|
+
worker records that pending step and returns without exceeding its boundary or claiming task completion.
|
|
244
|
+
When no task work remains, or the user ends the task or asks for no prompt, use `next-prompt --clear` to remove the obsolete generated
|
|
245
|
+
section (or remove the old prompt manually without a helper), and checkpoint without `--next-block`.
|
|
246
|
+
Preserve pending verification, accounting and acceptance in STATE; clearing is not proof of completion.
|
|
247
|
+
Reconcile PLAN/STATE current status with the latest authorization, worktree and checkpoint;
|
|
248
|
+
move obsolete planning-only claims to STATE-HISTORY.md. Report passed, failed and
|
|
249
|
+
unavailable checks, pre-existing issues and evidence locations.
|
|
250
|
+
Technical verification permits `verified`; user acceptance/reopening is recorded separately,
|
|
251
|
+
never inferred from the agent's closing message.
|
|
252
|
+
After children have returned and all validation, state and reporting writes are finished, release
|
|
253
|
+
the execution claim. A conflict never resolves merely because the other worktree files went quiet;
|
|
254
|
+
use the explicit transfer/recovery procedure in the ownership contract.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Blocker checklist
|
|
2
|
+
|
|
3
|
+
A **blocker** is anything the agent cannot settle alone that would stop or reshape the work: a fact
|
|
4
|
+
only the user knows, an action only the user or a third party can take, a permission, or a check
|
|
5
|
+
the agent cannot run. The **blocker hunt** finds them before the work they affect starts.
|
|
6
|
+
|
|
7
|
+
## Probe first
|
|
8
|
+
|
|
9
|
+
Settle by looking whatever looking can settle, then ask about the rest:
|
|
10
|
+
|
|
11
|
+
- Read the official requirements of every external platform the task touches (submission, review,
|
|
12
|
+
publication, authentication, hosting limits). Record the links and each requirement found.
|
|
13
|
+
- Run the project's verification cycle once and record the failures that already exist.
|
|
14
|
+
- Validate a draft configuration or manifest when a validator exists.
|
|
15
|
+
- Check what you can operate here: portals and dashboards, an authenticated browser, GUI clients,
|
|
16
|
+
media files, push and deploy. Every step you cannot operate is a step for the user.
|
|
17
|
+
|
|
18
|
+
## Categories
|
|
19
|
+
|
|
20
|
+
Name the concrete value for each category, or ask for it. Skip a category only when the task
|
|
21
|
+
cannot touch it.
|
|
22
|
+
|
|
23
|
+
| Category | Settle |
|
|
24
|
+
|---|---|
|
|
25
|
+
| Accounts and credentials | which account, who holds it, where the secret lives (never its value) |
|
|
26
|
+
| Console and portal access | which console, organization and project; who can sign in |
|
|
27
|
+
| Who operates each step | for every portal, GUI or physical step: the agent or the user |
|
|
28
|
+
| Permissions | the permission list below |
|
|
29
|
+
| Environments | staging or production, which domain, which data |
|
|
30
|
+
| Size facts | facts that change how much to build: how many users, existing data, whether data may be lost |
|
|
31
|
+
| Product decisions | choices only the user can make |
|
|
32
|
+
| Third-party waits | reviews, approvals, propagation; expected duration; what the plan does meanwhile |
|
|
33
|
+
| Paid services | which plan, who pays, limits that stop the work |
|
|
34
|
+
| Legal and content | texts, policies and media the user must supply or approve |
|
|
35
|
+
| Checks the agent cannot run | who verifies each one, below |
|
|
36
|
+
|
|
37
|
+
## Close every item
|
|
38
|
+
|
|
39
|
+
Each item ends in exactly one state:
|
|
40
|
+
|
|
41
|
+
- **decided:** the answer is recorded where it applies;
|
|
42
|
+
- **your action:** the user agreed to do it; the item names the point it unblocks and how the agent
|
|
43
|
+
will check it, and it appears in **Your actions**;
|
|
44
|
+
- **not in this task:** moved out of the task with the user's yes.
|
|
45
|
+
|
|
46
|
+
An item never asked, or recorded as unknown, is still open, and the work that depends on it is not
|
|
47
|
+
ready.
|
|
48
|
+
|
|
49
|
+
## Permission list
|
|
50
|
+
|
|
51
|
+
Ask once for the actions the agent may take during this task without asking again. Each entry
|
|
52
|
+
names the action, its target and its limit, for example: push to `auth/*` branches; deploy
|
|
53
|
+
`mcp-service`; write to the staging database.
|
|
54
|
+
|
|
55
|
+
These actions always need a fresh yes, even when listed: submitting or publishing to a store or
|
|
56
|
+
portal; DNS and certificates; deleting or migrating production data; spending money; messages to
|
|
57
|
+
real people. Ask before any action outside the list.
|
|
58
|
+
|
|
59
|
+
## Who verifies
|
|
60
|
+
|
|
61
|
+
Every acceptance criterion names its verifier:
|
|
62
|
+
|
|
63
|
+
- **agent:** the agent runs the check;
|
|
64
|
+
- **user:** the user performs it and reports; the report is the evidence;
|
|
65
|
+
- **waived:** nobody runs it; record the reason and the user's yes.
|
|
66
|
+
|
|
67
|
+
## Asking
|
|
68
|
+
|
|
69
|
+
Ask in rounds. Number each question and give your recommended answer, so that "yes" accepts it.
|
|
70
|
+
Put every question that is ready into the same round; a question that depends on another answer
|
|
71
|
+
waits for the next round.
|