@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/CHANGELOG.md +96 -22
- package/README.md +67 -57
- package/gemini-extension.json +6 -6
- package/package.json +50 -45
- package/scripts/init.js +207 -147
- package/scripts/lib.js +66 -3
- package/scripts/postinstall.js +42 -42
- package/scripts/preuninstall.js +17 -17
- package/skills/mkt-agentic-sdlc/ENFORCEMENT.md +116 -82
- package/skills/mkt-agentic-sdlc/SKILL.md +236 -201
- package/skills/mkt-agentic-sdlc/dispatch.md +87 -0
- package/skills/mkt-agentic-sdlc/elicitation.md +147 -89
- package/skills/mkt-agentic-sdlc/frameworks.md +105 -105
- package/skills/mkt-agentic-sdlc/guides.md +287 -0
- package/skills/mkt-agentic-sdlc/research.md +110 -104
- package/skills/mkt-agentic-sdlc/review.md +180 -73
- package/skills/mkt-agentic-sdlc/routing.md +100 -0
- package/skills/mkt-agentic-sdlc/scripts/mkt_check.py +703 -567
- package/skills/mkt-agentic-sdlc/scripts/sdlc_core.py +1996 -0
- package/skills/mkt-agentic-sdlc/templates.md +616 -444
- package/skills/mkt-agentic-sdlc/vision.md +245 -0
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
+
|
package/scripts/postinstall.js
CHANGED
|
@@ -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');
|
package/scripts/preuninstall.js
CHANGED
|
@@ -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);
|