codymaster 4.5.4 → 4.8.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.
Files changed (133) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/README.md +86 -31
  3. package/dist/backends/viking-backend.js +235 -0
  4. package/dist/backends/viking-http-client.js +176 -0
  5. package/dist/browse-server.js +251 -0
  6. package/dist/cli/command-registry.js +26 -0
  7. package/dist/cli/commands/agent.js +120 -0
  8. package/dist/cli/commands/dashboard.js +93 -0
  9. package/dist/cli/commands/design-studio.js +111 -0
  10. package/dist/cli/commands/distro.js +25 -0
  11. package/dist/cli/commands/engineering.js +488 -0
  12. package/dist/cli/commands/project.js +324 -0
  13. package/dist/cli/commands/skill-chain.js +269 -0
  14. package/dist/cli/commands/system.js +89 -0
  15. package/dist/cli/commands/task.js +254 -0
  16. package/dist/cli/update-check.js +83 -0
  17. package/dist/cm-config.js +110 -0
  18. package/dist/cm-suggest.js +77 -0
  19. package/dist/continuity.js +8 -0
  20. package/dist/distro-validate.js +54 -0
  21. package/dist/guardian-core.js +74 -0
  22. package/dist/index.js +36 -2759
  23. package/dist/mcp-context-server.js +60 -1
  24. package/dist/mcp-skills-tools.js +81 -0
  25. package/dist/retro-summary.js +70 -0
  26. package/dist/second-opinion-providers.js +79 -0
  27. package/dist/sprint-pipeline.js +228 -0
  28. package/dist/storage-backend.js +63 -0
  29. package/dist/utils/cli-utils.js +76 -0
  30. package/dist/utils/skill-utils.js +32 -0
  31. package/install.sh +286 -58
  32. package/package.json +16 -5
  33. package/scripts/build-skills.mjs +51 -0
  34. package/scripts/gate-0-repo-hygiene.js +75 -0
  35. package/scripts/postinstall.js +56 -1
  36. package/scripts/security-scan.js +1 -1
  37. package/scripts/validate-skills.mjs +42 -0
  38. package/scripts/viking-demo.ts +105 -0
  39. package/skills/CLAUDE.md +2 -2
  40. package/skills/_shared/helpers.md +10 -0
  41. package/skills/cm-ads-tracker/SKILL.md +3 -6
  42. package/skills/cm-browse/SKILL.md +28 -0
  43. package/skills/cm-conductor-worktrees/SKILL.md +24 -0
  44. package/skills/cm-content-factory/SKILL.md +1 -1
  45. package/skills/cm-content-factory/landing/docs/content/changelog.md +36 -0
  46. package/skills/cm-content-factory/landing/docs/content/deployment.md +46 -0
  47. package/skills/cm-content-factory/landing/docs/content/execution-flow.md +67 -0
  48. package/skills/cm-content-factory/landing/docs/content/openspace.md +27 -0
  49. package/skills/cm-content-factory/landing/docs/content/openviking.md +33 -0
  50. package/skills/cm-content-factory/landing/docs/content/use-cases.md +26 -0
  51. package/skills/cm-content-factory/landing/docs/content/v5-intro.md +28 -0
  52. package/skills/cm-content-factory/landing/docs/index.html +240 -0
  53. package/skills/cm-content-factory/landing/index.html +99 -99
  54. package/skills/cm-content-factory/landing/script.js +42 -0
  55. package/skills/cm-content-factory/landing/translations.js +400 -400
  56. package/skills/cm-continuity/SKILL.md +33 -6
  57. package/skills/cm-design-studio/SKILL.md +30 -0
  58. package/skills/cm-ecosystem-roadmap/SKILL.md +11 -0
  59. package/skills/cm-engineering-meta/SKILL.md +69 -0
  60. package/skills/cm-growth-hacking/SKILL.md +1 -12
  61. package/skills/cm-guardian-runtime/SKILL.md +22 -0
  62. package/skills/cm-mcp-engineering/SKILL.md +18 -0
  63. package/skills/cm-notebooklm/SKILL.md +1 -17
  64. package/skills/cm-post-deploy-canary/SKILL.md +18 -0
  65. package/skills/cm-qa-visual-cli/SKILL.md +18 -0
  66. package/skills/cm-retro-cli/SKILL.md +19 -0
  67. package/skills/cm-second-opinion-cli/SKILL.md +19 -0
  68. package/skills/cm-secret-shield/SKILL.md +2 -2
  69. package/skills/cm-sprint-bus/SKILL.md +29 -0
  70. package/skills/cm-start/SKILL.md +11 -2
  71. package/skills/cm-tdd/SKILL.md +61 -74
  72. package/skills/profiles/README.md +21 -0
  73. package/skills/profiles/core.txt +23 -0
  74. package/skills/profiles/design.txt +6 -0
  75. package/skills/profiles/full.txt +58 -0
  76. package/skills/profiles/growth.txt +10 -0
  77. package/skills/profiles/knowledge.txt +7 -0
  78. package/scripts/test-gemini.js +0 -13
  79. package/skills/cm-frappe-agent/SKILL.md +0 -134
  80. package/skills/cm-frappe-agent/agents/doctype-architect.md +0 -596
  81. package/skills/cm-frappe-agent/agents/erpnext-customizer.md +0 -643
  82. package/skills/cm-frappe-agent/agents/frappe-backend.md +0 -814
  83. package/skills/cm-frappe-agent/agents/frappe-custom-frontend.md +0 -557
  84. package/skills/cm-frappe-agent/agents/frappe-debugger.md +0 -625
  85. package/skills/cm-frappe-agent/agents/frappe-fixer.md +0 -275
  86. package/skills/cm-frappe-agent/agents/frappe-frontend.md +0 -660
  87. package/skills/cm-frappe-agent/agents/frappe-installer.md +0 -158
  88. package/skills/cm-frappe-agent/agents/frappe-performance.md +0 -307
  89. package/skills/cm-frappe-agent/agents/frappe-planner.md +0 -419
  90. package/skills/cm-frappe-agent/agents/frappe-remote-ops.md +0 -153
  91. package/skills/cm-frappe-agent/agents/github-workflow.md +0 -286
  92. package/skills/cm-frappe-agent/commands/frappe-app.md +0 -351
  93. package/skills/cm-frappe-agent/commands/frappe-backend.md +0 -162
  94. package/skills/cm-frappe-agent/commands/frappe-bench.md +0 -254
  95. package/skills/cm-frappe-agent/commands/frappe-debug.md +0 -263
  96. package/skills/cm-frappe-agent/commands/frappe-doctype-create.md +0 -272
  97. package/skills/cm-frappe-agent/commands/frappe-doctype-field.md +0 -310
  98. package/skills/cm-frappe-agent/commands/frappe-erpnext.md +0 -210
  99. package/skills/cm-frappe-agent/commands/frappe-fix.md +0 -59
  100. package/skills/cm-frappe-agent/commands/frappe-frontend.md +0 -210
  101. package/skills/cm-frappe-agent/commands/frappe-fullstack.md +0 -243
  102. package/skills/cm-frappe-agent/commands/frappe-github.md +0 -57
  103. package/skills/cm-frappe-agent/commands/frappe-install.md +0 -52
  104. package/skills/cm-frappe-agent/commands/frappe-plan.md +0 -442
  105. package/skills/cm-frappe-agent/commands/frappe-remote.md +0 -58
  106. package/skills/cm-frappe-agent/commands/frappe-test.md +0 -356
  107. package/skills/cm-frappe-agent/docs/README.md +0 -51
  108. package/skills/cm-frappe-agent/docs/agents-catalog.md +0 -113
  109. package/skills/cm-frappe-agent/docs/architecture.md +0 -149
  110. package/skills/cm-frappe-agent/docs/commands-catalog.md +0 -82
  111. package/skills/cm-frappe-agent/docs/resources-catalog.md +0 -66
  112. package/skills/cm-frappe-agent/docs/sitemap-urls.txt +0 -52
  113. package/skills/cm-frappe-agent/docs/sitemap.md +0 -81
  114. package/skills/cm-frappe-agent/docs/sop/user-guide.md +0 -178
  115. package/skills/cm-frappe-agent/docs/sop/vibe-coding-guide.md +0 -122
  116. package/skills/cm-frappe-agent/resources/7-layer-architecture.md +0 -985
  117. package/skills/cm-frappe-agent/resources/bench_commands.md +0 -73
  118. package/skills/cm-frappe-agent/resources/code-patterns-guide.md +0 -948
  119. package/skills/cm-frappe-agent/resources/common_pitfalls.md +0 -266
  120. package/skills/cm-frappe-agent/resources/doctype-registry.md +0 -158
  121. package/skills/cm-frappe-agent/resources/installation-guide.md +0 -289
  122. package/skills/cm-frappe-agent/resources/rest-api-patterns.md +0 -182
  123. package/skills/cm-frappe-agent/resources/scaffold_checklist.md +0 -82
  124. package/skills/cm-frappe-agent/resources/upgrade_patterns.md +0 -113
  125. package/skills/cm-frappe-agent/resources/web-form-patterns.md +0 -252
  126. package/skills/cm-frappe-agent/skills/bench-commands/SKILL.md +0 -621
  127. package/skills/cm-frappe-agent/skills/client-scripts/SKILL.md +0 -642
  128. package/skills/cm-frappe-agent/skills/doctype-patterns/SKILL.md +0 -576
  129. package/skills/cm-frappe-agent/skills/frappe-api/SKILL.md +0 -740
  130. package/skills/cm-frappe-agent/skills/remote-operations/SKILL.md +0 -47
  131. package/skills/cm-frappe-agent/skills/server-scripts/SKILL.md +0 -608
  132. package/skills/cm-frappe-agent/skills/web-forms/SKILL.md +0 -46
  133. package/skills/frappe-app-builder.zip +0 -0
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initDesignStudioArtifacts = initDesignStudioArtifacts;
7
+ exports.registerDesignStudioCommands = registerDesignStudioCommands;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ function projectPath(opt) {
12
+ return path_1.default.resolve(opt || process.cwd());
13
+ }
14
+ const CHECKLIST = `# Design studio — checklist
15
+
16
+ - [ ] Problem / JTBD one-liner
17
+ - [ ] 2–3 UI variants named (A/B/C)
18
+ - [ ] Chosen variant + rationale
19
+ - [ ] Handoff block filled in HANDOFF.md
20
+ `;
21
+ const VARIANTS = `# Variants
22
+
23
+ | Id | Name | Notes |
24
+ |----|------|-------|
25
+ | A | | |
26
+ | B | | |
27
+ | C | | |
28
+ `;
29
+ const HANDOFF = `# Handoff to implementation
30
+
31
+ **Chosen variant:** (A/B/C)
32
+
33
+ **Screens / flows:**
34
+
35
+ **Tokens / components to reuse:**
36
+
37
+ **Out of scope:**
38
+
39
+ **Agent prompt stub:**
40
+
41
+ \`\`\`
42
+ Implement the chosen variant using existing design system tokens. …
43
+ \`\`\`
44
+ `;
45
+ const README = `# .cm/design-studio
46
+
47
+ Local artifact folder for **cm-design-studio**: variants, checklist, handoff.
48
+
49
+ Happy path:
50
+
51
+ 1. \`cm design-studio init\`
52
+ 2. Edit CHECKLIST.md + VARIANTS.md
53
+ 3. Fill HANDOFF.md, then run your build skill (e.g. cm-execution) with that stub.
54
+ `;
55
+ /** Writes default artifact files; skips paths that already exist. Returns created + skipped counts. */
56
+ function initDesignStudioArtifacts(root) {
57
+ const base = path_1.default.join(root, '.cm', 'design-studio');
58
+ fs_1.default.mkdirSync(base, { recursive: true });
59
+ const files = [
60
+ ['README.md', README],
61
+ ['CHECKLIST.md', CHECKLIST],
62
+ ['VARIANTS.md', VARIANTS],
63
+ ['HANDOFF.md', HANDOFF],
64
+ ];
65
+ let created = 0;
66
+ let skipped = 0;
67
+ for (const [name, body] of files) {
68
+ const p = path_1.default.join(base, name);
69
+ if (fs_1.default.existsSync(p)) {
70
+ skipped++;
71
+ }
72
+ else {
73
+ fs_1.default.writeFileSync(p, body, 'utf8');
74
+ created++;
75
+ }
76
+ }
77
+ return { created, skipped };
78
+ }
79
+ function registerDesignStudioCommands(program) {
80
+ const ds = program
81
+ .command('design-studio')
82
+ .description('Design variant workspace under .cm/design-studio');
83
+ ds.command('init')
84
+ .description('Create .cm/design-studio with checklist and handoff templates')
85
+ .option('--project <dir>', 'project root', process.cwd())
86
+ .action((opts) => {
87
+ const root = projectPath(opts.project);
88
+ const { created, skipped } = initDesignStudioArtifacts(root);
89
+ const base = path_1.default.join(root, '.cm', 'design-studio');
90
+ if (created)
91
+ console.log(chalk_1.default.green(`wrote ${created} file(s) under`), base);
92
+ if (skipped)
93
+ console.log(chalk_1.default.yellow(`skipped ${skipped} existing`));
94
+ });
95
+ ds.command('status')
96
+ .description('List design-studio artifact files if present')
97
+ .option('--project <dir>', 'project root', process.cwd())
98
+ .action((opts) => {
99
+ const root = projectPath(opts.project);
100
+ const base = path_1.default.join(root, '.cm', 'design-studio');
101
+ if (!fs_1.default.existsSync(base)) {
102
+ console.log(chalk_1.default.yellow('Not initialized. Run:'), chalk_1.default.cyan('cm design-studio init'));
103
+ return;
104
+ }
105
+ for (const f of fs_1.default.readdirSync(base)) {
106
+ const p = path_1.default.join(base, f);
107
+ const st = fs_1.default.statSync(p);
108
+ console.log(st.isDirectory() ? `${f}/` : f);
109
+ }
110
+ });
111
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerDistroCommands = registerDistroCommands;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const distro_validate_1 = require("../../distro-validate");
9
+ function registerDistroCommands(program) {
10
+ const distro = program.command('distro').description('Skill pack validation (ecosystem roadmap)');
11
+ distro
12
+ .command('validate')
13
+ .description('Validate a skill directory (SKILL.md / tmpl + optional meta.json)')
14
+ .argument('<dir>', 'path to skill folder')
15
+ .action((dir) => {
16
+ const r = (0, distro_validate_1.validateSkillPackDir)(dir);
17
+ for (const w of r.warnings)
18
+ console.log(chalk_1.default.yellow('warning:'), w);
19
+ for (const e of r.errors)
20
+ console.error(chalk_1.default.red('error:'), e);
21
+ if (!r.ok)
22
+ process.exit(1);
23
+ console.log(chalk_1.default.green('OK'), dir);
24
+ });
25
+ }
@@ -0,0 +1,488 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.registerEngineeringCommands = registerEngineeringCommands;
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const child_process_1 = require("child_process");
19
+ const http_1 = __importDefault(require("http"));
20
+ const chalk_1 = __importDefault(require("chalk"));
21
+ const browse_server_1 = require("../../browse-server");
22
+ const guardian_core_1 = require("../../guardian-core");
23
+ const cm_config_1 = require("../../cm-config");
24
+ const second_opinion_providers_1 = require("../../second-opinion-providers");
25
+ const sprint_pipeline_1 = require("../../sprint-pipeline");
26
+ const retro_summary_1 = require("../../retro-summary");
27
+ const cm_suggest_1 = require("../../cm-suggest");
28
+ function projectPath(opt) {
29
+ return path_1.default.resolve(opt || process.cwd());
30
+ }
31
+ function registerEngineeringCommands(program) {
32
+ const browse = program.command('browse').description('Playwright browse daemon (local QA / screenshots)');
33
+ browse
34
+ .command('start')
35
+ .option('-p, --port <n>', 'port (default: .cm/config.yaml browse.port or 17395)')
36
+ .option('-H, --host <h>', 'bind host (default: config or 127.0.0.1)')
37
+ .option('--token <t>', 'bearer token (or env CM_BROWSE_TOKEN or config browse.token)')
38
+ .option('--headed', 'headed browser', false)
39
+ .action((opts) => __awaiter(this, void 0, void 0, function* () {
40
+ var _a, _b, _c, _d, _e, _f, _g;
41
+ const root = process.cwd();
42
+ const cfg = (0, cm_config_1.loadCmConfig)(root);
43
+ const port = parseInt(String((_c = (_a = opts.port) !== null && _a !== void 0 ? _a : (_b = cfg.browse) === null || _b === void 0 ? void 0 : _b.port) !== null && _c !== void 0 ? _c : 17395), 10);
44
+ const host = String((_f = (_d = opts.host) !== null && _d !== void 0 ? _d : (_e = cfg.browse) === null || _e === void 0 ? void 0 : _e.host) !== null && _f !== void 0 ? _f : '127.0.0.1');
45
+ const token = opts.token ||
46
+ process.env.CM_BROWSE_TOKEN ||
47
+ ((_g = cfg.browse) === null || _g === void 0 ? void 0 : _g.token) ||
48
+ 'dev-token-change-me';
49
+ const daemon = new browse_server_1.BrowseDaemon({
50
+ host,
51
+ port,
52
+ token,
53
+ headless: !opts.headed,
54
+ });
55
+ yield daemon.listen();
56
+ console.log(chalk_1.default.green(`cm-browse listening http://${host}:${port}`));
57
+ console.log(chalk_1.default.dim(`Authorization: Bearer ${token.slice(0, 8)}…`));
58
+ console.log(chalk_1.default.dim('POST /session/start, /navigate, /refs/refresh, /click, /fill, GET /screenshot'));
59
+ process.on('SIGINT', () => __awaiter(this, void 0, void 0, function* () {
60
+ yield daemon.close();
61
+ process.exit(0);
62
+ }));
63
+ }));
64
+ const guardian = program.command('guardian').description('Runtime safety: destructive command patterns + path freeze');
65
+ guardian
66
+ .command('check')
67
+ .argument('<cmd...>', 'shell command to check')
68
+ .action((parts) => {
69
+ var _a;
70
+ const cmd = parts.join(' ');
71
+ const cfg = (0, cm_config_1.loadCmConfig)(process.cwd());
72
+ const extra = (_a = cfg.guardian) === null || _a === void 0 ? void 0 : _a.whitelist_prefixes;
73
+ const r = (0, guardian_core_1.checkShellCommand)(cmd, (extra === null || extra === void 0 ? void 0 : extra.length) ? { extraWhitelist: extra } : undefined);
74
+ if (!r.safe) {
75
+ console.error(chalk_1.default.red('BLOCKED:'), r.reason);
76
+ console.error(chalk_1.default.dim('Pattern:'), r.matchedPattern);
77
+ (0, guardian_core_1.appendGuardianLog)(process.cwd(), `BLOCKED: ${cmd}`);
78
+ process.exit(1);
79
+ }
80
+ console.log(chalk_1.default.green('OK'), chalk_1.default.dim(cmd));
81
+ });
82
+ guardian
83
+ .command('path-check')
84
+ .requiredOption('--file <f>', 'file path')
85
+ .option('--roots <r>', 'comma-separated roots (default: config guardian.freeze_roots or src,lib)')
86
+ .action((opts) => {
87
+ var _a, _b, _c;
88
+ const cwd = process.cwd();
89
+ const cfg = (0, cm_config_1.loadCmConfig)(cwd);
90
+ const rootsCsv = (_a = opts.roots) !== null && _a !== void 0 ? _a : (((_c = (_b = cfg.guardian) === null || _b === void 0 ? void 0 : _b.freeze_roots) === null || _c === void 0 ? void 0 : _c.length) ? cfg.guardian.freeze_roots.join(',') : 'src,lib');
91
+ const roots = (0, guardian_core_1.normalizeRoots)(cwd, String(rootsCsv)
92
+ .split(',')
93
+ .map((s) => s.trim())
94
+ .filter(Boolean));
95
+ const ok = (0, guardian_core_1.isPathUnderRoots)(opts.file, roots);
96
+ if (!ok) {
97
+ console.error(chalk_1.default.red('Path outside freeze roots:'), opts.file);
98
+ (0, guardian_core_1.appendGuardianLog)(cwd, `FREEZE_VIOLATION: ${opts.file}`);
99
+ process.exit(1);
100
+ }
101
+ console.log(chalk_1.default.green('OK'), opts.file);
102
+ });
103
+ const sprint = program.command('sprint').description('Opinionated pipeline + .cm/sprint Context Bus');
104
+ sprint
105
+ .command('init')
106
+ .option('--project <dir>')
107
+ .option('--from <step>', `one of: ${sprint_pipeline_1.SPRINT_STEPS.join(',')}`)
108
+ .action((opts) => {
109
+ const root = projectPath(opts.project);
110
+ const from = opts.from;
111
+ if (from && !sprint_pipeline_1.SPRINT_STEPS.includes(from)) {
112
+ console.error(chalk_1.default.red('Invalid --from'));
113
+ process.exit(1);
114
+ }
115
+ const state = (0, sprint_pipeline_1.initSprint)(root, from);
116
+ console.log(chalk_1.default.green('Sprint initialized'));
117
+ console.log(chalk_1.default.dim(JSON.stringify(state, null, 2)));
118
+ });
119
+ sprint
120
+ .command('status')
121
+ .option('--project <dir>')
122
+ .action((opts) => {
123
+ const root = projectPath(opts.project);
124
+ const state = (0, sprint_pipeline_1.readSprintState)(root);
125
+ if (!state) {
126
+ console.log(chalk_1.default.yellow('No sprint state. Run: cm sprint init'));
127
+ return;
128
+ }
129
+ const next = state.current_index >= state.pipeline.length
130
+ ? '(done)'
131
+ : state.pipeline[state.current_index];
132
+ console.log(chalk_1.default.cyan('Current step:'), next);
133
+ if (typeof next === 'string' && next !== '(done)')
134
+ console.log(chalk_1.default.dim('Skill hint:'), (0, sprint_pipeline_1.skillMappingForStep)(next));
135
+ console.log(chalk_1.default.dim('Completed:'), state.completed.join(', ') || '(none)');
136
+ console.log(chalk_1.default.dim('Skipped:'), state.skipped.join(', ') || '(none)');
137
+ });
138
+ sprint
139
+ .command('complete')
140
+ .argument('<step>', 'step name')
141
+ .option('--project <dir>')
142
+ .option('-m, --message <text>', 'artifact markdown body', '')
143
+ .action((step, opts) => {
144
+ const root = projectPath(opts.project);
145
+ if (!sprint_pipeline_1.SPRINT_STEPS.includes(step)) {
146
+ console.error(chalk_1.default.red('Invalid step'));
147
+ process.exit(1);
148
+ }
149
+ const body = opts.message ||
150
+ `# ${step}\n\n_Completed via \`cm sprint complete\` — replace with real notes._\n`;
151
+ try {
152
+ const state = (0, sprint_pipeline_1.completeSprintStep)(root, step, body);
153
+ console.log(chalk_1.default.green('Step recorded:', step));
154
+ console.log(chalk_1.default.dim('Next index:', state.current_index));
155
+ }
156
+ catch (e) {
157
+ console.error(chalk_1.default.red(e.message));
158
+ process.exit(1);
159
+ }
160
+ });
161
+ sprint
162
+ .command('skip')
163
+ .argument('[step]', 'step name (default: current step)')
164
+ .option('--project <dir>')
165
+ .action((step, opts) => {
166
+ const root = projectPath(opts.project);
167
+ const state = (0, sprint_pipeline_1.readSprintState)(root);
168
+ if (!state) {
169
+ console.error(chalk_1.default.red('No sprint state. Run: cm sprint init'));
170
+ process.exit(1);
171
+ }
172
+ if (state.current_index >= state.pipeline.length) {
173
+ console.error(chalk_1.default.red('Sprint pipeline already finished'));
174
+ process.exit(1);
175
+ }
176
+ const current = state.pipeline[state.current_index];
177
+ const target = (step || current);
178
+ if (step && !sprint_pipeline_1.SPRINT_STEPS.includes(target)) {
179
+ console.error(chalk_1.default.red('Invalid step'));
180
+ process.exit(1);
181
+ }
182
+ try {
183
+ const next = (0, sprint_pipeline_1.skipSprintStep)(root, target);
184
+ console.log(chalk_1.default.green('Skipped step:', target));
185
+ console.log(chalk_1.default.dim('Next index:', next.current_index));
186
+ }
187
+ catch (e) {
188
+ console.error(chalk_1.default.red(e.message));
189
+ process.exit(1);
190
+ }
191
+ });
192
+ sprint
193
+ .command('reset')
194
+ .option('--project <dir>')
195
+ .option('--no-backup', 'do not copy sprint files to .cm/sprint/backup before clearing')
196
+ .action((opts) => {
197
+ const root = projectPath(opts.project);
198
+ const r = (0, sprint_pipeline_1.resetSprint)(root, { backup: opts.backup !== false });
199
+ if (!r.ok) {
200
+ console.log(chalk_1.default.yellow('Nothing to reset (no sprint data under .cm/sprint).'));
201
+ return;
202
+ }
203
+ if (r.backupDir)
204
+ console.log(chalk_1.default.dim('Backup:'), r.backupDir);
205
+ console.log(chalk_1.default.green('Sprint data cleared. Run: cm sprint init'));
206
+ });
207
+ sprint
208
+ .command('dry-run')
209
+ .option('--project <dir>')
210
+ .action((opts) => {
211
+ const root = projectPath(opts.project);
212
+ const d = (0, sprint_pipeline_1.sprintDryRun)(root);
213
+ console.log(chalk_1.default.cyan('Steps:'), d.steps.join(' → '));
214
+ d.artifacts.forEach((a) => console.log(chalk_1.default.dim(' -'), a));
215
+ });
216
+ program
217
+ .command('second-opinion')
218
+ .description('Send unified diff to a secondary model (redacts obvious secrets)')
219
+ .option('--file <f>', 'file containing diff text')
220
+ .option('--provider <p>', 'openai | anthropic', 'openai')
221
+ .action((opts) => __awaiter(this, void 0, void 0, function* () {
222
+ const provider = String(opts.provider || 'openai').toLowerCase();
223
+ const raw = opts.file ? fs_1.default.readFileSync(opts.file, 'utf8') : '';
224
+ const text = raw ? (0, second_opinion_providers_1.redactDiffForReview)(raw) : '';
225
+ if (!opts.file || !text.trim()) {
226
+ console.log(chalk_1.default.yellow('Pass --file <diff.txt>. Set OPENAI_API_KEY (openai) or ANTHROPIC_API_KEY (anthropic).'));
227
+ console.log(chalk_1.default.dim('Diff content is redacted for common secret patterns before sending.'));
228
+ return;
229
+ }
230
+ try {
231
+ let out;
232
+ if (provider === 'anthropic')
233
+ out = yield (0, second_opinion_providers_1.reviewWithAnthropic)(text);
234
+ else if (provider === 'openai')
235
+ out = yield (0, second_opinion_providers_1.reviewWithOpenAI)(text);
236
+ else {
237
+ console.error(chalk_1.default.red('Unknown --provider (use openai or anthropic)'));
238
+ process.exit(1);
239
+ }
240
+ console.log(out);
241
+ }
242
+ catch (e) {
243
+ console.error(chalk_1.default.red(e.message));
244
+ process.exit(1);
245
+ }
246
+ }));
247
+ program
248
+ .command('qa-visual')
249
+ .description('Hit cm-browse for screenshot + health (requires browse running)')
250
+ .requiredOption('--url <u>', 'page URL to navigate')
251
+ .option('--port <n>', 'browse daemon port (default: config browse.port or 17395)')
252
+ .option('--token <t>', 'or env CM_BROWSE_TOKEN or config browse.token')
253
+ .action((opts) => __awaiter(this, void 0, void 0, function* () {
254
+ var _a, _b, _c, _d;
255
+ const cfg = (0, cm_config_1.loadCmConfig)(process.cwd());
256
+ const token = opts.token || process.env.CM_BROWSE_TOKEN || ((_a = cfg.browse) === null || _a === void 0 ? void 0 : _a.token) || 'dev-token-change-me';
257
+ const port = parseInt(String((_d = (_b = opts.port) !== null && _b !== void 0 ? _b : (_c = cfg.browse) === null || _c === void 0 ? void 0 : _c.port) !== null && _d !== void 0 ? _d : 17395), 10);
258
+ const auth = `Bearer ${token}`;
259
+ yield browseRequest(port, '/session/start', 'POST', auth, { headless: true });
260
+ yield browseRequest(port, '/navigate', 'POST', auth, { url: opts.url });
261
+ yield browseRequest(port, '/refs/refresh', 'POST', auth, {});
262
+ const png = yield browseBuffer(port, '/screenshot', auth);
263
+ const out = path_1.default.join(process.cwd(), 'cm-qa-visual.png');
264
+ fs_1.default.writeFileSync(out, png);
265
+ console.log(chalk_1.default.green('Screenshot saved'), out);
266
+ }));
267
+ program
268
+ .command('canary')
269
+ .description('Post-deploy smoke: HTTP fetch + optional browse console; baseline compare')
270
+ .requiredOption('--url <u>', 'URL to fetch (http/https)')
271
+ .option('--browse-port <n>', 'if set, GET /console from browse (default: config canary.browse_port)')
272
+ .option('--token <t>', 'browse token (env CM_BROWSE_TOKEN or config)')
273
+ .option('--save-baseline', 'write .cm/canary-baseline.json after check')
274
+ .option('--compare-baseline', 'fail on HTTP regression or 2× latency vs baseline')
275
+ .action((opts) => __awaiter(this, void 0, void 0, function* () {
276
+ var _a, _b, _c, _d;
277
+ const root = process.cwd();
278
+ const cfg = (0, cm_config_1.loadCmConfig)(root);
279
+ const u = new URL(opts.url);
280
+ const { status, latency_ms } = yield httpProbeUrl(u.href);
281
+ if (status >= 400) {
282
+ console.error(chalk_1.default.red(`HTTP ${status}`), u.href);
283
+ process.exit(1);
284
+ }
285
+ if (opts.compareBaseline) {
286
+ const baselinePath = path_1.default.join(root, '.cm', 'canary-baseline.json');
287
+ if (!fs_1.default.existsSync(baselinePath)) {
288
+ console.error(chalk_1.default.red('No baseline file. Run once with --save-baseline'));
289
+ process.exit(1);
290
+ }
291
+ const prev = JSON.parse(fs_1.default.readFileSync(baselinePath, 'utf8'));
292
+ if (prev.http_status !== undefined &&
293
+ prev.http_status < 400 &&
294
+ status >= 400) {
295
+ console.error(chalk_1.default.red('HTTP regression vs baseline'));
296
+ process.exit(1);
297
+ }
298
+ if (typeof prev.latency_ms === 'number' &&
299
+ prev.latency_ms > 50 &&
300
+ latency_ms > prev.latency_ms * 2) {
301
+ console.error(chalk_1.default.red(`Latency regression: ${latency_ms}ms vs baseline ${prev.latency_ms}ms`));
302
+ process.exit(1);
303
+ }
304
+ console.log(chalk_1.default.dim('Baseline compare OK'));
305
+ }
306
+ if (opts.saveBaseline) {
307
+ const baselinePath = path_1.default.join(root, '.cm', 'canary-baseline.json');
308
+ fs_1.default.mkdirSync(path_1.default.dirname(baselinePath), { recursive: true });
309
+ fs_1.default.writeFileSync(baselinePath, JSON.stringify({
310
+ url: opts.url,
311
+ http_status: status,
312
+ latency_ms,
313
+ at: new Date().toISOString(),
314
+ }, null, 2), 'utf8');
315
+ console.log(chalk_1.default.dim('Wrote'), baselinePath);
316
+ }
317
+ console.log(chalk_1.default.green('HTTP OK'), u.href, chalk_1.default.dim(`${status} ${latency_ms}ms`));
318
+ const browsePort = (_a = opts.browsePort) !== null && _a !== void 0 ? _a : (((_b = cfg.canary) === null || _b === void 0 ? void 0 : _b.browse_port) != null ? String(cfg.canary.browse_port) : undefined);
319
+ if (browsePort) {
320
+ const token = opts.token ||
321
+ process.env.CM_BROWSE_TOKEN ||
322
+ ((_c = cfg.canary) === null || _c === void 0 ? void 0 : _c.token) ||
323
+ ((_d = cfg.browse) === null || _d === void 0 ? void 0 : _d.token) ||
324
+ 'dev-token-change-me';
325
+ const raw = yield browseRaw(parseInt(browsePort, 10), '/console', `Bearer ${token}`);
326
+ console.log(chalk_1.default.dim('Browse console (last messages):'), raw.slice(0, 500));
327
+ }
328
+ }));
329
+ const conductor = program.command('conductor').description('Git worktree helpers for parallel sprints');
330
+ conductor
331
+ .command('add')
332
+ .requiredOption('--at <dir>', 'new worktree directory')
333
+ .requiredOption('--branch <b>', 'branch name')
334
+ .option('--base <b>', 'start from branch', 'main')
335
+ .action((opts) => {
336
+ (0, child_process_1.execSync)(`git worktree add -b ${opts.branch} ${opts.at} ${opts.base}`, {
337
+ stdio: 'inherit',
338
+ cwd: process.cwd(),
339
+ });
340
+ console.log(chalk_1.default.green('Worktree created'));
341
+ });
342
+ conductor.command('list').action(() => {
343
+ (0, child_process_1.execSync)('git worktree list', { stdio: 'inherit', cwd: process.cwd() });
344
+ });
345
+ const retro = program
346
+ .command('retro')
347
+ .description('Append operational learning (.cm/operational-learnings.jsonl) or print summary');
348
+ retro
349
+ .command('summary')
350
+ .description('Aggregate JSONL by tool; optional --since filter')
351
+ .option('--project <dir>')
352
+ .option('--since <iso>', 'include entries on or after this ISO timestamp')
353
+ .option('--format <f>', 'json | md', 'md')
354
+ .action((opts) => {
355
+ const root = projectPath(opts.project);
356
+ const j = path_1.default.join(root, '.cm', 'operational-learnings.jsonl');
357
+ let entries = (0, retro_summary_1.loadRetroEntries)(j);
358
+ if (opts.since)
359
+ entries = (0, retro_summary_1.filterSince)(entries, opts.since);
360
+ const byTool = (0, retro_summary_1.countByTool)(entries);
361
+ const fmt = (opts.format || 'md').toLowerCase();
362
+ if (fmt === 'json')
363
+ console.log((0, retro_summary_1.formatRetroJson)(entries, byTool));
364
+ else
365
+ console.log((0, retro_summary_1.formatRetroMarkdown)(entries, byTool));
366
+ });
367
+ retro
368
+ .option('--note <text>', 'append entry')
369
+ .option('--tool <t>', 'tool label', 'cli')
370
+ .option('--project <dir>')
371
+ .option('--summary', 'print last 20 lines (legacy quick view)')
372
+ .action((opts) => {
373
+ const root = projectPath(opts.project);
374
+ const dir = path_1.default.join(root, '.cm');
375
+ if (!fs_1.default.existsSync(dir))
376
+ fs_1.default.mkdirSync(dir, { recursive: true });
377
+ const j = path_1.default.join(dir, 'operational-learnings.jsonl');
378
+ if (opts.summary) {
379
+ if (!fs_1.default.existsSync(j)) {
380
+ console.log(chalk_1.default.yellow('No entries yet'));
381
+ return;
382
+ }
383
+ const lines = fs_1.default.readFileSync(j, 'utf8').trim().split('\n').filter(Boolean).slice(-20);
384
+ for (const line of lines)
385
+ console.log(line);
386
+ return;
387
+ }
388
+ if (!opts.note) {
389
+ console.log(chalk_1.default.yellow('Pass --note "...", --summary, or: cm retro summary'));
390
+ return;
391
+ }
392
+ const rec = {
393
+ ts: new Date().toISOString(),
394
+ tool: opts.tool,
395
+ note: opts.note,
396
+ };
397
+ fs_1.default.appendFileSync(j, JSON.stringify(rec) + '\n', 'utf8');
398
+ console.log(chalk_1.default.green('Recorded'));
399
+ });
400
+ program
401
+ .command('suggest')
402
+ .description('Proactive skill hints from git status + sprint state')
403
+ .option('--project <dir>')
404
+ .action((opts) => {
405
+ const root = projectPath(opts.project);
406
+ const list = (0, cm_suggest_1.suggestFromContext)(root);
407
+ if (list.length === 0) {
408
+ console.log(chalk_1.default.yellow('No strong signals. Try cm-start or cm-planning for the next step.'));
409
+ return;
410
+ }
411
+ for (const s of list) {
412
+ console.log(chalk_1.default.cyan(s.skill));
413
+ console.log(chalk_1.default.dim(` ${s.reason}`));
414
+ }
415
+ });
416
+ }
417
+ function browseRequest(port, pathname, method, auth, body) {
418
+ return new Promise((resolve, reject) => {
419
+ const data = Buffer.from(JSON.stringify(body));
420
+ const req = http_1.default.request({
421
+ hostname: '127.0.0.1',
422
+ port,
423
+ path: pathname,
424
+ method,
425
+ headers: {
426
+ 'Content-Type': 'application/json',
427
+ 'Content-Length': data.length,
428
+ Authorization: auth,
429
+ },
430
+ }, (res) => {
431
+ res.resume();
432
+ if (res.statusCode && res.statusCode >= 400)
433
+ reject(new Error(`HTTP ${res.statusCode}`));
434
+ else
435
+ resolve();
436
+ });
437
+ req.on('error', reject);
438
+ req.write(data);
439
+ req.end();
440
+ });
441
+ }
442
+ function browseBuffer(port, pathname, auth) {
443
+ return new Promise((resolve, reject) => {
444
+ http_1.default.get({
445
+ hostname: '127.0.0.1',
446
+ port,
447
+ path: pathname,
448
+ headers: { Authorization: auth },
449
+ }, (res) => {
450
+ const chunks = [];
451
+ res.on('data', (c) => chunks.push(c));
452
+ res.on('end', () => {
453
+ if (res.statusCode && res.statusCode >= 400)
454
+ reject(new Error(`HTTP ${res.statusCode}`));
455
+ else
456
+ resolve(Buffer.concat(chunks));
457
+ });
458
+ }).on('error', reject);
459
+ });
460
+ }
461
+ function browseRaw(port, pathname, auth) {
462
+ return new Promise((resolve, reject) => {
463
+ http_1.default.get({
464
+ hostname: '127.0.0.1',
465
+ port,
466
+ path: pathname,
467
+ headers: { Authorization: auth },
468
+ }, (res) => {
469
+ let s = '';
470
+ res.on('data', (c) => (s += c));
471
+ res.on('end', () => {
472
+ if (res.statusCode && res.statusCode >= 400)
473
+ reject(new Error(`HTTP ${res.statusCode}`));
474
+ else
475
+ resolve(s);
476
+ });
477
+ }).on('error', reject);
478
+ });
479
+ }
480
+ function httpProbeUrl(url) {
481
+ return __awaiter(this, void 0, void 0, function* () {
482
+ const t0 = performance.now();
483
+ const res = yield fetch(url, { redirect: 'follow' });
484
+ yield res.arrayBuffer().catch(() => { });
485
+ const latency_ms = Math.round(performance.now() - t0);
486
+ return { status: res.status, latency_ms };
487
+ });
488
+ }