fdeops 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +18 -0
- package/CLAUDE.md.template +25 -0
- package/LICENSE +21 -0
- package/README.md +328 -0
- package/adapters/AGENTS.md +24 -0
- package/adapters/GEMINI.md +24 -0
- package/adapters/README.md +31 -0
- package/adapters/copilot-instructions.md +24 -0
- package/adapters/cursor.fde.mdc +29 -0
- package/bin/check.js +260 -0
- package/bin/fde.js +690 -0
- package/bin/install.js +220 -0
- package/hooks/hooks.json +40 -0
- package/hooks/pre-compact +66 -0
- package/hooks/run-hook.cmd +3 -0
- package/hooks/session-start +91 -0
- package/hooks/session-stop +86 -0
- package/package.json +52 -0
- package/skills/fde/SKILL.md +219 -0
- package/skills/fde/references/ai.md +91 -0
- package/skills/fde/references/artifacts.md +247 -0
- package/skills/fde/references/assumption-audit.md +77 -0
- package/skills/fde/references/audit.md +61 -0
- package/skills/fde/references/blast-radius.md +91 -0
- package/skills/fde/references/build.md +98 -0
- package/skills/fde/references/business-case.md +78 -0
- package/skills/fde/references/close.md +43 -0
- package/skills/fde/references/dashboard.md +40 -0
- package/skills/fde/references/debrief.md +36 -0
- package/skills/fde/references/debug.md +55 -0
- package/skills/fde/references/demo-prep.md +31 -0
- package/skills/fde/references/discover.md +163 -0
- package/skills/fde/references/exec-narrative.md +108 -0
- package/skills/fde/references/fintech.md +48 -0
- package/skills/fde/references/gov.md +47 -0
- package/skills/fde/references/handoff-engineering.md +139 -0
- package/skills/fde/references/healthcare.md +45 -0
- package/skills/fde/references/incremental-build.md +91 -0
- package/skills/fde/references/initiative-triage.md +78 -0
- package/skills/fde/references/land.md +75 -0
- package/skills/fde/references/multi-customer-ops.md +114 -0
- package/skills/fde/references/observability.md +103 -0
- package/skills/fde/references/options-analysis.md +81 -0
- package/skills/fde/references/pattern-extract.md +93 -0
- package/skills/fde/references/plan.md +108 -0
- package/skills/fde/references/qa-live.md +113 -0
- package/skills/fde/references/rescue.md +81 -0
- package/skills/fde/references/review.md +53 -0
- package/skills/fde/references/rollback-drill.md +102 -0
- package/skills/fde/references/scope-defense.md +71 -0
- package/skills/fde/references/security-audit.md +105 -0
- package/skills/fde/references/ship.md +121 -0
- package/skills/fde/references/sketch.md +40 -0
- package/skills/fde/references/stakeholder-radar.md +68 -0
- package/skills/fde/references/status.md +30 -0
- package/skills/fde/references/test-on-legacy.md +108 -0
- package/skills/fde/references/trust-engineering.md +100 -0
- package/skills/fde/references/use-case-scoring.md +70 -0
- package/templates/.fde/README.md +13 -0
- package/templates/.fde/brief.md +8 -0
- package/templates/.fde/context.md +14 -0
- package/templates/.fde/decisions.md +18 -0
- package/templates/.fde/delivery.md +7 -0
- package/templates/.fde/reality.md +7 -0
- package/templates/.fde/retrospectives/.gitkeep +0 -0
- package/templates/.fde/risks.md +5 -0
- package/templates/.fde/stakeholders.md +10 -0
- package/templates/.fde/success.md +7 -0
- package/templates/.fde/terrain.md +7 -0
- package/templates/.fde/trust-profile.md +11 -0
package/bin/check.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Operational smoke checks before publish. Run: npm run check
|
|
4
|
+
*/
|
|
5
|
+
const fs = require('fs')
|
|
6
|
+
const path = require('path')
|
|
7
|
+
|
|
8
|
+
const root = path.join(__dirname, '..')
|
|
9
|
+
let failed = 0
|
|
10
|
+
|
|
11
|
+
function fail(msg) {
|
|
12
|
+
console.error('FAIL:', msg)
|
|
13
|
+
failed = 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function ok(msg) {
|
|
17
|
+
console.log('OK:', msg)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function read(rel) {
|
|
21
|
+
return fs.readFileSync(path.join(root, rel), 'utf8')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const requiredTemplates = [
|
|
25
|
+
'context.md',
|
|
26
|
+
'brief.md',
|
|
27
|
+
'success.md',
|
|
28
|
+
'stakeholders.md',
|
|
29
|
+
'trust-profile.md',
|
|
30
|
+
'reality.md',
|
|
31
|
+
'terrain.md',
|
|
32
|
+
'decisions.md',
|
|
33
|
+
'risks.md',
|
|
34
|
+
'delivery.md',
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
for (const f of requiredTemplates) {
|
|
38
|
+
const p = path.join(root, 'templates', '.fde', f)
|
|
39
|
+
if (!fs.existsSync(p)) fail(`missing template templates/.fde/${f}`)
|
|
40
|
+
else ok(`template ${f}`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const gif = path.join(root, 'media', 'demo.gif')
|
|
44
|
+
if (!fs.existsSync(gif) || fs.statSync(gif).size < 1000) {
|
|
45
|
+
fail('media/demo.gif missing or too small')
|
|
46
|
+
} else {
|
|
47
|
+
ok('media/demo.gif')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const dir of fs.readdirSync(path.join(root, 'skills'))) {
|
|
51
|
+
const skill = path.join(root, 'skills', dir, 'SKILL.md')
|
|
52
|
+
if (!fs.existsSync(skill)) continue
|
|
53
|
+
const body = fs.readFileSync(skill, 'utf8')
|
|
54
|
+
if (!body.includes('## Purpose')) fail(`${dir}/SKILL.md missing ## Purpose`)
|
|
55
|
+
if (!body.includes('## Principles')) fail(`${dir}/SKILL.md missing ## Principles`)
|
|
56
|
+
}
|
|
57
|
+
ok('skills structure')
|
|
58
|
+
|
|
59
|
+
// v3: one skill + phase references (progressive disclosure)
|
|
60
|
+
const requiredReferences = [
|
|
61
|
+
'land.md', 'discover.md', 'audit.md', 'plan.md', 'build.md', 'review.md',
|
|
62
|
+
'debug.md', 'rescue.md', 'ship.md', 'sketch.md', 'close.md', 'dashboard.md',
|
|
63
|
+
'debrief.md', 'status.md', 'demo-prep.md',
|
|
64
|
+
'healthcare.md', 'fintech.md', 'gov.md',
|
|
65
|
+
]
|
|
66
|
+
for (const f of requiredReferences) {
|
|
67
|
+
const p = path.join(root, 'skills', 'fde', 'references', f)
|
|
68
|
+
if (!fs.existsSync(p)) {
|
|
69
|
+
fail(`missing phase reference skills/fde/references/${f}`)
|
|
70
|
+
} else {
|
|
71
|
+
const body = fs.readFileSync(p, 'utf8')
|
|
72
|
+
if (!/## Principles/.test(body)) fail(`references/${f} missing ## Principles`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
ok('phase references')
|
|
76
|
+
|
|
77
|
+
const router = read('skills/fde/SKILL.md')
|
|
78
|
+
if (!router.includes('memory contract')) fail('SKILL.md must define the memory contract')
|
|
79
|
+
// every references/<name>.md the router mentions must exist
|
|
80
|
+
const mentioned = [...new Set([...router.matchAll(/references\/([a-z-]+\.md)/g)].map(m => m[1]))]
|
|
81
|
+
if (mentioned.length === 0) fail('SKILL.md must dispatch to references/')
|
|
82
|
+
for (const refFile of mentioned) {
|
|
83
|
+
if (!fs.existsSync(path.join(root, 'skills', 'fde', 'references', refFile))) {
|
|
84
|
+
fail(`SKILL.md routes to references/${refFile} which does not exist`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
ok(`router dispatch (${mentioned.length} reference targets verified) + memory contract`)
|
|
88
|
+
|
|
89
|
+
const install = read('bin/install.js')
|
|
90
|
+
if (install.includes('scaffoldFdeInProject(process.cwd())')) {
|
|
91
|
+
fail('install.js must not auto-scaffold .fde in customer cwd')
|
|
92
|
+
} else if (!install.includes("arg === 'init'")) {
|
|
93
|
+
fail('install.js must support: npx fdeops init <name>')
|
|
94
|
+
} else {
|
|
95
|
+
ok('install.js engagement model')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (read('package.json').includes('postinstall')) {
|
|
99
|
+
fail('package.json must not auto-run install on npm postinstall')
|
|
100
|
+
} else {
|
|
101
|
+
ok('no surprise postinstall')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const readme = read('README.md')
|
|
105
|
+
if (readme.includes('demo.gif')) fail('README must not embed demo.gif (removed for clarity)')
|
|
106
|
+
else ok('README no demo gif')
|
|
107
|
+
|
|
108
|
+
for (const section of [
|
|
109
|
+
'How it works',
|
|
110
|
+
'Quickstart',
|
|
111
|
+
'The basic workflow',
|
|
112
|
+
"What's inside",
|
|
113
|
+
'Engagement memory',
|
|
114
|
+
'Who this is for',
|
|
115
|
+
'Who does what',
|
|
116
|
+
'Without fdeops vs with fdeops',
|
|
117
|
+
'Principles',
|
|
118
|
+
]) {
|
|
119
|
+
if (!readme.includes(section)) fail(`README missing section: ${section}`)
|
|
120
|
+
}
|
|
121
|
+
if (!readme.includes('AI coding agent')) {
|
|
122
|
+
fail('README must say AI coding agent (not ambiguous "agent")')
|
|
123
|
+
}
|
|
124
|
+
if (!/agent.*always means the AI|“agent” always means/i.test(readme)) {
|
|
125
|
+
fail('README must define that agent means AI, not human')
|
|
126
|
+
}
|
|
127
|
+
ok('README clarity sections')
|
|
128
|
+
|
|
129
|
+
if (readme.includes('your-client-repo')) {
|
|
130
|
+
fail('README must not instruct install in customer repo (your-client-repo)')
|
|
131
|
+
} else ok('README no customer-repo install')
|
|
132
|
+
|
|
133
|
+
if (!readme.includes('fde-engagements') || !/fdeops.*init.*engagement/i.test(readme)) {
|
|
134
|
+
fail('README must document fde-engagements + init flow')
|
|
135
|
+
} else ok('README engagement path')
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(path.join(root, 'docs', 'USAGE.md'))) {
|
|
138
|
+
fail('docs/USAGE.md missing')
|
|
139
|
+
} else ok('docs/USAGE.md')
|
|
140
|
+
|
|
141
|
+
if (!readme.includes('FDEOPS_ENGAGEMENT')) {
|
|
142
|
+
fail('README must document FDEOPS_ENGAGEMENT')
|
|
143
|
+
} else ok('README FDEOPS_ENGAGEMENT')
|
|
144
|
+
|
|
145
|
+
const badPhrases = ['team of ten', 'solo 100x', '100x engineer']
|
|
146
|
+
for (const phrase of badPhrases) {
|
|
147
|
+
if (readme.toLowerCase().includes(phrase.toLowerCase())) {
|
|
148
|
+
fail(`README must not contain hype phrase: ${phrase}`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// fdeops stands on its own. The README must never frame it as a derivative -
|
|
152
|
+
// a fork, port, or rebuild of another project.
|
|
153
|
+
const derivativeFraming = [
|
|
154
|
+
/\b(fork of|forked from|port of|rebuild of|reimplementation of|based on)\s+[A-Z]/,
|
|
155
|
+
/\b(inspired by|built on top of|powered by)\s+[A-Z][A-Za-z0-9-]+/,
|
|
156
|
+
]
|
|
157
|
+
for (const rx of derivativeFraming) {
|
|
158
|
+
if (rx.test(readme)) fail(`README must not frame fdeops as derivative: ${rx}`)
|
|
159
|
+
}
|
|
160
|
+
if (/docs\/internal|PMF_360/i.test(readme)) {
|
|
161
|
+
fail('README must not link docs/internal or PMF_360')
|
|
162
|
+
}
|
|
163
|
+
ok('README tone')
|
|
164
|
+
|
|
165
|
+
if (fs.existsSync(path.join(root, '.codex')) || fs.existsSync(path.join(root, '.opencode'))) {
|
|
166
|
+
fail('.codex/ or .opencode/ must not live at repo root - use docs/internal/experimental-agents/')
|
|
167
|
+
} else ok('no root-level experimental stubs')
|
|
168
|
+
|
|
169
|
+
for (const rel of ['docs/schema.md', 'docs/skills-reference.md', 'PRIVACY.md']) {
|
|
170
|
+
const text = read(rel)
|
|
171
|
+
if (text.includes('your-client-repo') || /scaffold.*project root/i.test(text)) {
|
|
172
|
+
fail(`${rel} contradicts laptop engagement model`)
|
|
173
|
+
}
|
|
174
|
+
if (!text.includes('fde-engagements') && rel !== 'PRIVACY.md') {
|
|
175
|
+
fail(`${rel} should mention fde-engagements default path`)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
ok('docs aligned with engagement model')
|
|
179
|
+
|
|
180
|
+
if (!fs.existsSync(path.join(root, 'docs', 'OPERATIONS.md'))) fail('docs/OPERATIONS.md missing')
|
|
181
|
+
else ok('docs/OPERATIONS.md')
|
|
182
|
+
|
|
183
|
+
if (!fs.existsSync(path.join(root, 'docs', 'schema.md'))) fail('docs/schema.md missing')
|
|
184
|
+
else ok('docs/schema.md')
|
|
185
|
+
|
|
186
|
+
if (!fs.existsSync(path.join(root, 'SECURITY.md'))) fail('SECURITY.md missing')
|
|
187
|
+
else ok('SECURITY.md')
|
|
188
|
+
|
|
189
|
+
const exampleFiles = ['reality.md', 'decisions.md', 'delivery.md', 'stakeholders.md']
|
|
190
|
+
for (const f of exampleFiles) {
|
|
191
|
+
const p = path.join(root, 'examples', 'acme-payments', '.fde', f)
|
|
192
|
+
if (!fs.existsSync(p)) fail(`examples/acme-payments/.fde/${f} missing`)
|
|
193
|
+
}
|
|
194
|
+
ok('examples walkthrough files')
|
|
195
|
+
|
|
196
|
+
if (fs.existsSync(path.join(root, 'tasks', 'plan.md'))) {
|
|
197
|
+
fail('tasks/plan.md should not be in public tree (move to docs/internal)')
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (fs.existsSync(path.join(root, 'patterns'))) {
|
|
201
|
+
fail('patterns/ is deprecated - use skills/ only (overlays live there)')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const pmf = path.join(root, 'docs', 'internal', 'PMF_360_REVIEW.md')
|
|
205
|
+
if (fs.existsSync(pmf) && !read('docs/internal/PMF_360_REVIEW.md').includes('INTERNAL')) {
|
|
206
|
+
fail('PMF_360_REVIEW.md needs INTERNAL banner')
|
|
207
|
+
} else if (fs.existsSync(pmf)) ok('internal PMF banner')
|
|
208
|
+
|
|
209
|
+
const hook = read('hooks/session-start')
|
|
210
|
+
if (!hook.includes('FDEOPS_ENGAGEMENT')) {
|
|
211
|
+
fail('session-start hook must read FDEOPS_ENGAGEMENT env var')
|
|
212
|
+
} else ok('hook FDEOPS_ENGAGEMENT')
|
|
213
|
+
|
|
214
|
+
// v3: write-side memory backstop
|
|
215
|
+
if (!fs.existsSync(path.join(root, 'hooks', 'session-stop'))) {
|
|
216
|
+
fail('hooks/session-stop missing (write-side memory backstop)')
|
|
217
|
+
} else {
|
|
218
|
+
const stopHook = read('hooks/session-stop')
|
|
219
|
+
if (!stopHook.includes('FDEOPS_ENGAGEMENT')) fail('session-stop must resolve FDEOPS_ENGAGEMENT')
|
|
220
|
+
if (!stopHook.includes('context.md')) fail('session-stop must append to context.md')
|
|
221
|
+
ok('session-stop write side')
|
|
222
|
+
}
|
|
223
|
+
for (const h of ['session-start', 'session-stop', 'pre-compact']) {
|
|
224
|
+
const mode = fs.statSync(path.join(root, 'hooks', h)).mode
|
|
225
|
+
if (!(mode & 0o111)) fail(`hooks/${h} lost its executable bit`)
|
|
226
|
+
}
|
|
227
|
+
ok('hook exec bits')
|
|
228
|
+
|
|
229
|
+
// v3.1: the fde CLI (deterministic core)
|
|
230
|
+
if (!fs.existsSync(path.join(root, 'bin', 'fde.js'))) {
|
|
231
|
+
fail('bin/fde.js missing (the deterministic core)')
|
|
232
|
+
} else {
|
|
233
|
+
const cli = read('bin/fde.js')
|
|
234
|
+
for (const sub of ['cmdScan', 'cmdResume', 'cmdLog', 'cmdReceipts', 'cmdCapture', 'cmdStatus']) {
|
|
235
|
+
if (!cli.includes(sub)) fail(`fde.js missing ${sub}`)
|
|
236
|
+
}
|
|
237
|
+
if (!JSON.parse(read('package.json')).bin.fde) fail('package.json must expose the fde bin')
|
|
238
|
+
if (!read('bin/install.js').includes('fde.js')) fail('install.js must deploy fde.js')
|
|
239
|
+
if (!read('skills/fde/SKILL.md').includes('fde resume')) fail('SKILL.md must use the CLI for memory ops')
|
|
240
|
+
ok('fde CLI present and wired')
|
|
241
|
+
}
|
|
242
|
+
const hooksJson = JSON.parse(read('hooks/hooks.json'))
|
|
243
|
+
if (!hooksJson.hooks.SessionEnd) {
|
|
244
|
+
fail('hooks.json must register SessionEnd → session-stop')
|
|
245
|
+
} else ok('SessionEnd registered')
|
|
246
|
+
if (!read('bin/install.js').includes('session-stop')) {
|
|
247
|
+
fail('install.js must copy session-stop hook')
|
|
248
|
+
} else ok('install.js copies session-stop')
|
|
249
|
+
|
|
250
|
+
const pkg = JSON.parse(read('package.json'))
|
|
251
|
+
const plugin = JSON.parse(read('.claude-plugin/plugin.json'))
|
|
252
|
+
if (pkg.version !== plugin.version) {
|
|
253
|
+
fail(`version mismatch package.json ${pkg.version} vs plugin ${plugin.version}`)
|
|
254
|
+
} else ok('plugin version aligned')
|
|
255
|
+
|
|
256
|
+
if (!fs.existsSync(path.join(root, '.github', 'ISSUE_TEMPLATE', 'bug_report.yml'))) {
|
|
257
|
+
fail('GitHub issue template missing')
|
|
258
|
+
} else ok('issue templates')
|
|
259
|
+
|
|
260
|
+
process.exit(failed)
|