@iamk77/skill-checklist 0.2.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/dist/index.js ADDED
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const init_js_1 = require("./commands/init.js");
5
+ const show_js_1 = require("./commands/show.js");
6
+ const verify_js_1 = require("./commands/verify.js");
7
+ const check_js_1 = require("./commands/check.js");
8
+ const phases_js_1 = require("./commands/phases.js");
9
+ const reset_js_1 = require("./commands/reset.js");
10
+ const program = new commander_1.Command()
11
+ .name('checklist')
12
+ .description('Flight checklist CLI for Claude Code skills')
13
+ .version('0.2.0');
14
+ // Flags are uniform across every command so an agent never has to remember
15
+ // which command accepts what. In the normal skill flow none are needed:
16
+ // `--dir` defaults to $CLAUDE_SKILL_DIR / the active checklist, and `--path`
17
+ // defaults to that same dir.
18
+ const DIR_OPT = ['-d, --dir <dir>', 'Directory containing .checklist.yml'];
19
+ const PATH_OPT = ['-p, --path <path>', 'Target skill directory for builtins (defaults to --dir)'];
20
+ program
21
+ .command('init [dir]')
22
+ .description('Load .checklist.yml, clear state, show ready summary')
23
+ .option(...DIR_OPT)
24
+ .option(...PATH_OPT)
25
+ .option('--force', 'Clear existing state without prompting')
26
+ .action(init_js_1.initCommand);
27
+ program
28
+ .command('show [phase]')
29
+ .description('Show checklist overview, or a specific phase with readings')
30
+ .option(...DIR_OPT)
31
+ .option(...PATH_OPT)
32
+ .action(show_js_1.showCommand);
33
+ program
34
+ .command('verify <phase>')
35
+ .description('Batch verify mechanical checks for a phase')
36
+ .option(...DIR_OPT)
37
+ .option(...PATH_OPT)
38
+ .action(verify_js_1.verifyCommand);
39
+ program
40
+ .command('check <phase> <item-id>')
41
+ .description('Manually confirm a human-judgment check item')
42
+ .option(...DIR_OPT)
43
+ .option(...PATH_OPT)
44
+ .action(check_js_1.checkCommand);
45
+ program
46
+ .command('phases')
47
+ .description('List all phases')
48
+ .option(...DIR_OPT)
49
+ .option(...PATH_OPT)
50
+ .action(phases_js_1.phasesCommand);
51
+ program
52
+ .command('reset')
53
+ .alias('done')
54
+ .description('End-of-run cleanup: clear this skill\'s state and active pointer')
55
+ .option(...DIR_OPT)
56
+ .option(...PATH_OPT)
57
+ .action(reset_js_1.resetCommand);
58
+ program.parse();
package/dist/loader.js ADDED
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadChecklist = loadChecklist;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const yaml = __importStar(require("js-yaml"));
40
+ const CONFIG_FILE = '.checklist.yml';
41
+ function loadChecklist(dir) {
42
+ const filePath = path.resolve(dir, CONFIG_FILE);
43
+ if (!fs.existsSync(filePath)) {
44
+ // Agents naturally point checklist at the project they are working on, but
45
+ // a checklist lives in the *skill* directory (next to SKILL.md). Redirect
46
+ // instead of dead-ending.
47
+ const skillHint = process.env.CLAUDE_SKILL_DIR
48
+ ? `\n this skill's dir is: ${process.env.CLAUDE_SKILL_DIR}\n try: checklist init "${process.env.CLAUDE_SKILL_DIR}"`
49
+ : `\n a checklist lives in the skill directory (next to SKILL.md), not your project/working dir.\n try: checklist init <skill-dir>`;
50
+ throw new Error(`${CONFIG_FILE} not found in ${dir}${skillHint}`);
51
+ }
52
+ const raw = fs.readFileSync(filePath, 'utf-8');
53
+ const data = yaml.load(raw);
54
+ if (!data || typeof data !== 'object') {
55
+ throw new Error(`${CONFIG_FILE} is empty or not a valid YAML object`);
56
+ }
57
+ if (!Array.isArray(data.phases)) {
58
+ throw new Error(`${CONFIG_FILE} missing "phases" array`);
59
+ }
60
+ const phases = data.phases.map((p, i) => {
61
+ const phase = p;
62
+ if (!phase.name || typeof phase.name !== 'string') {
63
+ throw new Error(`Phase ${i}: missing "name" field`);
64
+ }
65
+ if (!Array.isArray(phase.checks)) {
66
+ throw new Error(`Phase "${phase.name}": missing "checks" array`);
67
+ }
68
+ if (phase.checks.length === 0) {
69
+ // A phase with no checks is vacuously gate-complete (`[].every` is true),
70
+ // letting an empty stage pass with zero work. The format is documented as
71
+ // a non-empty checks list, so reject it at the parse boundary.
72
+ throw new Error(`Phase "${phase.name}": "checks" array is empty`);
73
+ }
74
+ const checks = phase.checks.map((c, j) => {
75
+ const check = c;
76
+ if (!check.id || typeof check.id !== 'string') {
77
+ throw new Error(`Phase "${phase.name}", check ${j}: missing "id"`);
78
+ }
79
+ if (!check.description || typeof check.description !== 'string') {
80
+ throw new Error(`Phase "${phase.name}", check "${check.id}": missing "description"`);
81
+ }
82
+ return {
83
+ id: check.id,
84
+ description: check.description,
85
+ verify: typeof check.verify === 'string' ? check.verify : undefined,
86
+ };
87
+ });
88
+ const seenIds = new Set();
89
+ for (const ch of checks) {
90
+ if (seenIds.has(ch.id)) {
91
+ throw new Error(`Phase "${phase.name}": duplicate check id "${ch.id}"`);
92
+ }
93
+ seenIds.add(ch.id);
94
+ }
95
+ return { name: phase.name, checks };
96
+ });
97
+ if (phases.length === 0) {
98
+ throw new Error(`${CONFIG_FILE}: "phases" array is empty`);
99
+ }
100
+ // Phases are addressed BY NAME (findPhaseIndex, case-insensitively). Two phases
101
+ // sharing a name would make every non-first one unreachable through its only
102
+ // documented handle, so reject duplicates the same way duplicate check ids are.
103
+ const seenPhaseNames = new Set();
104
+ for (const ph of phases) {
105
+ const key = ph.name.toLowerCase();
106
+ if (seenPhaseNames.has(key)) {
107
+ throw new Error(`${CONFIG_FILE}: duplicate phase name "${ph.name}"`);
108
+ }
109
+ seenPhaseNames.add(key);
110
+ }
111
+ return { phases };
112
+ }
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveDir = resolveDir;
37
+ exports.writeActivePointer = writeActivePointer;
38
+ exports.clearActivePointer = clearActivePointer;
39
+ exports.findPhaseIndex = findPhaseIndex;
40
+ exports.runPhase = runPhase;
41
+ exports.gatePriorPhases = gatePriorPhases;
42
+ const fs = __importStar(require("node:fs"));
43
+ const os = __importStar(require("node:os"));
44
+ const path = __importStar(require("node:path"));
45
+ const state_js_1 = require("./state.js");
46
+ const runner_js_1 = require("./runner.js");
47
+ // Single, cwd-independent record of the active checklist dir. `init` writes it
48
+ // (its dir comes from the harness-expanded ${CLAUDE_SKILL_DIR}, so it is
49
+ // reliable), and every later command resolves it no matter which directory they
50
+ // run from — so the skill never has to pass --dir, and there is no cwd-coupled
51
+ // pointer that a stale copy could shadow. Location is overridable via
52
+ // CHECKLIST_HOME (used to sandbox tests).
53
+ const CONFIG_FILE = '.checklist.yml';
54
+ function activePointerPath() {
55
+ const dir = process.env.CHECKLIST_HOME ||
56
+ (process.env.XDG_CONFIG_HOME
57
+ ? path.join(process.env.XDG_CONFIG_HOME, 'checklist')
58
+ : path.join(os.homedir(), '.config', 'checklist'));
59
+ return path.join(dir, 'active');
60
+ }
61
+ function resolveDir(explicit) {
62
+ if (explicit)
63
+ return explicit;
64
+ if (process.env.CHECKLIST_DIR)
65
+ return process.env.CHECKLIST_DIR;
66
+ // A running skill always knows its own dir; the harness exposes it here. This
67
+ // lets an agent run any checklist command (even before `init`) with no flags
68
+ // and no guesswork about where .checklist.yml lives.
69
+ if (process.env.CLAUDE_SKILL_DIR)
70
+ return process.env.CLAUDE_SKILL_DIR;
71
+ const pointerPath = activePointerPath();
72
+ let raw;
73
+ try {
74
+ raw = fs.readFileSync(pointerPath, 'utf-8');
75
+ }
76
+ catch {
77
+ return process.cwd(); // no pointer (ENOENT) or unreadable
78
+ }
79
+ const target = raw.trim();
80
+ if (target) {
81
+ try {
82
+ fs.statSync(path.join(target, CONFIG_FILE));
83
+ return target; // points at a real checklist dir
84
+ }
85
+ catch (e) {
86
+ // Only self-heal when the target is *definitely* gone. statSync returning
87
+ // ENOENT/ENOTDIR means the dir/file is absent; any other errno (EACCES,
88
+ // EIO, ELOOP, an NFS stall, ...) means "can't tell" — never delete a valid
89
+ // pointer over a transient read failure.
90
+ const code = e.code;
91
+ if (code !== 'ENOENT' && code !== 'ENOTDIR') {
92
+ return target;
93
+ }
94
+ }
95
+ }
96
+ // Empty content, or target is definitely gone: self-heal and fall through.
97
+ try {
98
+ fs.unlinkSync(pointerPath);
99
+ }
100
+ catch {
101
+ /* best-effort cleanup; ignore races */
102
+ }
103
+ return process.cwd();
104
+ }
105
+ function writeActivePointer(targetDir) {
106
+ const absDir = path.resolve(targetDir);
107
+ const pointerPath = activePointerPath();
108
+ fs.mkdirSync(path.dirname(pointerPath), { recursive: true });
109
+ fs.writeFileSync(pointerPath, absDir, 'utf-8');
110
+ }
111
+ // Remove the active pointer. With a targetDir, only removes it when it points
112
+ // there (so `reset` of skill A never clobbers an active pointer for skill B).
113
+ // Returns whether a pointer was removed.
114
+ function clearActivePointer(targetDir) {
115
+ const pointerPath = activePointerPath();
116
+ let current;
117
+ try {
118
+ current = fs.readFileSync(pointerPath, 'utf-8').trim();
119
+ }
120
+ catch {
121
+ return false; // no pointer (handles the ENOENT race too)
122
+ }
123
+ if (targetDir && current !== path.resolve(targetDir))
124
+ return false;
125
+ try {
126
+ fs.unlinkSync(pointerPath);
127
+ return true;
128
+ }
129
+ catch {
130
+ return false;
131
+ }
132
+ }
133
+ function findPhaseIndex(config, nameOrIndex) {
134
+ const num = parseInt(nameOrIndex, 10);
135
+ if (!isNaN(num) && num >= 0 && num < config.phases.length) {
136
+ return num;
137
+ }
138
+ const idx = config.phases.findIndex(p => p.name.toLowerCase() === nameOrIndex.toLowerCase());
139
+ if (idx === -1) {
140
+ throw new Error(`Phase not found: "${nameOrIndex}". Use \`checklist phases\` to list available phases.`);
141
+ }
142
+ return idx;
143
+ }
144
+ async function runPhase(phase, phaseIndex, cwd, targetPath) {
145
+ const checks = await Promise.all(phase.checks.map(item => (0, runner_js_1.runCheck)(item, cwd, targetPath)));
146
+ let mechanicalPassed = 0;
147
+ let mechanicalTotal = 0;
148
+ let manualCount = 0;
149
+ for (const c of checks) {
150
+ if (c.kind === 'manual') {
151
+ manualCount++;
152
+ }
153
+ else {
154
+ mechanicalTotal++;
155
+ if (c.result?.status === 'pass')
156
+ mechanicalPassed++;
157
+ }
158
+ }
159
+ return {
160
+ phaseName: phase.name,
161
+ phaseIndex,
162
+ checks,
163
+ mechanicalPassed,
164
+ mechanicalTotal,
165
+ manualCount,
166
+ };
167
+ }
168
+ function gatePriorPhases(config, targetPhaseIndex, state) {
169
+ for (let i = 0; i < targetPhaseIndex; i++) {
170
+ const phase = config.phases[i];
171
+ const ids = phase.checks.map(c => c.id);
172
+ if (!(0, state_js_1.isPhaseComplete)(state, i, ids)) {
173
+ return {
174
+ passed: false,
175
+ failedPhase: phase.name,
176
+ failedPhaseIndex: i,
177
+ };
178
+ }
179
+ }
180
+ return { passed: true };
181
+ }
package/dist/runner.js ADDED
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runCheck = runCheck;
37
+ const node_child_process_1 = require("node:child_process");
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const index_js_1 = require("./builtins/index.js");
41
+ const EXEC_TIMEOUT = 10_000;
42
+ const PREFIX_MAP = {
43
+ 'builtin:': 'builtin',
44
+ 'shell:': 'shell',
45
+ 'script:': 'script',
46
+ };
47
+ function classifyVerify(verify) {
48
+ for (const [prefix, kind] of Object.entries(PREFIX_MAP)) {
49
+ if (verify.startsWith(prefix)) {
50
+ return { kind, value: verify.slice(prefix.length).trim(), explicit: true };
51
+ }
52
+ }
53
+ const firstToken = verify.split(/\s/)[0];
54
+ if (firstToken.includes('/') || /\.(sh|bash|ts|js|py)$/.test(firstToken)) {
55
+ return { kind: 'script', value: verify, explicit: false };
56
+ }
57
+ return { kind: 'shell', value: verify, explicit: false };
58
+ }
59
+ async function runShell(command, cwd) {
60
+ try {
61
+ const stdout = (0, node_child_process_1.execSync)(command, {
62
+ cwd,
63
+ timeout: EXEC_TIMEOUT,
64
+ encoding: 'utf-8',
65
+ shell: '/bin/bash',
66
+ stdio: ['pipe', 'pipe', 'pipe'],
67
+ }).trim();
68
+ return { status: 'pass', message: stdout || 'OK' };
69
+ }
70
+ catch (e) {
71
+ const err = e;
72
+ const detail = (err.stderr || err.message || 'command failed').trim();
73
+ return { status: 'fail', message: detail };
74
+ }
75
+ }
76
+ async function runBuiltin(name, targetPath) {
77
+ const handler = (0, index_js_1.getBuiltin)(name);
78
+ if (!handler) {
79
+ const available = (0, index_js_1.listBuiltins)().join(', ');
80
+ return { status: 'error', message: `unknown builtin "${name}". available: ${available}` };
81
+ }
82
+ return handler(targetPath);
83
+ }
84
+ async function runScript(scriptPath, cwd, explicit) {
85
+ const base = path.resolve(cwd);
86
+ const resolved = path.resolve(base, scriptPath);
87
+ const rel = path.relative(base, resolved);
88
+ // Containment: the script must live inside the checklist dir. Absolute paths
89
+ // are fine as long as they resolve within `base`; escapes (`..`, other roots)
90
+ // are rejected to prevent a malicious .checklist.yml from executing arbitrary
91
+ // files elsewhere on disk.
92
+ if (rel === '..' || rel.startsWith('..' + path.sep) || path.isAbsolute(rel)) {
93
+ return {
94
+ status: 'error',
95
+ message: `script path escapes the checklist dir: "${scriptPath}" (resolved to "${resolved}")`,
96
+ };
97
+ }
98
+ if (!fs.existsSync(resolved)) {
99
+ const hint = explicit
100
+ ? `script not found: "${resolved}" (resolved from "${scriptPath}")`
101
+ : `auto-classified as script (first token contains "/" or has script extension), but file not found: "${resolved}". use "shell:" prefix if this is a shell command`;
102
+ return { status: 'error', message: hint };
103
+ }
104
+ // Containment (canonical): the lexical check above never follows symlinks, but
105
+ // execution does — a symlink planted inside the dir can point outside it. Re-check
106
+ // against the real (symlink-resolved) paths before running.
107
+ const realBase = fs.realpathSync(base);
108
+ const realResolved = fs.realpathSync(resolved);
109
+ const realRel = path.relative(realBase, realResolved);
110
+ if (realRel === '..' || realRel.startsWith('..' + path.sep) || path.isAbsolute(realRel)) {
111
+ return {
112
+ status: 'error',
113
+ message: `script path escapes the checklist dir via symlink: "${scriptPath}" (resolves to "${realResolved}")`,
114
+ };
115
+ }
116
+ return runShell(realResolved, cwd);
117
+ }
118
+ async function runCheck(item, cwd, targetPath) {
119
+ if (!item.verify) {
120
+ return { item, kind: 'manual' };
121
+ }
122
+ const { kind, value, explicit } = classifyVerify(item.verify);
123
+ let result;
124
+ switch (kind) {
125
+ case 'builtin':
126
+ result = await runBuiltin(value, targetPath);
127
+ break;
128
+ case 'script':
129
+ result = await runScript(value, cwd, explicit);
130
+ break;
131
+ case 'shell':
132
+ result = await runShell(value, cwd);
133
+ break;
134
+ }
135
+ return { item, kind: 'mechanical', result };
136
+ }
package/dist/state.js ADDED
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadState = loadState;
37
+ exports.saveState = saveState;
38
+ exports.clearState = clearState;
39
+ exports.isItemChecked = isItemChecked;
40
+ exports.getItemResult = getItemResult;
41
+ exports.setItemResult = setItemResult;
42
+ exports.isPhaseComplete = isPhaseComplete;
43
+ exports.phaseProgress = phaseProgress;
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ const STATE_FILE = '.checklist.state.json';
47
+ function statePath(dir) {
48
+ return path.resolve(dir, STATE_FILE);
49
+ }
50
+ function loadState(dir) {
51
+ const p = statePath(dir);
52
+ if (!fs.existsSync(p)) {
53
+ return { checked: {} };
54
+ }
55
+ let parsed;
56
+ try {
57
+ parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
58
+ }
59
+ catch {
60
+ throw new Error(`state file is corrupt: ${p}. run \`checklist init --force\` to reset it`);
61
+ }
62
+ if (!parsed || typeof parsed !== 'object') {
63
+ throw new Error(`state file is malformed: ${p}. run \`checklist init --force\` to reset it`);
64
+ }
65
+ // `typeof null === 'object'` and `typeof [] === 'object'`, so a corrupt or
66
+ // partially-written file shaped like {"checked":null} or {"checked":[]} would
67
+ // slip past a bare typeof guard and then crash gate evaluation downstream with
68
+ // an unhandled TypeError. Reject any non-plain-object `checked` here instead,
69
+ // so the corruption surfaces as the documented malformed-state error.
70
+ const checked = parsed.checked;
71
+ if (typeof checked !== 'object' || checked === null || Array.isArray(checked)) {
72
+ throw new Error(`state file is malformed: ${p}. run \`checklist init --force\` to reset it`);
73
+ }
74
+ return parsed;
75
+ }
76
+ function saveState(dir, state) {
77
+ fs.writeFileSync(statePath(dir), JSON.stringify(state, null, 2), 'utf-8');
78
+ }
79
+ function clearState(dir) {
80
+ const p = statePath(dir);
81
+ if (fs.existsSync(p)) {
82
+ fs.unlinkSync(p);
83
+ }
84
+ }
85
+ function isItemChecked(state, phaseIndex, itemId) {
86
+ // An item counts as checked only when its recorded result is a PASS — not
87
+ // merely present. A stored fail/error, or a once-green check that later
88
+ // regressed and was re-recorded, must NOT satisfy the gate. The gate is the
89
+ // safety floor: completeness means current pass-status, not existence of a row.
90
+ return state.checked[String(phaseIndex)]?.[itemId]?.status === 'pass';
91
+ }
92
+ function getItemResult(state, phaseIndex, itemId) {
93
+ return state.checked[String(phaseIndex)]?.[itemId];
94
+ }
95
+ function setItemResult(state, phaseIndex, itemId, result) {
96
+ const key = String(phaseIndex);
97
+ if (!state.checked[key]) {
98
+ state.checked[key] = {};
99
+ }
100
+ state.checked[key][itemId] = result;
101
+ }
102
+ function isPhaseComplete(state, phaseIndex, itemIds) {
103
+ return itemIds.every(id => isItemChecked(state, phaseIndex, id));
104
+ }
105
+ function phaseProgress(state, phaseIndex, itemIds) {
106
+ const done = itemIds.filter(id => isItemChecked(state, phaseIndex, id)).length;
107
+ return { done, total: itemIds.length };
108
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@iamk77/skill-checklist",
3
+ "version": "0.2.0",
4
+ "description": "Flight checklist CLI that gates a Claude Code skill's phases — a stage cannot open until every check in every prior stage is recorded as passing",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "skill",
9
+ "checklist",
10
+ "gate",
11
+ "cli",
12
+ "agent"
13
+ ],
14
+ "homepage": "https://github.com/IamK77/Skill/tree/main/devtools/checklist#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/IamK77/Skill/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/IamK77/Skill.git",
21
+ "directory": "devtools/checklist"
22
+ },
23
+ "author": "IamK77",
24
+ "license": "Apache-2.0",
25
+ "bin": {
26
+ "checklist": "bin/checklist.js"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "bin",
31
+ "NOTICE"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "dev": "tsc --watch",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "test:coverage": "vitest run --coverage",
39
+ "prepublishOnly": "npm run build"
40
+ },
41
+ "dependencies": {
42
+ "commander": "^14.0.0",
43
+ "gray-matter": "^4.0.3",
44
+ "js-yaml": "^4.1.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/js-yaml": "^4.0.9",
48
+ "@types/node": "^20.0.0",
49
+ "@vitest/coverage-v8": "^4.1.6",
50
+ "tsx": "^4.21.0",
51
+ "typescript": "^5.5.0",
52
+ "vitest": "^4.1.6"
53
+ },
54
+ "engines": {
55
+ "node": ">=18"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ }
60
+ }