@antoneeo/mkt-agentic-sdlc-skill 0.2.1 → 0.3.1

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/scripts/init.js CHANGED
@@ -1,147 +1,207 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { execSync } = require('child_process');
6
- const { SKILL_SOURCE, CLIENTS, clientDetected, loadTemplates, templateFor } = require('./lib');
7
-
8
- const cwd = process.cwd();
9
-
10
- // 1. Directory layout (canonical mkt_docs structure)
11
- const directories = [
12
- 'mkt_docs',
13
- 'mkt_docs/vision',
14
- 'mkt_docs/research',
15
- 'mkt_docs/strategy',
16
- 'mkt_docs/tactics',
17
- 'mkt_docs/deliverables',
18
- 'mkt_docs/audit',
19
- 'mkt_docs/spikes',
20
- ].map((d) => path.join(cwd, d));
21
-
22
- // 2. Project protocol (thin pointer — the operating contract is the skill).
23
- // Deliberately short: duplicating the skill's rules here made them drift.
24
- const protocolContent = `# Marketing Agentic SDLC — Project Protocol (pointer)
25
-
26
- This project follows the Marketing Agentic SDLC Evidence-First process. The full
27
- operating contract is the \`mkt-agentic-sdlc\` skill (installed in your agent's
28
- skills directory); this file is only the minimal always-on pointer.
29
-
30
- ## Rule Zero — Triage every request
31
- - E1 Quick: single question or asset feedback (positioning opinion, copy critique). Answer directly; numbers still need a source.
32
- - E2 Campaign: single-campaign or single-channel plan. Light path: mini-brief + targeted research + campaign document.
33
- - E3 Full Plan: complete marketing plan. Full SOSTAC workflow via the skill: Discovery -> Research -> Situation -> Objectives -> Strategy -> Tactics -> Action -> Control -> Packaging.
34
- - Research Spike: time-boxed market question; outcome in \`mkt_docs/spikes/RESEARCH_[topic].md\`.
35
- - When in doubt, pick the higher level. Declare the chosen level when starting.
36
-
37
- ## The three non-negotiables
38
- 1. Evidence ledger: every market number carries an [EV-nn] reference (FACT / BENCHMARK / ASSUMPTION). No invented numbers, ever.
39
- 2. Ask only what the user owns: facts come from the user, analysis comes from research. Never ask the user to do the analysis.
40
- 3. Internal consistency: budget sums, funnel math and the objective->tactic->KPI chain are validated mechanically before delivery.
41
-
42
- ## Where things live
43
- - Vision (gate for E3): \`mkt_docs/vision/MKT_VISION.md\` — \`Status: DRAFT\` informs, \`Status: APPROVED\` binds.
44
- - Evidence: \`mkt_docs/research/evidence_ledger.md\` (the single source for every number).
45
- - Strategy artifacts: \`mkt_docs/strategy/\`; tactical artifacts: \`mkt_docs/tactics/\`; final deliverables: \`mkt_docs/deliverables/\`.
46
- - Must-reads: \`mkt_docs/README.md\`; full generated manifest: \`mkt_docs/INDEX.md\`.
47
- - If devPNT is available for this project, its M-VISION / plans / governed artifacts take over (Hybrid mode — see the skill).
48
-
49
- ## Closure gate
50
- Deliverables ship only after \`python <skill_dir>/scripts/mkt_check.py check\` is
51
- CLEAN (where the validator is adopted) and the CMO review passed.
52
-
53
- If the mkt-agentic-sdlc skill is not available in this client, ask the user to install it:
54
- \`npm i -g @antoneeo/mkt-agentic-sdlc-skill\` (the skill installs automatically;
55
- if npm blocks scripts, run \`npx mkt-sdlc-install-skill\`).
56
- `;
57
-
58
- console.log('🚀 Initializing Marketing Agentic SDLC workflow...');
59
-
60
- // 3. Load templates from the single source (skill's templates.md)
61
- let sections;
62
- try {
63
- sections = loadTemplates();
64
- } catch (err) {
65
- console.error(`❌ Cannot load templates: ${err.message}`);
66
- process.exit(1);
67
- }
68
-
69
- let seedFiles;
70
- try {
71
- seedFiles = [
72
- ['mkt_docs/README.md', templateFor(sections, 'mkt_docs/README.md')],
73
- ['mkt_docs/vision/MKT_VISION.md', templateFor(sections, 'MKT_VISION.md')],
74
- ['mkt_docs/vision/principles.md', templateFor(sections, 'principles.md')],
75
- ['mkt_docs/research/evidence_ledger.md', templateFor(sections, 'evidence_ledger.md')],
76
- // NOTE: INDEX.md is NOT seeded — it is generated by `mkt_check.py index`
77
- // and would immediately fail validate.
78
- ];
79
- } catch (err) {
80
- console.error(`❌ ${err.message}`);
81
- process.exit(1);
82
- }
83
-
84
- // 4. Create directories
85
- directories.forEach((dir) => {
86
- if (!fs.existsSync(dir)) {
87
- fs.mkdirSync(dir, { recursive: true });
88
- console.log(`📁 Created directory: ${path.relative(cwd, dir)}`);
89
- }
90
- });
91
-
92
- // 5. Write seed files (never overwrite)
93
- const writeIfNotExists = (relPath, content, description) => {
94
- const filePath = path.join(cwd, relPath);
95
- if (!fs.existsSync(filePath)) {
96
- fs.writeFileSync(filePath, content, 'utf8');
97
- console.log(`📄 Created file: ${relPath}${description ? ` (${description})` : ''}`);
98
- return true;
99
- }
100
- console.log(`⏭️ Skipped: ${relPath} already exists.`);
101
- return false;
102
- };
103
-
104
- seedFiles.forEach(([relPath, content]) => writeIfNotExists(relPath, content));
105
-
106
- // 6. Client discovery and protocol pointers
107
- console.log('\n--- Environment Analysis ---');
108
-
109
- const protocolFiles = {
110
- claude: 'CLAUDE.md',
111
- gemini: 'GEMINI.md',
112
- codex: 'AGENTS.md',
113
- antigravity: 'AGENTS.md', // Antigravity CLI reads AGENTS.md; single protocolContent reused.
114
- };
115
-
116
- for (const client of CLIENTS) {
117
- if (clientDetected(client)) {
118
- console.log(`✅ ${client.label} detected.`);
119
- writeIfNotExists(protocolFiles[client.key], protocolContent, `${client.label} protocol pointer`);
120
- }
121
- }
122
-
123
- // Cursor/Windsurf (always recommended)
124
- writeIfNotExists('.cursorrules', protocolContent, 'Cursor/Windsurf rules');
125
-
126
- // 7. Generate mkt_docs/INDEX.md so the very first `validate` is already clean.
127
- // The manifest is generated, never seeded: delegate to the validator if Python is available.
128
- const validator = path.join(SKILL_SOURCE, 'scripts', 'mkt_check.py');
129
- let indexed = false;
130
- for (const py of ['python', 'python3', 'py']) {
131
- try {
132
- execSync(`${py} "${validator}" index --root "${cwd}"`, { stdio: 'ignore' });
133
- console.log('📇 Generated mkt_docs/INDEX.md (document manifest).');
134
- indexed = true;
135
- break;
136
- } catch (e) { /* try the next interpreter */ }
137
- }
138
- if (!indexed) {
139
- console.log('ℹ️ Python not found: generate the manifest later with '
140
- + '"python <skill_dir>/scripts/mkt_check.py index" (validate reports it until then).');
141
- }
142
-
143
- console.log('\n✅ Setup completed successfully!');
144
- console.log('💡 Next steps:');
145
- console.log(' 1. Make sure the mkt-agentic-sdlc skill is installed (mkt-sdlc-install-skill).');
146
- console.log(' 2. Restart/open the project in your AI client so it reads the protocol pointer.');
147
- console.log(' 3. Start an E3 engagement: the skill runs Discovery (its questions) before anything else.');
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+ const { SKILL_SOURCE, INSTALLED_SKILL_NAME, SELF_LENS, SIBLING_LENSES, CLIENTS, clientDetected, skillTarget, loadTemplates, templateFor } = require('./lib');
7
+
8
+ const cwd = process.cwd();
9
+
10
+ // 1. Directory layout (canonical mkt_docs structure)
11
+ const directories = [
12
+ 'mkt_docs',
13
+ 'mkt_docs/vision',
14
+ 'mkt_docs/research',
15
+ 'mkt_docs/strategy',
16
+ 'mkt_docs/tactics',
17
+ 'mkt_docs/deliverables',
18
+ 'mkt_docs/audit',
19
+ 'mkt_docs/spikes',
20
+ ].map((d) => path.join(cwd, d));
21
+
22
+ // 2. Project protocol (thin pointer — the operating contract is the skill).
23
+ // Deliberately short: duplicating the skill's rules here made them drift.
24
+ const protocolContent = `# Marketing Agentic SDLC — Project Protocol (pointer)
25
+
26
+ This project follows the Marketing Agentic SDLC Evidence-First process. The full
27
+ operating contract is the \`mkt-agentic-sdlc\` skill (installed in your agent's
28
+ skills directory); this file is only the minimal always-on pointer.
29
+
30
+ ## Rule Zero — Triage every request
31
+ - E1 Quick: single question or asset feedback (positioning opinion, copy critique). Answer directly; numbers still need a source.
32
+ - E2 Campaign: single-campaign or single-channel plan. Light path: mini-brief + targeted research + campaign document.
33
+ - E3 Full Plan: complete marketing plan. Full SOSTAC workflow via the skill: Discovery -> Research -> Situation -> Objectives -> Strategy -> Tactics -> Action -> Control -> Packaging.
34
+ - Research Spike: time-boxed market question; outcome in \`mkt_docs/spikes/RESEARCH_[topic].md\`.
35
+ - When in doubt, pick the higher level. Declare the chosen level when starting.
36
+
37
+ ## The three non-negotiables
38
+ 1. Evidence ledger: every market number carries an [EV-nn] reference (FACT / BENCHMARK / ASSUMPTION). No invented numbers, ever.
39
+ 2. Ask only what the user owns: facts come from the user, analysis comes from research. Never ask the user to do the analysis.
40
+ 3. Internal consistency: budget sums, funnel math and the objective->tactic->KPI chain are validated mechanically before delivery.
41
+
42
+ ## Where things live
43
+ - Vision (gate for E3): \`mkt_docs/vision/MKT_VISION.md\` — \`Status: DRAFT\` informs, \`Status: APPROVED\` binds.
44
+ - Evidence: \`mkt_docs/research/evidence_ledger.md\` (the single source for every number).
45
+ - Strategy artifacts: \`mkt_docs/strategy/\`; tactical artifacts: \`mkt_docs/tactics/\`; final deliverables: \`mkt_docs/deliverables/\`.
46
+ - Must-reads: \`mkt_docs/README.md\`; full generated manifest: \`mkt_docs/INDEX.md\`.
47
+ - If devPNT is available for this project, its M-VISION / plans / governed artifacts take over (Hybrid mode — see the skill).
48
+
49
+ ## Closure gate
50
+ Deliverables ship only after \`python <skill_dir>/scripts/mkt_check.py check\` is
51
+ CLEAN (where the validator is adopted) and the CMO review passed.
52
+
53
+ If the mkt-agentic-sdlc skill is not available in this client, ask the user to install it:
54
+ \`npm i -g @antoneeo/mkt-agentic-sdlc-skill\` (the skill installs automatically;
55
+ if npm blocks scripts, run \`npx mkt-sdlc-install-skill\`).
56
+ `;
57
+
58
+ console.log('🚀 Initializing Marketing Agentic SDLC workflow...');
59
+
60
+ // 3. Load templates from the single source (skill's templates.md)
61
+ let sections;
62
+ try {
63
+ sections = loadTemplates();
64
+ } catch (err) {
65
+ console.error(`❌ Cannot load templates: ${err.message}`);
66
+ process.exit(1);
67
+ }
68
+
69
+ let seedFiles;
70
+ try {
71
+ seedFiles = [
72
+ ['mkt_docs/README.md', templateFor(sections, 'mkt_docs/README.md')],
73
+ ['mkt_docs/vision/MKT_VISION.md', templateFor(sections, 'MKT_VISION.md')],
74
+ ['mkt_docs/vision/principles.md', templateFor(sections, 'principles.md')],
75
+ ['mkt_docs/research/evidence_ledger.md', templateFor(sections, 'evidence_ledger.md')],
76
+ // NOTE: INDEX.md is NOT seeded — it is generated by `mkt_check.py index`
77
+ // and would immediately fail validate.
78
+ ];
79
+ } catch (err) {
80
+ console.error(`❌ ${err.message}`);
81
+ process.exit(1);
82
+ }
83
+
84
+ // 4. Create directories
85
+ directories.forEach((dir) => {
86
+ if (!fs.existsSync(dir)) {
87
+ fs.mkdirSync(dir, { recursive: true });
88
+ console.log(`📁 Created directory: ${path.relative(cwd, dir)}`);
89
+ }
90
+ });
91
+
92
+ // 5. Write seed files (never overwrite)
93
+ const writeIfNotExists = (relPath, content, description) => {
94
+ const filePath = path.join(cwd, relPath);
95
+ if (!fs.existsSync(filePath)) {
96
+ fs.writeFileSync(filePath, content, 'utf8');
97
+ console.log(`📄 Created file: ${relPath}${description ? ` (${description})` : ''}`);
98
+ return true;
99
+ }
100
+ console.log(`⏭️ Skipped: ${relPath} already exists.`);
101
+ return false;
102
+ };
103
+
104
+ seedFiles.forEach(([relPath, content]) => writeIfNotExists(relPath, content));
105
+
106
+ // 6. Client discovery and protocol pointers
107
+ console.log('\n--- Environment Analysis ---');
108
+
109
+ const protocolFiles = {
110
+ claude: 'CLAUDE.md',
111
+ gemini: 'GEMINI.md',
112
+ codex: 'AGENTS.md',
113
+ antigravity: 'AGENTS.md', // Antigravity CLI reads AGENTS.md; single protocolContent reused.
114
+ };
115
+
116
+ // A protocol pointer that ALREADY existed was written by someone else — typically a
117
+ // sibling lens's init, which seeded the project with its own triage ladder. This init
118
+ // must not overwrite it (create-only is the T1 guarantee), so the second ladder is
119
+ // written aside and merged by hand.
120
+ let protocolPreexisting = false;
121
+
122
+ for (const client of CLIENTS) {
123
+ if (clientDetected(client)) {
124
+ console.log(`✅ ${client.label} detected.`);
125
+ const created = writeIfNotExists(protocolFiles[client.key], protocolContent, `${client.label} protocol pointer`);
126
+ if (!created) protocolPreexisting = true;
127
+ }
128
+ }
129
+
130
+ // Cursor/Windsurf (always recommended)
131
+ writeIfNotExists('.cursorrules', protocolContent, 'Cursor/Windsurf rules');
132
+
133
+ // 6b. Sibling lenses: additive only. Never edits a user-authored root file.
134
+ function installedSiblingLenses() {
135
+ const found = new Map();
136
+ for (const client of CLIENTS) {
137
+ if (!clientDetected(client)) continue;
138
+ const skillsDir = path.dirname(skillTarget(client));
139
+ for (const [dirName, lens] of Object.entries(SIBLING_LENSES)) {
140
+ if (fs.existsSync(path.join(skillsDir, dirName))) found.set(dirName, lens);
141
+ }
142
+ }
143
+ return found;
144
+ }
145
+
146
+ const siblings = installedSiblingLenses();
147
+ if (siblings.size > 0) {
148
+ const list = [...siblings].map(([dir, lens]) => `- \`${dir}\` — the **${lens}** lens`).join('\n');
149
+ console.log(`\n🔀 Sibling lens detected: ${[...siblings.keys()].join(', ')}.`);
150
+ const wrote = writeIfNotExists('AGENTIC_MULTI_LENS.md', `# Multi-lens project — routing note (additive)
151
+
152
+ This project has more than one lens of the Agentic SDLC family installed:
153
+
154
+ ${list}
155
+ - \`${INSTALLED_SKILL_NAME}\` — the **${SELF_LENS}** lens
156
+
157
+ One \`ai_docs/\` tree, one project default (\`default_domain:\` in \`ai_docs/README.md\`),
158
+ one lens per unit of work. Before acting on any L2, L3 or Spike, run the domain router
159
+ in the skill's \`routing.md\`: it decides which lens's method and validation rules govern
160
+ that unit. L1 never reaches it. Never refer to a document whose meaning differs by lens
161
+ ("threat model", "vision", \`principles.md\`, \`handoff.md\`) by its bare name.
162
+
163
+ ${protocolPreexisting ? `**Merge step owed.** The always-on protocol pointer of this project (\`CLAUDE.md\` /
164
+ \`GEMINI.md\` / \`AGENTS.md\` / \`.cursorrules\`) was written by another lens's init and
165
+ carries ITS triage ladder. This init did not overwrite it. Add the marketing lens's ladder
166
+ to that file by hand — the pointer below — so both are always loaded.
167
+
168
+ ## Rule Zero — Triage every request (marketing lens)
169
+ - E1 Question: one answer from the existing plan, no document written.
170
+ - E2 Campaign: a single campaign or channel, on an approved strategy.
171
+ - E3 Full engagement: the nine-phase SOSTAC workflow, evidence ledger and all
172
+ three validator gates.
173
+ ` : `The always-on protocol pointer for the marketing lens was created by this init. When you
174
+ install another lens over this project, its init will leave its own ladder here for you
175
+ to merge.
176
+ `}
177
+ This file is NOT auto-loaded by any client: it is a note for you, deliberately additive.
178
+ Delete it once the merge is done.
179
+ `, 'multi-lens routing note');
180
+ if (wrote && protocolPreexisting) {
181
+ console.log(' ⚠️ A protocol pointer already existed and was NOT overwritten.');
182
+ console.log(` Merge the ${SELF_LENS}-lens ladder from AGENTIC_MULTI_LENS.md into it by hand.`);
183
+ }
184
+ }
185
+
186
+ // 7. Generate mkt_docs/INDEX.md so the very first `validate` is already clean.
187
+ // The manifest is generated, never seeded: delegate to the validator if Python is available.
188
+ const validator = path.join(SKILL_SOURCE, 'scripts', 'mkt_check.py');
189
+ let indexed = false;
190
+ for (const py of ['python', 'python3', 'py']) {
191
+ try {
192
+ execSync(`${py} "${validator}" index --root "${cwd}"`, { stdio: 'ignore' });
193
+ console.log('📇 Generated mkt_docs/INDEX.md (document manifest).');
194
+ indexed = true;
195
+ break;
196
+ } catch (e) { /* try the next interpreter */ }
197
+ }
198
+ if (!indexed) {
199
+ console.log('ℹ️ Python not found: generate the manifest later with '
200
+ + '"python <skill_dir>/scripts/mkt_check.py index" (validate reports it until then).');
201
+ }
202
+
203
+ console.log('\n✅ Setup completed successfully!');
204
+ console.log('💡 Next steps:');
205
+ console.log(' 1. Make sure the mkt-agentic-sdlc skill is installed (mkt-sdlc-install-skill).');
206
+ console.log(' 2. Restart/open the project in your AI client so it reads the protocol pointer.');
207
+ console.log(' 3. Start an E3 engagement: the skill runs Discovery (its questions) before anything else.');
package/scripts/lib.js CHANGED
@@ -10,7 +10,65 @@ const { execSync } = require('child_process');
10
10
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
11
11
  const SKILL_SOURCE = path.join(PACKAGE_ROOT, 'skills', 'mkt-agentic-sdlc');
12
12
  const TEMPLATES_PATH = path.join(SKILL_SOURCE, 'templates.md');
13
- const SKILL_DIR_NAME = 'mkt-agentic-sdlc';
13
+ // Kept for backward compatibility with this package's own scripts; the
14
+ // authoritative value is INSTALLED_SKILL_NAME, derived from SKILL.md below.
15
+ // The directory name each client loads the skill from. Derived from the manifest,
16
+ // never hard-coded by a consumer: three distributions share these scripts, and a
17
+ // literal here is how a copy-fork starts installing under its sibling's name.
18
+ const INSTALLED_SKILL_NAME = (() => {
19
+ const m = fs.readFileSync(path.join(SKILL_SOURCE, 'SKILL.md'), 'utf8').match(/^name:\s*(\S+)/m);
20
+ if (!m) throw new Error(`SKILL.md carries no 'name:' field: ${SKILL_SOURCE}`);
21
+ return m[1];
22
+ })();
23
+
24
+ // The family's lens table, read from the shared `routing.md` rather than restated
25
+ // here. Same reason as INSTALLED_SKILL_NAME above, and the same failure it prevents:
26
+ // BOTH the row for this lens and the rows for its siblings used to be literals copied
27
+ // between distributions, so kb and mkt wrote a multi-lens note announcing themselves
28
+ // as the code lens. routing.md is one of the byte-identical shared files, so this
29
+ // lookup cannot drift between distributions.
30
+ // Lazy on purpose: preuninstall.js needs INSTALLED_SKILL_NAME and nothing else, and
31
+ // must keep working on an installation damaged badly enough to have lost routing.md.
32
+ function lensTable() {
33
+ const routing = path.join(SKILL_SOURCE, 'routing.md');
34
+ if (!fs.existsSync(routing)) throw new Error(`routing.md missing from the skill: ${SKILL_SOURCE}`);
35
+ // Parsed as a table, not matched with a regex: a mis-escaped pattern matches the
36
+ // empty string and yields undefined instead of throwing, which is how this lookup
37
+ // failed the first time it was written.
38
+ const table = new Map();
39
+ for (const line of fs.readFileSync(routing, 'utf8').split(/\r?\n/)) {
40
+ const cells = line.split('|').map((cell) => cell.trim());
41
+ if (cells.length < 4) continue;
42
+ const [, lens, skill] = cells;
43
+ if (!/^[a-z]+$/.test(lens) || !/^`[a-z0-9-]+`$/.test(skill)) continue;
44
+ const name = skill.slice(1, -1);
45
+ // A duplicate row would otherwise be won silently by whichever came first, and a
46
+ // wrong routing.md propagates byte-identically to every distribution.
47
+ if (table.has(name)) throw new Error(`routing.md lists '${name}' more than once`);
48
+ table.set(name, lens);
49
+ }
50
+ if (!table.size) throw new Error(`routing.md carries no lens table: ${routing}`);
51
+ return table;
52
+ }
53
+
54
+ function selfLens() {
55
+ const lens = lensTable().get(INSTALLED_SKILL_NAME);
56
+ if (!lens) throw new Error(`routing.md has no lens row for '${INSTALLED_SKILL_NAME}'`);
57
+ return lens;
58
+ }
59
+
60
+ // Sibling lenses of the same family: one shared core, one docs tree, a different
61
+ // fidelity discipline each. Keyed by the installed skill directory name.
62
+ function siblingLenses() {
63
+ const table = lensTable();
64
+ selfLens(); // this lens must be in the table too
65
+ table.delete(INSTALLED_SKILL_NAME);
66
+ return Object.fromEntries(table);
67
+ }
68
+
69
+ // One entry per supported AI client. `home` may be overridden by an env var
70
+ // (Claude Desktop / portable installs); presence of the home dir counts as
71
+ // detection even when the CLI is not on PATH.
14
72
 
15
73
  // One entry per supported AI client. `home` may be overridden by an env var
16
74
  // (Claude Desktop / portable installs); presence of the home dir counts as
@@ -88,7 +146,7 @@ function skillTarget(client) {
88
146
  // (split on '/' to keep cross-platform path.join correctness). Entries
89
147
  // without it resolve to <home>/skills/mkt-agentic-sdlc exactly as before.
90
148
  const subdir = client.skillsSubdir ? client.skillsSubdir.split('/') : ['skills'];
91
- return path.join(client.home, ...subdir, SKILL_DIR_NAME);
149
+ return path.join(client.home, ...subdir, INSTALLED_SKILL_NAME);
92
150
  }
93
151
 
94
152
  function copyRecursive(src, dest) {
@@ -156,10 +214,10 @@ function templateFor(sections, needle, index = 0) {
156
214
  }
157
215
 
158
216
  module.exports = {
217
+ INSTALLED_SKILL_NAME,
159
218
  PACKAGE_ROOT,
160
219
  SKILL_SOURCE,
161
220
  TEMPLATES_PATH,
162
- SKILL_DIR_NAME,
163
221
  CLIENTS,
164
222
  commandExists,
165
223
  clientDetected,
@@ -168,3 +226,8 @@ module.exports = {
168
226
  loadTemplates,
169
227
  templateFor,
170
228
  };
229
+
230
+ // Lazy: reading routing.md is deferred to the consumer that actually asks.
231
+ Object.defineProperty(module.exports, 'SELF_LENS', { enumerable: true, get: selfLens });
232
+ Object.defineProperty(module.exports, 'SIBLING_LENSES', { enumerable: true, get: siblingLenses });
233
+
@@ -1,42 +1,42 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const { SKILL_SOURCE, CLIENTS, clientDetected, skillTarget, copyRecursive } = require('./lib');
5
-
6
- function installSkill(client) {
7
- if (!fs.existsSync(SKILL_SOURCE)) {
8
- console.log(`⚠️ Skill source not found at ${SKILL_SOURCE}; skipping ${client.label} install.`);
9
- return false;
10
- }
11
- const target = skillTarget(client);
12
- try {
13
- fs.mkdirSync(target, { recursive: true });
14
- copyRecursive(SKILL_SOURCE, target);
15
- console.log(`📦 Installed ${client.label} skill at: ${target}`);
16
- console.log(` ${client.reload}`);
17
- return true;
18
- } catch (err) {
19
- console.log(`⚠️ Failed to install ${client.label} skill: ${err.message}`);
20
- console.log(` Manual install: copy "${SKILL_SOURCE}" to "${target}".`);
21
- return false;
22
- }
23
- }
24
-
25
- console.log('\n--- Marketing Agentic SDLC Skill Discovery ---');
26
-
27
- let detected = false;
28
- for (const client of CLIENTS) {
29
- if (clientDetected(client)) {
30
- console.log(`✅ Detected: ${client.label}`);
31
- detected = true;
32
- installSkill(client);
33
- }
34
- }
35
-
36
- if (!detected) {
37
- console.log('ℹ️ No specific AI CLI detected globally, but you can still use the skill.');
38
- }
39
-
40
- console.log('\nTo initialize a project with the marketing protocol, run:');
41
- console.log('👉 npx mkt-sdlc-init');
42
- console.log('------------------------------------\n');
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const { SKILL_SOURCE, CLIENTS, clientDetected, skillTarget, copyRecursive } = require('./lib');
5
+
6
+ function installSkill(client) {
7
+ if (!fs.existsSync(SKILL_SOURCE)) {
8
+ console.log(`⚠️ Skill source not found at ${SKILL_SOURCE}; skipping ${client.label} install.`);
9
+ return false;
10
+ }
11
+ const target = skillTarget(client);
12
+ try {
13
+ fs.mkdirSync(target, { recursive: true });
14
+ copyRecursive(SKILL_SOURCE, target);
15
+ console.log(`📦 Installed ${client.label} skill at: ${target}`);
16
+ console.log(` ${client.reload}`);
17
+ return true;
18
+ } catch (err) {
19
+ console.log(`⚠️ Failed to install ${client.label} skill: ${err.message}`);
20
+ console.log(` Manual install: copy "${SKILL_SOURCE}" to "${target}".`);
21
+ return false;
22
+ }
23
+ }
24
+
25
+ console.log('\n--- Marketing Agentic SDLC Skill Discovery ---');
26
+
27
+ let detected = false;
28
+ for (const client of CLIENTS) {
29
+ if (clientDetected(client)) {
30
+ console.log(`✅ Detected: ${client.label}`);
31
+ detected = true;
32
+ installSkill(client);
33
+ }
34
+ }
35
+
36
+ if (!detected) {
37
+ console.log('ℹ️ No specific AI CLI detected globally, but you can still use the skill.');
38
+ }
39
+
40
+ console.log('\nTo initialize a project with the marketing protocol, run:');
41
+ console.log('👉 npx mkt-sdlc-init');
42
+ console.log('------------------------------------\n');
@@ -1,17 +1,17 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const { CLIENTS, skillTarget } = require('./lib');
5
-
6
- function removeSkill(client) {
7
- const target = skillTarget(client);
8
- if (!fs.existsSync(target)) return;
9
- try {
10
- fs.rmSync(target, { recursive: true, force: true });
11
- console.log(`🧹 Removed ${client.label} skill at: ${target}`);
12
- } catch (err) {
13
- console.log(`⚠️ Could not remove ${target}: ${err.message}`);
14
- }
15
- }
16
-
17
- CLIENTS.forEach(removeSkill);
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const { CLIENTS, skillTarget } = require('./lib');
5
+
6
+ function removeSkill(client) {
7
+ const target = skillTarget(client);
8
+ if (!fs.existsSync(target)) return;
9
+ try {
10
+ fs.rmSync(target, { recursive: true, force: true });
11
+ console.log(`🧹 Removed ${client.label} skill at: ${target}`);
12
+ } catch (err) {
13
+ console.log(`⚠️ Could not remove ${target}: ${err.message}`);
14
+ }
15
+ }
16
+
17
+ CLIENTS.forEach(removeSkill);