codymaster 4.6.0 → 5.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.
Files changed (161) hide show
  1. package/CHANGELOG.md +74 -8
  2. package/README.md +192 -95
  3. package/dist/advisory-handoff.js +89 -0
  4. package/dist/advisory-report.js +105 -0
  5. package/dist/browse-server.js +251 -0
  6. package/dist/cli/command-registry.js +34 -0
  7. package/dist/cli/commands/agent.js +120 -0
  8. package/dist/cli/commands/bench.js +69 -0
  9. package/dist/cli/commands/brain.js +108 -0
  10. package/dist/cli/commands/dashboard.js +93 -0
  11. package/dist/cli/commands/design-studio.js +111 -0
  12. package/dist/cli/commands/distro.js +25 -0
  13. package/dist/cli/commands/engineering.js +596 -0
  14. package/dist/cli/commands/evolve.js +123 -0
  15. package/dist/cli/commands/mcp-serve.js +104 -0
  16. package/dist/cli/commands/project.js +324 -0
  17. package/dist/cli/commands/skill-chain.js +269 -0
  18. package/dist/cli/commands/system.js +89 -0
  19. package/dist/cli/commands/task.js +254 -0
  20. package/dist/cli/update-check.js +83 -0
  21. package/dist/cm-config.js +92 -0
  22. package/dist/cm-suggest.js +77 -0
  23. package/dist/codybench/judges/automated.js +31 -0
  24. package/dist/codybench/runners/claude-code.js +32 -0
  25. package/dist/codybench/suites/memory-retention.js +85 -0
  26. package/dist/codybench/suites/tdd-regression.js +35 -0
  27. package/dist/codybench/suites/token-efficiency.js +55 -0
  28. package/dist/codybench/types.js +2 -0
  29. package/dist/context-db.js +157 -0
  30. package/dist/continuity.js +2 -6
  31. package/dist/distro-validate.js +54 -0
  32. package/dist/execution-analyzer.js +138 -0
  33. package/dist/guardian-core.js +74 -0
  34. package/dist/index.js +36 -2759
  35. package/dist/indexer/skills-lib.js +533 -0
  36. package/dist/indexer/skills-map.js +1374 -0
  37. package/dist/indexer/skills.js +16 -0
  38. package/dist/learning-promoter.js +246 -0
  39. package/dist/mcp-context-server.js +289 -1
  40. package/dist/mcp-skills-tools.js +81 -0
  41. package/dist/retro-summary.js +70 -0
  42. package/dist/second-opinion-providers.js +79 -0
  43. package/dist/skill-chain.js +63 -1
  44. package/dist/skill-evolver.js +456 -0
  45. package/dist/skill-execution-cache.js +254 -0
  46. package/dist/smart-brain-router.js +184 -0
  47. package/dist/sprint-pipeline.js +228 -0
  48. package/dist/storage-backend.js +14 -67
  49. package/dist/token-budget.js +88 -0
  50. package/dist/utils/cli-utils.js +76 -0
  51. package/dist/utils/skill-utils.js +32 -0
  52. package/package.json +17 -7
  53. package/scripts/build-skills.mjs +51 -0
  54. package/scripts/gate-0-repo-hygiene.js +75 -0
  55. package/scripts/postinstall.js +34 -28
  56. package/scripts/security-scan.js +1 -1
  57. package/scripts/validate-skills.mjs +42 -0
  58. package/skills/CLAUDE.md +2 -7
  59. package/skills/_shared/helpers.md +2 -8
  60. package/skills/cm-ads-tracker/SKILL.md +3 -6
  61. package/skills/cm-browse/SKILL.md +34 -0
  62. package/skills/cm-conductor-worktrees/SKILL.md +28 -0
  63. package/skills/cm-content-factory/SKILL.md +1 -1
  64. package/skills/cm-content-factory/landing/docs/content/changelog.md +36 -0
  65. package/skills/cm-content-factory/landing/docs/content/deployment.md +46 -0
  66. package/skills/cm-content-factory/landing/docs/content/execution-flow.md +67 -0
  67. package/skills/cm-content-factory/landing/docs/content/memory-system.md +38 -0
  68. package/skills/cm-content-factory/landing/docs/content/openspace.md +27 -0
  69. package/skills/cm-content-factory/landing/docs/content/use-cases.md +26 -0
  70. package/skills/cm-content-factory/landing/docs/content/v5-intro.md +28 -0
  71. package/skills/cm-content-factory/landing/docs/index.html +240 -0
  72. package/skills/cm-content-factory/landing/index.html +100 -100
  73. package/skills/cm-content-factory/landing/script.js +42 -0
  74. package/skills/cm-content-factory/landing/translations.js +400 -400
  75. package/skills/cm-continuity/SKILL.md +32 -33
  76. package/skills/cm-design-studio/SKILL.md +34 -0
  77. package/skills/cm-ecosystem-roadmap/SKILL.md +15 -0
  78. package/skills/cm-engineering-meta/SKILL.md +73 -0
  79. package/skills/cm-growth-hacking/SKILL.md +1 -12
  80. package/skills/cm-guardian-runtime/SKILL.md +26 -0
  81. package/skills/cm-mcp-engineering/SKILL.md +22 -0
  82. package/skills/cm-notebooklm/SKILL.md +1 -17
  83. package/skills/cm-post-deploy-canary/SKILL.md +22 -0
  84. package/skills/cm-project-bootstrap/SKILL.md +11 -0
  85. package/skills/cm-qa-visual-cli/SKILL.md +22 -0
  86. package/skills/cm-retro-cli/SKILL.md +23 -0
  87. package/skills/cm-second-opinion-cli/SKILL.md +23 -0
  88. package/skills/cm-secret-shield/SKILL.md +2 -2
  89. package/skills/cm-security-gate/SKILL.md +1 -0
  90. package/skills/cm-skill-chain/SKILL.md +25 -4
  91. package/skills/cm-skill-evolution/SKILL.md +83 -0
  92. package/skills/cm-skill-health/SKILL.md +83 -0
  93. package/skills/cm-skill-index/SKILL.md +11 -3
  94. package/skills/cm-skill-search/SKILL.md +49 -0
  95. package/skills/cm-skill-share/SKILL.md +58 -0
  96. package/skills/cm-sprint-bus/SKILL.md +33 -0
  97. package/skills/cm-start/SKILL.md +0 -10
  98. package/skills/cm-tdd/SKILL.md +59 -72
  99. package/skills/profiles/README.md +21 -0
  100. package/skills/profiles/core.txt +23 -0
  101. package/skills/profiles/design.txt +6 -0
  102. package/skills/profiles/full.txt +62 -0
  103. package/skills/profiles/growth.txt +10 -0
  104. package/skills/profiles/knowledge.txt +7 -0
  105. package/install.sh +0 -901
  106. package/scripts/test-gemini.js +0 -13
  107. package/skills/cm-frappe-agent/SKILL.md +0 -134
  108. package/skills/cm-frappe-agent/agents/doctype-architect.md +0 -596
  109. package/skills/cm-frappe-agent/agents/erpnext-customizer.md +0 -643
  110. package/skills/cm-frappe-agent/agents/frappe-backend.md +0 -814
  111. package/skills/cm-frappe-agent/agents/frappe-custom-frontend.md +0 -557
  112. package/skills/cm-frappe-agent/agents/frappe-debugger.md +0 -625
  113. package/skills/cm-frappe-agent/agents/frappe-fixer.md +0 -275
  114. package/skills/cm-frappe-agent/agents/frappe-frontend.md +0 -660
  115. package/skills/cm-frappe-agent/agents/frappe-installer.md +0 -158
  116. package/skills/cm-frappe-agent/agents/frappe-performance.md +0 -307
  117. package/skills/cm-frappe-agent/agents/frappe-planner.md +0 -419
  118. package/skills/cm-frappe-agent/agents/frappe-remote-ops.md +0 -153
  119. package/skills/cm-frappe-agent/agents/github-workflow.md +0 -286
  120. package/skills/cm-frappe-agent/commands/frappe-app.md +0 -351
  121. package/skills/cm-frappe-agent/commands/frappe-backend.md +0 -162
  122. package/skills/cm-frappe-agent/commands/frappe-bench.md +0 -254
  123. package/skills/cm-frappe-agent/commands/frappe-debug.md +0 -263
  124. package/skills/cm-frappe-agent/commands/frappe-doctype-create.md +0 -272
  125. package/skills/cm-frappe-agent/commands/frappe-doctype-field.md +0 -310
  126. package/skills/cm-frappe-agent/commands/frappe-erpnext.md +0 -210
  127. package/skills/cm-frappe-agent/commands/frappe-fix.md +0 -59
  128. package/skills/cm-frappe-agent/commands/frappe-frontend.md +0 -210
  129. package/skills/cm-frappe-agent/commands/frappe-fullstack.md +0 -243
  130. package/skills/cm-frappe-agent/commands/frappe-github.md +0 -57
  131. package/skills/cm-frappe-agent/commands/frappe-install.md +0 -52
  132. package/skills/cm-frappe-agent/commands/frappe-plan.md +0 -442
  133. package/skills/cm-frappe-agent/commands/frappe-remote.md +0 -58
  134. package/skills/cm-frappe-agent/commands/frappe-test.md +0 -356
  135. package/skills/cm-frappe-agent/docs/README.md +0 -51
  136. package/skills/cm-frappe-agent/docs/agents-catalog.md +0 -113
  137. package/skills/cm-frappe-agent/docs/architecture.md +0 -149
  138. package/skills/cm-frappe-agent/docs/commands-catalog.md +0 -82
  139. package/skills/cm-frappe-agent/docs/resources-catalog.md +0 -66
  140. package/skills/cm-frappe-agent/docs/sitemap-urls.txt +0 -52
  141. package/skills/cm-frappe-agent/docs/sitemap.md +0 -81
  142. package/skills/cm-frappe-agent/docs/sop/user-guide.md +0 -178
  143. package/skills/cm-frappe-agent/docs/sop/vibe-coding-guide.md +0 -122
  144. package/skills/cm-frappe-agent/resources/7-layer-architecture.md +0 -985
  145. package/skills/cm-frappe-agent/resources/bench_commands.md +0 -73
  146. package/skills/cm-frappe-agent/resources/code-patterns-guide.md +0 -948
  147. package/skills/cm-frappe-agent/resources/common_pitfalls.md +0 -266
  148. package/skills/cm-frappe-agent/resources/doctype-registry.md +0 -158
  149. package/skills/cm-frappe-agent/resources/installation-guide.md +0 -289
  150. package/skills/cm-frappe-agent/resources/rest-api-patterns.md +0 -182
  151. package/skills/cm-frappe-agent/resources/scaffold_checklist.md +0 -82
  152. package/skills/cm-frappe-agent/resources/upgrade_patterns.md +0 -113
  153. package/skills/cm-frappe-agent/resources/web-form-patterns.md +0 -252
  154. package/skills/cm-frappe-agent/skills/bench-commands/SKILL.md +0 -621
  155. package/skills/cm-frappe-agent/skills/client-scripts/SKILL.md +0 -642
  156. package/skills/cm-frappe-agent/skills/doctype-patterns/SKILL.md +0 -576
  157. package/skills/cm-frappe-agent/skills/frappe-api/SKILL.md +0 -740
  158. package/skills/cm-frappe-agent/skills/remote-operations/SKILL.md +0 -47
  159. package/skills/cm-frappe-agent/skills/server-scripts/SKILL.md +0 -608
  160. package/skills/cm-frappe-agent/skills/web-forms/SKILL.md +0 -46
  161. package/skills/frappe-app-builder.zip +0 -0
@@ -1,16 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.VikingBackend = exports.SqliteBackend = void 0;
3
+ exports.SqliteBackend = void 0;
7
4
  exports.getBackend = getBackend;
8
- const fs_1 = __importDefault(require("fs"));
9
- const path_1 = __importDefault(require("path"));
10
5
  const context_db_1 = require("./context-db");
11
- const viking_backend_1 = require("./backends/viking-backend");
12
- Object.defineProperty(exports, "VikingBackend", { enumerable: true, get: function () { return viking_backend_1.VikingBackend; } });
13
- const viking_http_client_1 = require("./backends/viking-http-client");
6
+ const cm_config_1 = require("./cm-config");
14
7
  // ─── SqliteBackend ────────────────────────────────────────────────────────────
15
8
  /**
16
9
  * Default backend — thin wrapper around context-db.ts (better-sqlite3 + FTS5).
@@ -37,65 +30,18 @@ class SqliteBackend {
37
30
  }
38
31
  writeSkillOutput(o) { (0, context_db_1.writeSkillOutput)(this.dbPath, o); }
39
32
  getSkillOutputs(sessionId) { return (0, context_db_1.getSkillOutputs)(this.dbPath, sessionId); }
33
+ recordExecutionAnalysis(a) { (0, context_db_1.recordExecutionAnalysis)(this.dbPath, a); }
34
+ getExecutionAnalyses(limit = 20) { return (0, context_db_1.getExecutionAnalyses)(this.dbPath, limit); }
35
+ getSkillMetric(skill) { return (0, context_db_1.getSkillMetric)(this.dbPath, skill); }
36
+ listSkillMetrics(limit = 50) { return (0, context_db_1.listSkillMetrics)(this.dbPath, limit); }
40
37
  }
41
38
  exports.SqliteBackend = SqliteBackend;
42
- /**
43
- * Minimal YAML parser — reads `storage.backend` and `storage.viking.*` keys.
44
- * Avoids adding a js-yaml dependency for a handful of config fields.
45
- *
46
- * Supported format:
47
- * storage:
48
- * backend: viking
49
- * viking:
50
- * host: localhost
51
- * port: 1933
52
- * workspace: codymaster
53
- * timeout: 60000
54
- */
55
- function loadStorageConfig(projectPath) {
56
- var _a;
57
- const configPath = path_1.default.join(projectPath, '.cm', 'config.yaml');
58
- if (!fs_1.default.existsSync(configPath))
59
- return {};
60
- try {
61
- const raw = fs_1.default.readFileSync(configPath, 'utf-8');
62
- // Extract storage.backend
63
- const backendMatch = raw.match(/^storage:\s*\n(?:[ \t]+\S[^\n]*\n)*?[ \t]+backend:\s*(\S+)/m);
64
- const backend = (_a = backendMatch === null || backendMatch === void 0 ? void 0 : backendMatch[1]) === null || _a === void 0 ? void 0 : _a.trim();
65
- // Extract storage.viking.* keys
66
- const vikingBlock = raw.match(/[ \t]+viking:\s*\n((?:[ \t]{4,}[^\n]+\n?)*)/m);
67
- let viking;
68
- if (vikingBlock === null || vikingBlock === void 0 ? void 0 : vikingBlock[1]) {
69
- viking = {};
70
- for (const line of vikingBlock[1].split('\n')) {
71
- const kv = line.match(/[ \t]+(\w+):\s*(\S+)/);
72
- if (!kv)
73
- continue;
74
- const [, key, val] = kv;
75
- if (key === 'host')
76
- viking.host = val;
77
- if (key === 'workspace')
78
- viking.workspace = val;
79
- if (key === 'port')
80
- viking.port = parseInt(val, 10);
81
- if (key === 'timeout')
82
- viking.timeout = parseInt(val, 10);
83
- }
84
- }
85
- if (!backend)
86
- return {};
87
- return { storage: Object.assign({ backend }, (viking ? { viking } : {})) };
88
- }
89
- catch (_b) {
90
- return {};
91
- }
92
- }
93
39
  // ─── Factory ─────────────────────────────────────────────────────────────────
94
40
  /**
95
41
  * Returns the configured StorageBackend for the given project.
96
42
  *
97
- * Reads `.cm/config.yaml → storage.backend` (default: `sqlite`).
98
- * For `viking` backend, reads `storage.viking.*` for connection config.
43
+ * Reads `.cm/config.yaml → storage.backend` via `loadCmConfig` (default: `sqlite`).
44
+ * Legacy `storage.backend: viking` configs are warned and routed back to sqlite.
99
45
  *
100
46
  * Usage:
101
47
  * const backend = getBackend('/path/to/project');
@@ -103,13 +49,14 @@ function loadStorageConfig(projectPath) {
103
49
  * const results = backend.queryLearnings('i18n locale');
104
50
  */
105
51
  function getBackend(projectPath) {
106
- var _a, _b, _c;
107
- const config = loadStorageConfig(projectPath);
108
- const engine = (_b = (_a = config === null || config === void 0 ? void 0 : config.storage) === null || _a === void 0 ? void 0 : _a.backend) !== null && _b !== void 0 ? _b : 'sqlite';
52
+ var _a, _b;
53
+ const cfg = (0, cm_config_1.loadCmConfig)(projectPath);
54
+ const engine = ((_b = (_a = cfg.storage) === null || _a === void 0 ? void 0 : _a.backend) !== null && _b !== void 0 ? _b : 'sqlite').toLowerCase();
109
55
  switch (engine) {
110
56
  case 'viking': {
111
- const vikingConfig = Object.assign(Object.assign({}, viking_http_client_1.DEFAULT_VIKING_CONFIG), (_c = config === null || config === void 0 ? void 0 : config.storage) === null || _c === void 0 ? void 0 : _c.viking);
112
- return new viking_backend_1.VikingBackend(vikingConfig);
57
+ console.warn('[CodyMaster] storage.backend: viking has been removed. ' +
58
+ 'Falling back to sqlite for the supported default path.');
59
+ return new SqliteBackend(projectPath);
113
60
  }
114
61
  case 'sqlite':
115
62
  default:
@@ -8,6 +8,10 @@ exports.loadBudget = loadBudget;
8
8
  exports.checkBudget = checkBudget;
9
9
  exports.estimateTokens = estimateTokens;
10
10
  exports.generateBudgetReport = generateBudgetReport;
11
+ exports.getDefaultTierBudgets = getDefaultTierBudgets;
12
+ exports.checkTierBudget = checkTierBudget;
13
+ exports.generateTierReport = generateTierReport;
14
+ exports.formatSavingsReport = formatSavingsReport;
11
15
  const fs_1 = __importDefault(require("fs"));
12
16
  const path_1 = __importDefault(require("path"));
13
17
  // ─── Constants ──────────────────────────────────────────────────────────────
@@ -106,3 +110,87 @@ function generateBudgetReport(budget) {
106
110
  lines.push(`${'TOTAL'.padEnd(25)} ${sum.toLocaleString().padStart(10)} ${(((sum / total) * 100).toFixed(2) + '%').padStart(10)}`);
107
111
  return lines.join('\n');
108
112
  }
113
+ // ─── Per-Tier Budgets (Smart Brain Router integration) ───────────────────────
114
+ /**
115
+ * Default per-tier token budgets for the 5-Tier Brain architecture.
116
+ * Used by SmartBrainRouter to enforce tier-level spending limits.
117
+ */
118
+ function getDefaultTierBudgets() {
119
+ return [
120
+ { tier: 1, label: 'Sensory', soft: 0, hard: 0 }, // Always on, zero cost
121
+ { tier: 2, label: 'Working', soft: 500, hard: 1000 }, // CONTINUITY.md
122
+ { tier: 3, label: 'Long-term', soft: 500, hard: 3000 }, // Learnings/decisions
123
+ { tier: 4, label: 'Semantic', soft: 0, hard: 2000 }, // qmd vector search
124
+ { tier: 5, label: 'Structural', soft: 0, hard: 4000 }, // CodeGraph AST
125
+ ];
126
+ }
127
+ /**
128
+ * Check if a tier's token usage is within budget.
129
+ */
130
+ function checkTierBudget(tierBudgets, tier, tokenCount, enforcement = 'soft') {
131
+ const budget = tierBudgets.find(tb => tb.tier === tier);
132
+ if (!budget) {
133
+ return { allowed: false, remaining: 0, suggestion: `Unknown tier ${tier}` };
134
+ }
135
+ const limit = enforcement === 'hard' ? budget.hard : budget.soft;
136
+ if (limit === 0) {
137
+ // Tier is disabled by default (soft=0), but allowed up to hard limit
138
+ if (tokenCount <= budget.hard) {
139
+ return { allowed: true, remaining: budget.hard - tokenCount };
140
+ }
141
+ return {
142
+ allowed: false,
143
+ remaining: budget.hard - tokenCount,
144
+ suggestion: `Tier ${tier} (${budget.label}) exceeds hard limit of ${budget.hard} tokens.`,
145
+ };
146
+ }
147
+ const remaining = limit - tokenCount;
148
+ if (remaining >= 0) {
149
+ return { allowed: true, remaining };
150
+ }
151
+ return {
152
+ allowed: enforcement !== 'hard',
153
+ remaining,
154
+ suggestion: `Tier ${tier} (${budget.label}) over by ~${Math.abs(remaining)} tokens. Consider L0 loading.`,
155
+ };
156
+ }
157
+ /**
158
+ * Generate a tier-level budget report.
159
+ */
160
+ function generateTierReport(tierBudgets) {
161
+ const lines = [
162
+ `Brain Tier Budget Report`,
163
+ '─'.repeat(60),
164
+ `${'Tier'.padEnd(6)} ${'Label'.padEnd(12)} ${'Soft'.padStart(8)} ${'Hard'.padStart(8)} ${'Status'.padStart(10)}`,
165
+ '─'.repeat(60),
166
+ ];
167
+ for (const tb of tierBudgets) {
168
+ const status = tb.soft === 0 ? 'off/demand' : 'active';
169
+ lines.push(`${String(tb.tier).padEnd(6)} ${tb.label.padEnd(12)} ${tb.soft.toLocaleString().padStart(8)} ${tb.hard.toLocaleString().padStart(8)} ${status.padStart(10)}`);
170
+ }
171
+ const totalSoft = tierBudgets.reduce((s, tb) => s + tb.soft, 0);
172
+ const totalHard = tierBudgets.reduce((s, tb) => s + tb.hard, 0);
173
+ lines.push('─'.repeat(60));
174
+ lines.push(`${'TOTAL'.padEnd(18)} ${totalSoft.toLocaleString().padStart(8)} ${totalHard.toLocaleString().padStart(8)}`);
175
+ return lines.join('\n');
176
+ }
177
+ /**
178
+ * Format token savings report for CLI display.
179
+ */
180
+ function formatSavingsReport(savings) {
181
+ const lines = [
182
+ `💰 Token Savings Report`,
183
+ '─'.repeat(50),
184
+ `Brain routing: ~${savings.brainRoutingSaved.toLocaleString()} tokens saved`,
185
+ `Cache hits: ~${savings.cacheHitsSaved.toLocaleString()} tokens saved`,
186
+ `Progressive loading: ~${savings.progressiveLoadSaved.toLocaleString()} tokens saved`,
187
+ '─'.repeat(50),
188
+ `Total saved: ~${savings.totalSaved.toLocaleString()} tokens`,
189
+ `Tasks this session: ${savings.sessionTasks}`,
190
+ ];
191
+ if (savings.sessionTasks > 0) {
192
+ const avgSaved = Math.round(savings.totalSaved / savings.sessionTasks);
193
+ lines.push(`Avg saved per task: ~${avgSaved.toLocaleString()} tokens`);
194
+ }
195
+ return lines.join('\n');
196
+ }
@@ -0,0 +1,76 @@
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.padRight = padRight;
7
+ exports.openUrl = openUrl;
8
+ exports.formatTimeAgoCli = formatTimeAgoCli;
9
+ exports.progressBar = progressBar;
10
+ exports.isDashboardRunning = isDashboardRunning;
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const child_process_1 = require("child_process");
14
+ const chalk_1 = __importDefault(require("chalk"));
15
+ /**
16
+ * Pads a string on the right with spaces.
17
+ */
18
+ function padRight(str, len) {
19
+ if (str.length >= len)
20
+ return str;
21
+ return str + ' '.repeat(len - str.length);
22
+ }
23
+ /**
24
+ * Opens a URL in the default browser.
25
+ */
26
+ function openUrl(url) {
27
+ const plat = os_1.default.platform();
28
+ const command = plat === 'darwin' ? 'open' : plat === 'win32' ? 'start' : 'xdg-open';
29
+ (0, child_process_1.exec)(`${command} "${url}"`);
30
+ }
31
+ /**
32
+ * Formats a date string relative to now (e.g. "2m ago", "1h ago").
33
+ */
34
+ function formatTimeAgoCli(dateStr) {
35
+ const date = new Date(dateStr);
36
+ const now = new Date();
37
+ const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
38
+ if (seconds < 60)
39
+ return `${seconds}s ago`;
40
+ const minutes = Math.floor(seconds / 60);
41
+ if (minutes < 60)
42
+ return `${minutes}m ago`;
43
+ const hours = Math.floor(minutes / 60);
44
+ if (hours < 24)
45
+ return `${hours}h ago`;
46
+ const days = Math.floor(hours / 24);
47
+ return `${days}d ago`;
48
+ }
49
+ /**
50
+ * Generates a green/gray progress bar string.
51
+ */
52
+ function progressBar(pct) {
53
+ const total = 12;
54
+ const filled = Math.max(0, Math.min(total, Math.round((pct / 100) * total)));
55
+ return chalk_1.default.green('█'.repeat(filled)) + chalk_1.default.gray('░'.repeat(total - filled));
56
+ }
57
+ /**
58
+ * Returns true if the dashboard is running (via PID file).
59
+ */
60
+ function isDashboardRunning(pidFile) {
61
+ try {
62
+ if (!fs_1.default.existsSync(pidFile))
63
+ return false;
64
+ const pidInput = fs_1.default.readFileSync(pidFile, 'utf-8').trim();
65
+ if (!pidInput)
66
+ return false;
67
+ const pid = parseInt(pidInput);
68
+ if (isNaN(pid))
69
+ return false;
70
+ process.kill(pid, 0);
71
+ return true;
72
+ }
73
+ catch (_a) {
74
+ return false;
75
+ }
76
+ }
@@ -0,0 +1,32 @@
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.getAvailableSkills = getAvailableSkills;
7
+ exports.getSkillCount = getSkillCount;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ /**
11
+ * Discovers and returns a list of available 'cm-' skills.
12
+ */
13
+ function getAvailableSkills() {
14
+ try {
15
+ const rootDir = path_1.default.resolve(__dirname, '..', '..');
16
+ const skillsDir = path_1.default.join(rootDir, 'skills');
17
+ if (!fs_1.default.existsSync(skillsDir)) {
18
+ return [];
19
+ }
20
+ return fs_1.default.readdirSync(skillsDir)
21
+ .filter(f => f.startsWith('cm-') && fs_1.default.statSync(path_1.default.join(skillsDir, f)).isDirectory());
22
+ }
23
+ catch (_a) {
24
+ return [];
25
+ }
26
+ }
27
+ /**
28
+ * Returns the count of available 'cm-' skills.
29
+ */
30
+ function getSkillCount() {
31
+ return getAvailableSkills().length;
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codymaster",
3
- "version": "4.6.0",
3
+ "version": "5.2.0",
4
4
  "description": "68+ Skills. Ship 10x faster. AI-powered coding skill kit for Claude, Cursor, Gemini & more.",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -19,15 +19,23 @@
19
19
  "build": "tsc",
20
20
  "start": "ts-node src/index.ts",
21
21
  "test:gate": "vitest run --reporter=verbose",
22
+ "test:gate:kit": "npm run build && npm run validate:skills && npm run check:skills && vitest run --reporter=verbose",
22
23
  "gate:secrets": "node scripts/gate-0-secrets.js",
24
+ "gate:hygiene": "node scripts/gate-0-repo-hygiene.js",
23
25
  "gate:fix": "node scripts/security-fixer.js --fix",
24
26
  "gate:check": "node scripts/security-fixer.js",
25
27
  "gate:syntax": "node scripts/gate-1-syntax.js",
26
28
  "gate:dist": "node scripts/gate-5-dist-verify.js",
27
29
  "gate:smoke": "node scripts/gate-6-smoke-test.js",
28
- "deploy": "npm run gate:secrets && npm run gate:syntax && npm run test:gate && npm run gate:dist && npm run gate:smoke",
29
- "deploy:dry": "npm run gate:secrets && npm run gate:syntax && npm run test:gate && npm run gate:dist && echo '✅ All gates passed. Ready to deploy.'",
30
- "postinstall": "node scripts/postinstall.js"
30
+ "deploy": "npm run gate:secrets && npm run gate:hygiene && npm run gate:syntax && npm run test:gate:kit && npm run gate:dist && npm run gate:smoke",
31
+ "deploy:dry": "npm run gate:secrets && npm run gate:hygiene && npm run gate:syntax && npm run test:gate:kit && npm run gate:dist && echo '✅ All gates passed. Ready to deploy.'",
32
+ "postinstall": "node scripts/postinstall.js",
33
+ "validate:skills": "node scripts/validate-skills.mjs",
34
+ "build:skills": "node scripts/build-skills.mjs",
35
+ "check:skills": "node scripts/build-skills.mjs --check",
36
+ "docs:dev": "vitepress dev docs",
37
+ "docs:build": "vitepress build docs",
38
+ "docs:preview": "vitepress preview docs"
31
39
  },
32
40
  "keywords": [
33
41
  "ai",
@@ -52,8 +60,7 @@
52
60
  "commands/",
53
61
  "public/",
54
62
  "README.md",
55
- "CHANGELOG.md",
56
- "install.sh"
63
+ "CHANGELOG.md"
57
64
  ],
58
65
  "publishConfig": {
59
66
  "access": "public"
@@ -65,7 +72,8 @@
65
72
  "chokidar": "^5.0.0",
66
73
  "commander": "^14.0.3",
67
74
  "express": "^5.2.1",
68
- "prompts": "^2.4.2"
75
+ "prompts": "^2.4.2",
76
+ "yaml": "^2.8.3"
69
77
  },
70
78
  "devDependencies": {
71
79
  "@types/better-sqlite3": "^7.6.13",
@@ -74,8 +82,10 @@
74
82
  "@types/prompts": "^2.4.9",
75
83
  "acorn": "^8.16.0",
76
84
  "jsdom": "^29.0.1",
85
+ "playwright": "^1.50.0",
77
86
  "ts-node": "^10.9.2",
78
87
  "typescript": "^5.9.3",
88
+ "vitepress": "^1.6.4",
79
89
  "vitest": "^4.1.0"
80
90
  },
81
91
  "overrides": {
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate SKILL.md from SKILL.md.tmpl + meta.json when present.
4
+ * Usage: node scripts/build-skills.mjs [--check]
5
+ */
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const skillsRoot = path.join(__dirname, '..', 'skills');
12
+ const check = process.argv.includes('--check');
13
+
14
+ function render(tmpl, vars) {
15
+ return tmpl.replace(/\{\{(\w+)\}\}/g, (_, k) => (vars[k] != null ? String(vars[k]) : `{{${k}}}`));
16
+ }
17
+
18
+ let tmplCount = 0;
19
+ if (!fs.existsSync(skillsRoot)) process.exit(0);
20
+
21
+ for (const dir of fs.readdirSync(skillsRoot, { withFileTypes: true })) {
22
+ if (!dir.isDirectory()) continue;
23
+ const folder = path.join(skillsRoot, dir.name);
24
+ const tmplPath = path.join(folder, 'SKILL.md.tmpl');
25
+ const metaPath = path.join(folder, 'meta.json');
26
+ const outPath = path.join(folder, 'SKILL.md');
27
+ if (!fs.existsSync(tmplPath)) continue;
28
+
29
+ tmplCount++;
30
+ const tmpl = fs.readFileSync(tmplPath, 'utf8');
31
+ let meta = {};
32
+ if (fs.existsSync(metaPath)) {
33
+ meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
34
+ }
35
+ const out = render(tmpl, meta);
36
+ if (check) {
37
+ const cur = fs.existsSync(outPath) ? fs.readFileSync(outPath, 'utf8') : '';
38
+ if (cur !== out) {
39
+ console.error(`check failed: ${outPath} out of date (run npm run build:skills)`);
40
+ process.exit(2);
41
+ }
42
+ } else {
43
+ fs.writeFileSync(outPath, out, 'utf8');
44
+ }
45
+ }
46
+
47
+ if (tmplCount === 0) {
48
+ console.log('build-skills: no SKILL.md.tmpl under skills/ (OK)');
49
+ } else {
50
+ console.log(check ? `build-skills: --check OK (${tmplCount})` : `build-skills: wrote ${tmplCount} skill(s)`);
51
+ }
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Gate 0b: Repo Hygiene
4
+ * Ensures tracked files and git remote URLs are safe before push/deploy.
5
+ */
6
+ const { execFileSync } = require('child_process');
7
+
8
+ const FORBIDDEN_TRACKED_PATTERNS = [
9
+ /^\.DS_Store$/,
10
+ /^\.env(\..+)?$/,
11
+ /^\.dev\.vars(\..+)?$/,
12
+ /^.*\.(pem|key|p12|pfx)$/i,
13
+ /^.*\.(log|tmp|bak|swp)$/i,
14
+ ];
15
+
16
+ function run(command, args) {
17
+ return execFileSync(command, args, { encoding: 'utf-8' }).trim();
18
+ }
19
+
20
+ function hasEmbeddedCredentials(remoteUrl) {
21
+ return /^https?:\/\/[^/\s]+@/i.test(remoteUrl);
22
+ }
23
+
24
+ function getTrackedFiles() {
25
+ const out = run('git', ['ls-files']);
26
+ if (!out) return [];
27
+ return out.split('\n').filter(Boolean);
28
+ }
29
+
30
+ function checkTrackedFiles() {
31
+ const tracked = getTrackedFiles();
32
+ return tracked.filter((file) =>
33
+ !file.endsWith('.example') &&
34
+ FORBIDDEN_TRACKED_PATTERNS.some((pattern) => pattern.test(file)),
35
+ );
36
+ }
37
+
38
+ function checkOriginRemote() {
39
+ try {
40
+ return run('git', ['remote', 'get-url', 'origin']);
41
+ } catch (_error) {
42
+ return '';
43
+ }
44
+ }
45
+
46
+ function main() {
47
+ const failures = [];
48
+
49
+ const originUrl = checkOriginRemote();
50
+ if (originUrl && hasEmbeddedCredentials(originUrl)) {
51
+ failures.push(
52
+ [
53
+ 'origin remote URL has embedded credentials.',
54
+ 'Fix: git remote set-url origin https://github.com/<owner>/<repo>.git',
55
+ ].join(' '),
56
+ );
57
+ }
58
+
59
+ const badTrackedFiles = checkTrackedFiles();
60
+ if (badTrackedFiles.length > 0) {
61
+ failures.push(
62
+ `forbidden files are tracked by git: ${badTrackedFiles.join(', ')}`,
63
+ );
64
+ }
65
+
66
+ if (failures.length > 0) {
67
+ console.error('❌ Repo hygiene check failed:');
68
+ failures.forEach((failure) => console.error(` - ${failure}`));
69
+ process.exit(1);
70
+ }
71
+
72
+ console.log('✅ Repo hygiene check passed');
73
+ }
74
+
75
+ main();
@@ -11,7 +11,9 @@ const NC = '\x1b[0m';
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
- let skillCount = 60;
14
+ const { execSync, execFileSync } = require('child_process');
15
+
16
+ let skillCount = 68;
15
17
  try {
16
18
  const skillsDir = path.join(__dirname, '..', 'skills');
17
19
  if (fs.existsSync(skillsDir)) {
@@ -212,7 +214,6 @@ const showSkillGuide = (choice) => {
212
214
  }
213
215
 
214
216
  console.log('');
215
- console.log(`${W}${BOLD}${isVi ? 'Nhấn ENTER để quay lại...' : 'Press ENTER to go back...'}${NC}`);
216
217
  };
217
218
 
218
219
  const printMenu = () => {
@@ -242,20 +243,11 @@ const printMenu = () => {
242
243
  console.log(` ${C}⚙️ Operations${NC} : Triển khai an toàn & Quản lý CI/CD`);
243
244
  console.log(` ${C}📈 Growth${NC} : Tối ưu chuyển đổi (CRO) & Tracking`);
244
245
  console.log('');
245
- console.log(` ${W}${BOLD}💡 Nhập số (1-12) để xem hướng dẫn & ví dụ:${NC}`);
246
+ console.log(` ${W}${BOLD}🌟 Lệnh phổ biến:${NC}`);
246
247
  console.log('');
247
- console.log(` 1. ${Y}Cách CodyMaster vận hành ${NC} → cm-how-it-work`);
248
- console.log(` 2. ${Y}Vibe coding (Zero code) ${NC} → cm-start`);
249
- console.log(` 3. ${Y}Tham gia dự án có sẵn ${NC} → cm-brainstorm-idea`);
250
- console.log(` 4. ${Y}Code giao diện từ URL/Ảnh ${NC} → cm-ux-master`);
251
- console.log(` 5. ${Y}Lập trình TDD & Pair code ${NC} → cm-tdd`);
252
- console.log(` 6. ${Y}Dọn dẹp & Tái cấu trúc ${NC} → cm-clean-code`);
253
- console.log(` 7. ${Y}Quét & Sửa lỗi bảo mật ${NC} → cm-security-gate`);
254
- console.log(` 8. ${Y}Viết tài liệu Docs & API ${NC} → cm-dockit`);
255
- console.log(` 9. ${Y}Tạo Landing Page "WOW" ${NC} → cm-cro-methodology`);
256
- console.log(` 10. ${Y}Bảng theo dõi tiến độ ${NC} → cm dashboard`);
257
- console.log(` 11. ${Y}Xem Demo tự động ${NC} → /cm:demo`);
258
- console.log(` 12. ${Y}Trợ giúp & Cú pháp lệnh ${NC} → cm help`);
248
+ console.log(` 🔸 ${Y}Bảng tiến độ (Dashboard) ${NC} → cm dashboard`);
249
+ console.log(` 🔸 ${Y}Cần trợ giúp / Tìm kỹ năng ${NC} → cm help`);
250
+ console.log(` 🔸 ${Y}Cách CodyMaster hoạt động ${NC} → cm-how-it-work`);
259
251
  } else {
260
252
  console.log(` ${C}🎯 Orchestration${NC} : Task Planning & Agent Synergy`);
261
253
  console.log(` ${C}🎨 Product${NC} : UX/UI Mastery & User Psychology`);
@@ -264,20 +256,11 @@ const printMenu = () => {
264
256
  console.log(` ${C}⚙️ Operations${NC} : Safe Deployments & CI/CD Excellence`);
265
257
  console.log(` ${C}📈 Growth${NC} : Conversion Tracking & Hacks`);
266
258
  console.log('');
267
- console.log(` ${W}${BOLD}💡 Type a number (1-12) for guide & examples:${NC}`);
259
+ console.log(` ${W}${BOLD}🌟 Popular Commands:${NC}`);
268
260
  console.log('');
269
- console.log(` 1. ${Y}The ultimate guide ${NC} → cm-how-it-work`);
270
- console.log(` 2. ${Y}Vibe coding (Zero code) ${NC} → cm-start`);
271
- console.log(` 3. ${Y}Code an existing project ${NC} → cm-brainstorm-idea`);
272
- console.log(` 4. ${Y}Generate UX/UI designs ${NC} → cm-ux-master`);
273
- console.log(` 5. ${Y}Code TDD & Pair coding ${NC} → cm-tdd`);
274
- console.log(` 6. ${Y}Clean & Refactor codebase ${NC} → cm-clean-code`);
275
- console.log(` 7. ${Y}Scan for vulnerabilities ${NC} → cm-security-gate`);
276
- console.log(` 8. ${Y}Write docs & generate APIs ${NC} → cm-dockit`);
277
- console.log(` 9. ${Y}Release WOW landing page ${NC} → cm-cro-methodology`);
278
- console.log(` 10. ${Y}Open progress dashboard ${NC} → cm dashboard`);
279
- console.log(` 11. ${Y}See an interactive demo ${NC} → /cm:demo`);
280
- console.log(` 12. ${Y}Help & Command list ${NC} → cm help`);
261
+ console.log(` 🔸 ${Y}Open progress dashboard ${NC} → cm dashboard`);
262
+ console.log(` 🔸 ${Y}Help & Command list ${NC} → cm help`);
263
+ console.log(` 🔸 ${Y}The ultimate guide ${NC} → cm-how-it-work`);
281
264
  }
282
265
 
283
266
  console.log('');
@@ -285,7 +268,30 @@ const printMenu = () => {
285
268
  console.log('');
286
269
  };
287
270
 
271
+ const isInstalledAsNpmDependency = () => {
272
+ const root = path.resolve(__dirname, '..').replace(/\\/g, '/');
273
+ return /[/\\]node_modules[/\\]codymaster$/i.test(root);
274
+ };
275
+
276
+ const npmCmd = () => (process.platform === 'win32' ? 'npm.cmd' : 'npm');
277
+
278
+ const activateCli = () => {
279
+ const pkgRoot = path.join(__dirname, '..');
280
+
281
+ if (isInstalledAsNpmDependency()) {
282
+ console.log(` ${C}CodyMaster CLI (per-project install — official path):${NC}`);
283
+ console.log(` ${W}npx cm${NC} or ${W}npx codymaster${NC}`);
284
+ console.log(
285
+ ` ${DIM}Optional — bare ${W}cm${DIM} in any terminal: ${W}npm install -g codymaster${NC}`,
286
+ );
287
+ return;
288
+ }
289
+
290
+
291
+ };
292
+
288
293
  const main = () => {
294
+ activateCli();
289
295
  printMenu();
290
296
  };
291
297
 
@@ -6,7 +6,7 @@ const DANGEROUS_PATTERNS = [
6
6
  { name: 'Anon Key Variable', regex: /ANON_KEY\s*[=:]\s*['\"][a-zA-Z0-9._\/-]{20,}/g },
7
7
  { name: 'Private Key Block', regex: /-----BEGIN\s+(RSA|EC|DSA|OPENSSH)?\s*PRIVATE KEY-----/g },
8
8
  { name: 'JWT Token', regex: /eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
9
- { name: 'Generic API Key', regex: /(?:api[_-]?key|api[_-]?secret|access[_-]?token)\s*[=:]\s*['\"][a-zA-Z0-9\/+=]{20,}['\"/]/gi },
9
+ { name: 'Generic API Key', regex: /(?:api[_-]?key|api[_-]?secret|access[_-]?token)\s*[=:]\s*['\"][a-zA-Z0-9\/+=]{20,}['\"]/gi },
10
10
  { name: 'AWS Key', regex: /AKIA[0-9A-Z]{16}/g },
11
11
  { name: 'Slack Token', regex: /xox[baprs]-[0-9a-zA-Z-]{10,}/g },
12
12
  { name: 'GitHub Token', regex: /gh[ps]_[a-zA-Z0-9]{36,}/g },
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Ensures each skill folder under skills/ has SKILL.md with a title line.
4
+ */
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const root = path.join(__dirname, '..', 'skills');
11
+
12
+ /** Directories under skills/ that are not standalone skills (assets, packs). */
13
+ const SKIP_DIRS = new Set(['profiles', 'extensions', 'scripts']);
14
+
15
+ let errors = 0;
16
+ if (!fs.existsSync(root)) {
17
+ console.error('No skills/ directory');
18
+ process.exit(1);
19
+ }
20
+
21
+ for (const name of fs.readdirSync(root, { withFileTypes: true })) {
22
+ if (!name.isDirectory()) continue;
23
+ if (name.name.startsWith('_') || name.name.startsWith('.')) continue;
24
+ if (SKIP_DIRS.has(name.name)) continue;
25
+ const md = path.join(root, name.name, 'SKILL.md');
26
+ if (!fs.existsSync(md)) {
27
+ console.error(`Missing SKILL.md: ${name.name}`);
28
+ errors++;
29
+ continue;
30
+ }
31
+ const text = fs.readFileSync(md, 'utf8');
32
+ if (!/^#\s+\S/m.test(text)) {
33
+ console.error(`SKILL.md missing H1 title: ${name.name}`);
34
+ errors++;
35
+ }
36
+ }
37
+
38
+ if (errors) {
39
+ console.error(`validate-skills: ${errors} error(s)`);
40
+ process.exit(1);
41
+ }
42
+ console.log('validate-skills: OK');