fdeops 3.26.0 → 3.27.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/README.md +73 -159
- package/bin/fde.js +212 -99
- package/bin/install.js +1 -1
- package/bin/lib/delivery-gaps.js +34 -0
- package/bin/lib/provenance.js +42 -0
- package/bin/lib/render.js +16 -30
- package/bin/lib/value-ledger.js +8 -2
- package/mcp/fdeops-ingest/package.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/fde/SKILL.md +6 -5
- package/skills/fde/references/plan.md +2 -0
- package/skills/fde/references/poc.md +11 -3
- package/skills/fde/references/ship.md +3 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Source syntax records an inspectable attribution, never authenticates it.
|
|
4
|
+
// The entry's automatic log date is deliberately not a source.
|
|
5
|
+
const PLACEHOLDER = /^(?:none|unknown|tbd|pending|n\/?a|source|evidence|not provided|missing|\?+|-+)$/i
|
|
6
|
+
function sourceReference(value) {
|
|
7
|
+
const text = String(value || '')
|
|
8
|
+
const explicit = text.match(/\[source:\s*([^\]\n]+)\]/i)
|
|
9
|
+
if (explicit) {
|
|
10
|
+
const source = explicit[1].trim()
|
|
11
|
+
return source && !PLACEHOLDER.test(source) ? source : ''
|
|
12
|
+
}
|
|
13
|
+
const url = text.match(/https?:\/\/[^\s<>\])|]+/i)
|
|
14
|
+
if (url) return url[0]
|
|
15
|
+
const id = text.match(/\b(?:PR\s*#\s*\d+|commit\s+[a-f0-9]{7,40}|(?:transcript|meeting|email|run)\s*(?:id\s*)?:\s*[\w][\w./#-]*)/i)
|
|
16
|
+
if (id && !PLACEHOLDER.test(id[0].split(':').pop().trim())) return id[0]
|
|
17
|
+
const dated = text.match(/\b(?:meeting|call|email|transcript)\b[^\n|]{0,60}\b\d{4}-\d{2}-\d{2}\b/i)
|
|
18
|
+
if (dated) return dated[0]
|
|
19
|
+
const file = text.match(/(?:^|[\s(])((?:[\w.-]+\/)*[\w.-]+\.(?:md|txt|csv|json|pdf|png|html|log))(?:$|[\s)#])/i)
|
|
20
|
+
return file ? file[1] : ''
|
|
21
|
+
}
|
|
22
|
+
module.exports = { sourceReference, hasSource: value => Boolean(sourceReference(value)) }
|
|
23
|
+
|
|
24
|
+
// Both CLI bullets and the documented long-form decision format are records.
|
|
25
|
+
// Keep the source with its decision body; a heading alone is not the evidence.
|
|
26
|
+
function datedDecisions(value) {
|
|
27
|
+
const lines = String(value || '').split('\n')
|
|
28
|
+
const entries = []
|
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30
|
+
const bullet = lines[i].match(/^\s*[-*]\s*\[(\d{4}-\d{2}-\d{2})\]\s*(.+)/)
|
|
31
|
+
const heading = lines[i].match(/^(#{2,3})\s+\[?(\d{4}-\d{2}-\d{2})(?:\]|\s+-)?\s+(.+)/)
|
|
32
|
+
if (bullet) entries.push({ date: bullet[1], text: lines[i].trim(), line: i + 1 })
|
|
33
|
+
else if (heading) {
|
|
34
|
+
let end = i + 1
|
|
35
|
+
while (end < lines.length && !/^#{1,3}\s/.test(lines[end]) && !/^\s*[-*]\s*\[\d{4}-\d{2}-\d{2}\]/.test(lines[end])) end++
|
|
36
|
+
entries.push({ date: heading[2], text: lines.slice(i, end).join('\n').trim(), line: i + 1 })
|
|
37
|
+
i = end - 1
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return entries.sort((a, b) => a.date.localeCompare(b.date) || a.line - b.line)
|
|
41
|
+
}
|
|
42
|
+
module.exports.datedDecisions = datedDecisions
|
package/bin/lib/render.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict"
|
|
2
2
|
|
|
3
|
+
const { deliverySummary } = require('./delivery-gaps')
|
|
4
|
+
|
|
3
5
|
// Pure, local HTML renderer for the FDE fieldbook. Data extraction stays in fde.js.
|
|
4
6
|
|
|
5
7
|
function escapeHtml(s) {
|
|
@@ -199,6 +201,7 @@ strong{font-weight:600}
|
|
|
199
201
|
.fb-queue-row:nth-child(even),.fb-flag-row:nth-child(even){background:var(--panel)}
|
|
200
202
|
.fb-queue-slug{font-family:'Geist Mono',monospace;font-size:12px;color:var(--ink-faint);flex:0 0 130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
201
203
|
.fb-queue-action{font-size:14px;line-height:1.45;color:var(--ink);flex:1 1 auto}
|
|
204
|
+
.fb-queue-reason{display:block;font-size:12px;color:var(--ink-faint);margin-top:5px;line-height:1.5}
|
|
202
205
|
.fb-queue-touched{font-family:'Geist Mono',monospace;font-size:10.5px;color:var(--ink-faint);flex:0 0 auto}
|
|
203
206
|
.fb-flag-slug{font-family:'Geist Mono',monospace;font-size:12px;color:var(--ink-faint);flex:0 0 130px}
|
|
204
207
|
.fb-flag-text{font-family:'Geist Mono',monospace;font-size:12.5px}
|
|
@@ -308,13 +311,12 @@ function railItemHtml(e) {
|
|
|
308
311
|
|
|
309
312
|
function queueRowHtml(e) {
|
|
310
313
|
const dotClass = dotClassFor(e.signals.trust)
|
|
311
|
-
const
|
|
312
|
-
const actionText = noAction ? 'Set the next action with your agent' : e.next
|
|
314
|
+
const action = e.firstAction
|
|
313
315
|
const touchedClass = e.quiet ? 't-amber' : 't-faint'
|
|
314
316
|
return `<button type="button" class="fb-row fb-queue-row" data-client="eng-${e.slug}" data-nav="eng-${e.slug}">
|
|
315
317
|
<span class="dot dot-md ${dotClass}"></span>
|
|
316
318
|
<span class="fb-queue-slug">${inlineMd(e.name)}</span>
|
|
317
|
-
<span class="fb-queue-action${
|
|
319
|
+
<span class="fb-queue-action">${inlineMd(action.text)}<span class="fb-queue-reason${action.tone ? ' t-' + action.tone : ''}">${escapeHtml(action.reason)} · ${escapeHtml(action.source)}</span></span>
|
|
318
320
|
<span class="fb-queue-touched ${touchedClass}">${escapeHtml(e.signals.updated)}</span>
|
|
319
321
|
</button>`
|
|
320
322
|
}
|
|
@@ -340,7 +342,8 @@ function todayViewHtml({ today, total, attentionCount, highRiskTotal, todayQueue
|
|
|
340
342
|
<div class="fb-meta-line">${escapeHtml(today)} · ${total} engagement${total === 1 ? '' : 's'} · ${attentionCount} need you · ${highRiskTotal} high risk${highRiskTotal === 1 ? '' : 's'} open</div>
|
|
341
343
|
<p id="fb-results-empty" class="fb-empty-search" hidden>No clients match these filters. <button type="button" class="fb-btn" data-reset-filters>Clear filters</button></p>
|
|
342
344
|
<div class="fb-block" data-filter-section>
|
|
343
|
-
<div class="fb-sec">
|
|
345
|
+
<div class="fb-sec">Recommended first actions</div>
|
|
346
|
+
<p class="fb-evidence-note">One starting point per client, based on the saved record. Confirm the gap before acting; these suggestions do not change recorded next actions.</p>
|
|
344
347
|
${todayQueue}
|
|
345
348
|
</div>
|
|
346
349
|
${flagsHtml ? `<div class="fb-block" data-filter-section>
|
|
@@ -350,26 +353,6 @@ ${flagsHtml}
|
|
|
350
353
|
</div>`
|
|
351
354
|
}
|
|
352
355
|
|
|
353
|
-
function attentionFor(e) {
|
|
354
|
-
const items = []
|
|
355
|
-
const add = (text, tone = 'amber') => items.push({ text, tone })
|
|
356
|
-
if (e.signals.memoryWarn) add('Record needs repair: ' + e.signals.memoryWarn, 'red')
|
|
357
|
-
if (e.highRisks) add(`${e.highRisks} high risk${e.highRisks === 1 ? '' : 's'} open`, 'red')
|
|
358
|
-
if (e.signals.trust === 'RED' || e.signals.trust === 'amber') add('Check in with the customer: trust is ' + trustWord(e.signals.trust), e.signals.trust === 'RED' ? 'red' : 'amber')
|
|
359
|
-
if (e.signals.trust === 'new') add('No dated trust signal yet - ask someone')
|
|
360
|
-
if (!e.hasNext) add('Set the next action')
|
|
361
|
-
const rows = e.valueRows || []
|
|
362
|
-
const missingEvidence = rows.filter(r => r.evidenceMissing).length
|
|
363
|
-
if (missingEvidence) add(`${missingEvidence} outcome${missingEvidence === 1 ? '' : 's'} missing evidence`)
|
|
364
|
-
const claimed = rows.filter(r => r.state === 'claimed').length
|
|
365
|
-
const unmeasured = rows.filter(r => r.state === 'unmeasured').length
|
|
366
|
-
if (claimed) add(`${claimed} measured outcome${claimed === 1 ? '' : 's'} awaiting acceptance`)
|
|
367
|
-
if (unmeasured) add(`${unmeasured} outcome${unmeasured === 1 ? '' : 's'} not yet measured`)
|
|
368
|
-
if (e.signals.stale) add('Reconfirm the dated trust signal')
|
|
369
|
-
if (e.quiet) add('Record last updated ' + e.signals.updated)
|
|
370
|
-
return items
|
|
371
|
-
}
|
|
372
|
-
|
|
373
356
|
function agentActionsHtml(e) {
|
|
374
357
|
const prefix = `@fde Confirm the workspace is bound to ${JSON.stringify(e.name)} before using its records. `
|
|
375
358
|
const actions = [
|
|
@@ -385,7 +368,7 @@ function agentActionsHtml(e) {
|
|
|
385
368
|
|
|
386
369
|
function deliveryHtml(e) {
|
|
387
370
|
const rows = e.valueRows || []
|
|
388
|
-
const labels = { unmeasured: 'Not yet measured', claimed: 'Awaiting acceptance', accepted: 'Acceptance recorded' }
|
|
371
|
+
const labels = { unmeasured: 'Not yet measured', claimed: 'CLAIM · Awaiting acceptance', accepted: 'Acceptance recorded' }
|
|
389
372
|
const pending = value => !value || /^(?:pending|tbd|unknown|n\/a|none|awaiting)(?:\b|$)/i.test(value)
|
|
390
373
|
const cell = (value, fallback) => pending(value) ? `<span class="t-faint">${escapeHtml(value || fallback)}</span>` : inlineMd(value)
|
|
391
374
|
return `<section class="fb-block fb-delivery" aria-label="Delivery evidence">
|
|
@@ -452,7 +435,7 @@ ${e.realityMissing ? `<p class="fb-why fb-why-missing"><span class="fb-accent-la
|
|
|
452
435
|
const vitalsBlock = `<div class="fb-vitals">
|
|
453
436
|
${vitalsRows.map(([label, val, tone]) => `<div class="fb-vital-row"><span class="fb-vital-label">${escapeHtml(label)}</span><span class="fb-vital-val${tone ? ' t-' + tone : ''}">${escapeHtml(val)}</span></div>`).join('\n')}
|
|
454
437
|
${teamStatus ? `<div class="fb-vital-row"><span class="fb-vital-label">team status</span><span class="fb-vital-val">${teamStatus}</span></div>` : ''}
|
|
455
|
-
${e.stats.length ? `<div class="fb-vital-div"></div>${e.stats.map(s => `<div class="fb-vital-row"><span class="fb-vital-label">${inlineMd(s.label)}</span><span class="fb-vital-val">${inlineMd(s.from)} <span class="fb-arrow">→</span> <span class="fb-stat-to">${inlineMd(s.to)}</span></span></div>`).join('\n')}` : ''}
|
|
438
|
+
${e.stats.length ? `<div class="fb-vital-div"></div><p class="fb-footnote">Metric excerpts are CLAIMS; verify source and acceptance.</p>${e.stats.map(s => `<div class="fb-vital-row"><span class="fb-vital-label">${inlineMd(s.label)}</span><span class="fb-vital-val">${inlineMd(s.from)} <span class="fb-arrow">→</span> <span class="fb-stat-to">${inlineMd(s.to)}</span></span></div>`).join('\n')}` : ''}
|
|
456
439
|
</div>`
|
|
457
440
|
|
|
458
441
|
const peopleBlock = e.stakeholders.length ? `<div class="fb-block">
|
|
@@ -534,10 +517,13 @@ function paletteItemsHtml(ordered) {
|
|
|
534
517
|
}
|
|
535
518
|
|
|
536
519
|
function buildFieldbookHtml({ engagements, today, generatedAt = '' }) {
|
|
537
|
-
engagements = engagements.map(e =>
|
|
538
|
-
|
|
520
|
+
engagements = engagements.map(e => {
|
|
521
|
+
const { gaps, firstAction } = deliverySummary(e)
|
|
522
|
+
return { ...e, attention: gaps, firstAction }
|
|
523
|
+
})
|
|
524
|
+
// Rail and Today queue share an order: urgent record gaps first, then trust.
|
|
539
525
|
const tierRank = { RED: 0, amber: 1, green: 2, new: 3 }
|
|
540
|
-
const ordered = engagements.slice().sort((a, b) => Number(b.attention.length > 0) - Number(a.attention.length > 0) || tierRank[a.signals.trust] - tierRank[b.signals.trust])
|
|
526
|
+
const ordered = engagements.slice().sort((a, b) => Number(b.firstAction.tone === 'red') - Number(a.firstAction.tone === 'red') || Number(b.attention.length > 0) - Number(a.attention.length > 0) || tierRank[a.signals.trust] - tierRank[b.signals.trust])
|
|
541
527
|
const attentionCount = engagements.filter(e => e.attention.length > 0).length
|
|
542
528
|
const highRiskTotal = engagements.reduce((n, e) => n + e.highRisks, 0)
|
|
543
529
|
|
|
@@ -557,7 +543,7 @@ function buildFieldbookHtml({ engagements, today, generatedAt = '' }) {
|
|
|
557
543
|
|
|
558
544
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
559
545
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
560
|
-
<script>
|
|
546
|
+
<script>document.documentElement.setAttribute("data-fde-theme","dark");try{if(localStorage.getItem("fde-fieldbook-theme")==="light")document.documentElement.setAttribute("data-fde-theme","light");}catch(e){}</script>
|
|
561
547
|
<title>FDE Fieldbook</title><style>${dashStyles()}</style></head><body>
|
|
562
548
|
<a class="fb-skip" href="#fb-main">Skip to content</a>
|
|
563
549
|
<div class="fb-app">
|
package/bin/lib/value-ledger.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
+
const { hasSource } = require('./provenance')
|
|
2
3
|
|
|
3
4
|
// Empty measurements and unsigned outcomes are different facts. Keep their
|
|
4
5
|
// interpretation shared by status, reports, and exported vaults.
|
|
@@ -19,9 +20,14 @@ function acceptanceName(value) {
|
|
|
19
20
|
return name
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
function evidenceSource(evidence) {
|
|
24
|
+
const text = String(evidence || '').trim()
|
|
25
|
+
return hasSource(text) && !PENDING_CELL_RE.test(text) && !/\b(?:no evidence|never (?:run|tested|measured)|not (?:run|tested|measured|verified)|unverified|invalid|retracted)\b/i.test(text)
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
function valueState({ measured, accepted, acceptanceStatus, evidence }) {
|
|
23
29
|
if (!measured || PENDING_CELL_RE.test(measured)) return 'unmeasured'
|
|
24
|
-
if (!acceptanceName(accepted)) return 'claimed'
|
|
30
|
+
if (!acceptanceName(accepted) || !evidenceSource(evidence)) return 'claimed'
|
|
25
31
|
// Explicit status is authoritative when the column exists. Unknown values
|
|
26
32
|
// fail closed. Existing name-only ledgers remain readable during migration.
|
|
27
33
|
if (acceptanceStatus !== undefined) {
|
|
@@ -31,4 +37,4 @@ function valueState({ measured, accepted, acceptanceStatus, evidence }) {
|
|
|
31
37
|
return 'accepted'
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
module.exports = { PENDING_CELL_RE, acceptanceName, valueState }
|
|
40
|
+
module.exports = { PENDING_CELL_RE, acceptanceName, evidenceSource, valueState }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fdeops",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.27.0",
|
|
4
4
|
"description": "Client delivery tools for Forward Deployed Engineers. One @fde skill, local Markdown engagement records, and an offline dashboard for decisions, evidence, approvals, and next actions.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fdeops": "bin/install.js",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "fdeops",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.27.0",
|
|
5
5
|
"description": "Forward deployed engineering skills for AI coding agents. One @fde skill for the client work around the code. You confirm; then it lands in .fde/ on your laptop.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Subash Natarajan",
|
package/skills/fde/SKILL.md
CHANGED
|
@@ -28,13 +28,13 @@ A one-line typo or compile error in a file that will not ship. On a bound client
|
|
|
28
28
|
| **When did we agree?** | Don't argue from memory. Search the record. | `fde receipts <term>` | - |
|
|
29
29
|
| **What's the outcome?** | A number nobody signed is claimed, not delivered. | `fde status` | `references/readout.md` |
|
|
30
30
|
|
|
31
|
-
After a meeting: `fde debrief --smart` → one REVIEW screen (
|
|
31
|
+
After a meeting: `fde debrief --smart` → one REVIEW screen (decisions / asks / scope / delivery gaps / next / signer) → in chat, a four-row card (omit empty; Previously / Not yet agreed) → **Save this update?** (engineer accepted the record, not customer approval of every ask) → `--apply`. Walk-in: `fde prep`. Friday: `fde status`.
|
|
32
32
|
|
|
33
33
|
## Ground loop
|
|
34
34
|
|
|
35
35
|
On someone else's site the work is not "write code, remember later." Every change on a bound client stays on `@fde`:
|
|
36
36
|
|
|
37
|
-
1. **Name it** in `decisions.md` (plan) or
|
|
37
|
+
1. **Name it** in `decisions.md` (plan), or timebox the riskiest assumption and record what the POC proves.
|
|
38
38
|
2. **Characterise their code** before you change it. Brownfield: their tests, their runner. Greenfield: the empty tree, first path they can click.
|
|
39
39
|
3. **Prove it on their staging.** Staging they operate, a screen the signer in `success.md` can reject.
|
|
40
40
|
4. **If a model judges:** `evals.md` Verdict SHIP before that change is done (eval-pack).
|
|
@@ -58,7 +58,7 @@ Fallbacks: `node ~/.claude/fdeops/fde.js …`, then `npx --yes fdeops …`. Skil
|
|
|
58
58
|
|
|
59
59
|
## Entry (every session)
|
|
60
60
|
|
|
61
|
-
1. `fde resume` (16 KiB output ceiling, not a model token count). Read
|
|
61
|
+
1. `fde resume` (16 KiB output ceiling, not a model token count). Read client constraints first, then signer, goals, risks, delivery ledger and current context. This command is the inspectable packet the session hook loads; never substitute a recursive read of `.fde/` or raw transcripts. If truncated or a decision needs evidence, run `fde recall <specific topic>`; narrow the query rather than loading the whole history. `--max-bytes 4096` reduces the allowance for smaller models. `--full` only when the complete log is explicitly needed.
|
|
62
62
|
2. **NO ENGAGEMENT:** ask "What should we call this client?" then **you** init. Pasted notes → debrief after bind.
|
|
63
63
|
3. Playback 2-3 lines. `hygiene:` → offer `fde doctor`; **never auto-rewrite**.
|
|
64
64
|
4. Route. Read **one** `references/*.md`. Confirm, then write.
|
|
@@ -72,7 +72,8 @@ Writes need a bind (`FDEOPS_ENGAGEMENT` or registry). Never install fdeops on in
|
|
|
72
72
|
| debrief / pasted notes | `fde debrief --smart` → REVIEW → four-row chat card → Save this update? → `--apply`. `--smart` is a gate, not a brain. `references/debrief.md` |
|
|
73
73
|
| prep me for … | `fde prep "<label>"` |
|
|
74
74
|
| when did we agree | `fde receipts <term>` |
|
|
75
|
-
| sponsor update / the
|
|
75
|
+
| sponsor update / defend the number | `fde defend` |
|
|
76
|
+
| successor / rotation / portable handoff | `fde handoff` (stdout; `--out new-file.md` only after export requested) |
|
|
76
77
|
| they went quiet | `fde log contact "…" --signal amber\|green\|red` |
|
|
77
78
|
| fieldbook page | `fde dashboard` (`--all` portfolio, `--open` to open the file) |
|
|
78
79
|
| clean up the fieldbook | `fde doctor` - never auto-rewrite |
|
|
@@ -85,7 +86,7 @@ Writes need a bind (`FDEOPS_ENGAGEMENT` or registry). Never install fdeops on in
|
|
|
85
86
|
|
|
86
87
|
1. **On entry:** `fde resume` only. Pull other `.fde/` files when the skill needs them.
|
|
87
88
|
2. **Deliverable = memory.** The work *is* the `.fde/` file. The reference names which one.
|
|
88
|
-
3. **Evidence.**
|
|
89
|
+
3. **Evidence.** Without a supplied source, a decision or measurement remains CLAIM. Use `[source: meeting YYYY-MM-DD]`, a PR/URL, transcript ID, or artifact path. The automatic log date is not attribution. ON RECORD means a source was supplied, not that it was authenticated or the customer approved. Never invent a source, signer, or acceptance.
|
|
89
90
|
4. **No invented facts.** People, quotes, meetings, numbers: they said it or the repo shows it. Else `unknown - ask: <question>`.
|
|
90
91
|
5. **Session digest** (end of session and before a PR) - thinking, not the chat. Confirm, then write. Never a transcript dump.
|
|
91
92
|
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
**Read first:** `reality.md`, `success.md`, `terrain.md`, `stakeholders.md`. Load `business-case.md` if poc produced one. Not the full folder.
|
|
6
6
|
|
|
7
|
+
**Before committing a plan or building:** run `fde doctor --ready`. Missing binary success or a named customer-side signer blocks progression: review the proposed acceptance check and authority with the FDE first. Use a test/input and observable pass/fail under **Done when:** or **Acceptance check:**. A number, role, or successful demo alone is insufficient. Do not invent missing facts to pass lint.
|
|
8
|
+
|
|
7
9
|
## Validation gate (confirm understanding, clarify where it elevates)
|
|
8
10
|
|
|
9
11
|
Before planning, state what you're working from in 2-3 lines:
|
|
@@ -14,10 +14,10 @@ A green check on synthetic data is not a validated solution. The person who can
|
|
|
14
14
|
|
|
15
15
|
**1. Pick by score when several use cases compete.** Use the scoring model from `discover.md` - (Value × Data readiness) / Complexity. If discover or score-use-cases already produced a ranking, reuse it; never invent a third ranking.
|
|
16
16
|
|
|
17
|
-
**2. Build the minimum that tests the assumption.**
|
|
17
|
+
**2. Build the minimum that tests the assumption.** Timebox the experiment with the FDE; aim for a same-day result when access and evidence permit it. Skip cosmetic polish, but keep the input validation, access controls, and failure handling needed to protect the test environment and data. Label shortcuts and simulated inputs. The POC is done when the person who can say no has seen the evidence and reacted, not when the code looks finished.
|
|
18
18
|
|
|
19
19
|
**3. AI directions - test these before anything else:**
|
|
20
|
-
- Data: available, clean, sufficient volume? Synthetic
|
|
20
|
+
- Data: available, clean, sufficient volume? Synthetic data can test mechanics, but does not establish production quality or real-world coverage.
|
|
21
21
|
- Environment: are external model calls even allowed here?
|
|
22
22
|
- Latency: acceptable against real user expectations, not ideal conditions?
|
|
23
23
|
- Is AI the right tool at all - or is this a data-quality or process problem wearing an AI costume?
|
|
@@ -26,6 +26,14 @@ A green check on synthetic data is not a validated solution. The person who can
|
|
|
26
26
|
|
|
27
27
|
**5. Translate to business language** once validated: problem solved, cost of inaction, success in numbers, 2-3 trade-offs. Three sentences max for the stakeholder - can't say it in three, don't understand it yet.
|
|
28
28
|
|
|
29
|
+
## If proceeding to production
|
|
30
|
+
|
|
31
|
+
Carry the hypothesis, test evidence, customer reaction, and remaining unknowns into the existing `plan` and `ship` workflow. A working demo does not establish production readiness or customer acceptance.
|
|
32
|
+
|
|
33
|
+
Inspect the prototype before deciding what to reuse. Keep components whose behavior and boundaries are suitable and tested. Replace or harden shortcuts that fail production requirements; rewrite only where the evidence justifies it. Record the decision and remaining work in `decisions.md`, rather than treating all prototype code as disposable or all working code as ready to deploy.
|
|
34
|
+
|
|
35
|
+
Production work includes the actual data path, permissions, failure recovery, realistic load, observability, ownership, and required AI evaluations. Use the existing ship gates for those checks.
|
|
36
|
+
|
|
29
37
|
## Artifact
|
|
30
38
|
|
|
31
39
|
**`prototype-log.md`** - what was built, shown, the actual reaction, what was learned (including kills - a killed prototype that saved three weeks is a win worth recording).
|
|
@@ -38,7 +46,7 @@ Tell the FDE: did the riskiest assumption hold · what the customer's reaction a
|
|
|
38
46
|
|
|
39
47
|
## Principles
|
|
40
48
|
|
|
41
|
-
-
|
|
49
|
+
- Optimize for a bounded learning outcome. Agree a timebox and revisit scope if access or evidence blocks it; never skip necessary safeguards to meet an arbitrary duration.
|
|
42
50
|
- Write pass/fail before you build. A demo with no kill line is a show.
|
|
43
51
|
- Show it rough. Polish misleads.
|
|
44
52
|
- Prototype the killer assumption, not the demo.
|
|
@@ -12,7 +12,9 @@ Do not ask them to pick a mode. Name where you are, then start at the matching s
|
|
|
12
12
|
|
|
13
13
|
If going live, opening question: **has anyone actually *run* the rollback, or is it still a slide?** If only planned, that's today's work - say so plainly.
|
|
14
14
|
|
|
15
|
-
A
|
|
15
|
+
A bounded experiment that tests an assumption is `poc`. This skill turns a validated direction into a maintainable change on a repo they will own, then production. Inspect existing prototype code and retain suitable tested parts; replace unsafe shortcuts based on evidence. A successful demo alone does not satisfy the readiness gates below.
|
|
16
|
+
|
|
17
|
+
**Before committing a plan or building:** run `fde doctor --ready`. Missing binary success or a named customer-side signer blocks progression: review the proposed acceptance check and authority with the FDE first. Use a test/input and observable pass/fail under **Done when:** or **Acceptance check:**. A number, role, or successful demo alone is insufficient. Do not invent missing facts to pass lint.
|
|
16
18
|
|
|
17
19
|
## Field (name it once, then the same loop)
|
|
18
20
|
|