create-claude-cabinet 0.45.0 → 0.47.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 +6 -10
- package/lib/cli.js +374 -91
- package/lib/copy.js +108 -6
- package/lib/db-setup.js +122 -17
- package/lib/engagement-server-setup.js +34 -9
- package/lib/metadata.js +51 -2
- package/lib/migrate-from-omega.js +13 -1
- package/lib/mux-setup.js +33 -9
- package/lib/settings-merge.js +52 -10
- package/lib/watchtower-setup.js +230 -0
- package/package.json +5 -1
- package/templates/cabinet/_cabinet-member-template.md +8 -3
- package/templates/cabinet/advisories-state-schema.md +34 -7
- package/templates/cabinet/checklist-stats-schema.md +15 -1
- package/templates/cabinet/composition-patterns.md +4 -3
- package/templates/cabinet/memory-lifecycle-contract.md +135 -0
- package/templates/cabinet/pib-db-access.md +13 -0
- package/templates/cabinet/skill-output-conventions.md +35 -1
- package/templates/cabinet/watchtower-contracts.md +503 -1
- package/templates/cabinet/worktree-invocation-contract.md +87 -0
- package/templates/engagement/OVERVIEW.md +12 -0
- package/templates/engagement/__tests__/engagement.test.mjs +177 -2
- package/templates/engagement/__tests__/invoice-doc.test.mjs +195 -0
- package/templates/engagement/engagement-preview.mjs +100 -0
- package/templates/engagement/engagement-schema.md +240 -0
- package/templates/engagement/engagement.mjs +391 -11
- package/templates/engagement/pib-db-patches/pib-db-lib.mjs +10 -1
- package/templates/engagement/pib-db-patches/pib-db-mcp-server.mjs +5 -44
- package/templates/engagement/pib-db-patches/pib-db.mjs +5 -2
- package/templates/hooks/action-completion-gate.sh +9 -3
- package/templates/hooks/memory-index-guard.sh +17 -11
- package/templates/hooks/watchtower-session-start.sh +24 -2
- package/templates/mcp/pib-db.json +1 -4
- package/templates/mux/__tests__/mux-fail-loud.fixture.sh +44 -0
- package/templates/mux/__tests__/station-liveness.fixture.sh +234 -0
- package/templates/mux/__tests__/station-liveness.test.mjs +47 -0
- package/templates/mux/bin/mux +289 -56
- package/templates/mux/config/help.txt +1 -0
- package/templates/rules/acknowledge-when-corrected.md +33 -0
- package/templates/rules/maintainability.md +11 -0
- package/templates/rules/memory-capture.md +26 -4
- package/templates/rules/plan-before-bulk-or-irreversible-actions.md +48 -0
- package/templates/rules/verify-before-asserting.md +79 -0
- package/templates/scripts/__tests__/advisor-pass.test.mjs +238 -0
- package/templates/scripts/__tests__/advisories.test.mjs +262 -0
- package/templates/scripts/__tests__/ahead-check-origin.test.mjs +355 -0
- package/templates/scripts/__tests__/batch-disposition.test.mjs +235 -0
- package/templates/scripts/__tests__/claude-churn-authored.test.mjs +90 -0
- package/templates/scripts/__tests__/cross-ring-reader.test.mjs +505 -0
- package/templates/scripts/__tests__/dx-captures-briefing.test.mjs +169 -0
- package/templates/scripts/__tests__/feedback-outbox-flush.test.mjs +232 -0
- package/templates/scripts/__tests__/hook-runner.test.mjs +247 -0
- package/templates/scripts/__tests__/narrative-corpus.test.mjs +247 -0
- package/templates/scripts/__tests__/phase-shim.test.mjs +112 -0
- package/templates/scripts/__tests__/qa-handoff-gate.test.mjs +68 -0
- package/templates/scripts/__tests__/qa-handoff-merge-state.test.mjs +162 -0
- package/templates/scripts/__tests__/resolve-cli.test.mjs +314 -0
- package/templates/scripts/__tests__/ring-state-ownership.test.mjs +228 -3
- package/templates/scripts/__tests__/ring1-content-detector.test.mjs +168 -0
- package/templates/scripts/__tests__/ring1-flagged-actions.test.mjs +135 -0
- package/templates/scripts/__tests__/ring1-script-drift.test.mjs +126 -0
- package/templates/scripts/__tests__/ring2-queue-reader.test.mjs +80 -0
- package/templates/scripts/__tests__/ring2-recall-canary.test.mjs +150 -0
- package/templates/scripts/__tests__/ring2-roster-review.test.mjs +311 -0
- package/templates/scripts/__tests__/ring2-thread-context.test.mjs +189 -0
- package/templates/scripts/__tests__/ring3-chunk-merge.test.mjs +122 -0
- package/templates/scripts/__tests__/ring3-close-lenses.test.mjs +359 -0
- package/templates/scripts/__tests__/ring3-dedup.test.mjs +467 -0
- package/templates/scripts/__tests__/ring3-memory-titles.test.mjs +187 -0
- package/templates/scripts/__tests__/ring3-novelty-rescue.test.mjs +148 -0
- package/templates/scripts/__tests__/ring3-recent-slice.test.mjs +101 -0
- package/templates/scripts/__tests__/ring4-reconcile.test.mjs +393 -0
- package/templates/scripts/__tests__/routine-dispatch.test.mjs +312 -0
- package/templates/scripts/__tests__/shared-thread-reader.test.mjs +187 -0
- package/templates/scripts/__tests__/suppression-ledger.test.mjs +148 -0
- package/templates/scripts/__tests__/verify-backfill.test.mjs +103 -0
- package/templates/scripts/__tests__/verify-coverage.test.mjs +80 -0
- package/templates/scripts/__tests__/watchtower-snapshot.test.mjs +261 -0
- package/templates/scripts/__tests__/watchtower-sync.test.mjs +311 -0
- package/templates/scripts/load-triage-history.js +5 -2
- package/templates/scripts/pib-db-mcp-server.mjs +5 -44
- package/templates/scripts/pib-db-path.mjs +61 -0
- package/templates/scripts/pib-db.mjs +5 -2
- package/templates/scripts/validate-memory.mjs +214 -16
- package/templates/scripts/watchtower-advisories.mjs +309 -0
- package/templates/scripts/watchtower-build-context.mjs +301 -18
- package/templates/scripts/watchtower-cross-ring-reader.mjs +700 -0
- package/templates/scripts/watchtower-hook-runner.mjs +422 -0
- package/templates/scripts/watchtower-lib.mjs +447 -2
- package/templates/scripts/watchtower-narrative-corpus.mjs +385 -0
- package/templates/scripts/watchtower-phase-shim.mjs +171 -0
- package/templates/scripts/watchtower-queue.mjs +484 -1
- package/templates/scripts/watchtower-ring1.mjs +533 -53
- package/templates/scripts/watchtower-ring2.mjs +787 -48
- package/templates/scripts/watchtower-ring3-close.mjs +1357 -99
- package/templates/scripts/watchtower-ring4-runner.sh +85 -0
- package/templates/scripts/watchtower-ring4.mjs +753 -0
- package/templates/scripts/watchtower-routines.mjs +358 -0
- package/templates/scripts/watchtower-snapshot.mjs +452 -0
- package/templates/scripts/watchtower-status.sh +1 -1
- package/templates/scripts/watchtower-sync.mjs +393 -0
- package/templates/skills/audit/SKILL.md +5 -1
- package/templates/skills/briefing/SKILL.md +584 -236
- package/templates/skills/cabinet-anthropic-insider/SKILL.md +14 -6
- package/templates/skills/cabinet-historian/SKILL.md +14 -11
- package/templates/skills/cabinet-process-therapist/SKILL.md +28 -0
- package/templates/skills/cabinet-security/SKILL.md +11 -0
- package/templates/skills/cabinet-system-advocate/SKILL.md +22 -21
- package/templates/skills/cabinet-user-advocate/SKILL.md +13 -7
- package/templates/skills/catch-up/SKILL.md +113 -0
- package/templates/skills/cc-publish/SKILL.md +148 -25
- package/templates/skills/cc-remember/SKILL.md +45 -0
- package/templates/skills/close/SKILL.md +107 -0
- package/templates/skills/collab-client/SKILL.md +22 -5
- package/templates/skills/collab-consultant/SKILL.md +110 -2
- package/templates/skills/debrief/SKILL.md +148 -16
- package/templates/skills/debrief-classic/SKILL.md +696 -0
- package/templates/skills/debrief-classic/calibration.md +44 -0
- package/templates/skills/debrief-classic/phases/audit-pattern-capture.md +78 -0
- package/templates/skills/debrief-classic/phases/auto-maintenance.md +48 -0
- package/templates/skills/debrief-classic/phases/checklist-feedback.md +123 -0
- package/templates/skills/debrief-classic/phases/close-work.md +163 -0
- package/templates/skills/debrief-classic/phases/health-checks.md +54 -0
- package/templates/skills/debrief-classic/phases/inventory.md +40 -0
- package/templates/skills/debrief-classic/phases/loose-ends.md +52 -0
- package/templates/skills/debrief-classic/phases/methodology-capture.md +223 -0
- package/templates/skills/debrief-classic/phases/qa-handoff-sweep.md +78 -0
- package/templates/skills/debrief-classic/phases/record-lessons.md +177 -0
- package/templates/skills/debrief-classic/phases/report.md +59 -0
- package/templates/skills/debrief-classic/phases/update-state.md +48 -0
- package/templates/skills/debrief-classic/phases/upstream-feedback.md +185 -0
- package/templates/skills/debrief-classic/phases/verify-coverage.md +101 -0
- package/templates/skills/execute/SKILL.md +77 -6
- package/templates/skills/execute/phases/commit-and-deploy.md +8 -0
- package/templates/skills/execute-group/SKILL.md +23 -26
- package/templates/skills/generate-plan-groups/SKILL.md +20 -0
- package/templates/skills/inbox/SKILL.md +124 -7
- package/templates/skills/memory/SKILL.md +22 -6
- package/templates/skills/orient/SKILL.md +140 -52
- package/templates/skills/orient-classic/SKILL.md +770 -0
- package/templates/skills/orient-classic/phases/auto-maintenance.md +52 -0
- package/templates/skills/orient-classic/phases/briefing.md +53 -0
- package/templates/skills/orient-classic/phases/cabinet.md +46 -0
- package/templates/skills/orient-classic/phases/checklist-status.md +54 -0
- package/templates/skills/orient-classic/phases/context.md +88 -0
- package/templates/skills/orient-classic/phases/data-sync.md +35 -0
- package/templates/skills/orient-classic/phases/deferred-check.md +55 -0
- package/templates/skills/orient-classic/phases/dx-captures.md +53 -0
- package/templates/skills/orient-classic/phases/health-checks.md +50 -0
- package/templates/skills/orient-classic/phases/verify-backfill.md +109 -0
- package/templates/skills/orient-classic/phases/work-scan.md +69 -0
- package/templates/skills/plan/SKILL.md +8 -0
- package/templates/skills/qa-drain/SKILL.md +157 -0
- package/templates/skills/qa-handoff/SKILL.md +287 -46
- package/templates/skills/session-handoff/SKILL.md +175 -6
- package/templates/skills/setup-accounts/SKILL.md +38 -16
- package/templates/skills/threads/SKILL.md +4 -0
- package/templates/skills/triage-audit/SKILL.md +6 -0
- package/templates/skills/validate/phases/validators.md +41 -0
- package/templates/skills/watchtower/SKILL.md +199 -2
- package/templates/verify-runtime/CONVENTIONS.md +9 -0
- package/templates/verify-runtime/README.md +37 -0
- package/templates/watchtower/config.json.template +10 -2
- package/templates/watchtower/queue/items/item.json.schema +1 -1
- package/templates/workflows/execute-group-complete.js +10 -1
|
@@ -238,9 +238,15 @@ billing:
|
|
|
238
238
|
currency: USD
|
|
239
239
|
period: since-last-sync # engagement | month | since-last-sync
|
|
240
240
|
|
|
241
|
+
invoicing: # optional — see §8.1
|
|
242
|
+
mode: consolidated # consolidated | itemized
|
|
243
|
+
scope_label: "Consulting services"
|
|
244
|
+
terms_days: 30
|
|
245
|
+
|
|
241
246
|
recipients:
|
|
242
247
|
- id: ed
|
|
243
248
|
name: "Ed Maginnis"
|
|
249
|
+
org: "Maginnis Howard" # optional — shown in the invoice bill-to
|
|
244
250
|
email: "ed@example.com"
|
|
245
251
|
role: principal
|
|
246
252
|
scopes: [all]
|
|
@@ -337,6 +343,186 @@ checklist_summary # [ { type, count } ] when config has sections
|
|
|
337
343
|
|
|
338
344
|
---
|
|
339
345
|
|
|
346
|
+
## 5.1. Decision items — the structured options & decision-type contract
|
|
347
|
+
|
|
348
|
+
The single shared contract that the engine synthesis (`renderPacket`),
|
|
349
|
+
the fail-closed validator (`assertPacketInvariants`), the client
|
|
350
|
+
renderer (`/collab-client`), and the consultant inbox
|
|
351
|
+
(`/collab-consultant`) all conform to. P1.1–P1.4 of the engagement
|
|
352
|
+
upstream-fix program depend on this — pin it here, build to it.
|
|
353
|
+
|
|
354
|
+
### Where a decision is authored (once)
|
|
355
|
+
|
|
356
|
+
A decision is authored exactly once, in `engagement.yaml`, as a `decide`
|
|
357
|
+
item inside a `sections[]` block:
|
|
358
|
+
|
|
359
|
+
```yaml
|
|
360
|
+
sections:
|
|
361
|
+
- key: go_live
|
|
362
|
+
items:
|
|
363
|
+
- key: arbitration_forum_model
|
|
364
|
+
kind: decide
|
|
365
|
+
tier: recommended # optional priority hint (free text)
|
|
366
|
+
audience: [ed] # optional; default = all principals
|
|
367
|
+
prompt: "Which forum model…" # the question → client_title / title
|
|
368
|
+
help: "Tracking per-company…" # the explanation → client_body / why
|
|
369
|
+
tradeoff: "Per-company is…" # optional; appended to `why`
|
|
370
|
+
options: # omit / empty ⇒ free-text decision
|
|
371
|
+
- "A — forum per company, with an analysis notes field"
|
|
372
|
+
- value: "B — one forum per theory"
|
|
373
|
+
label: "One forum per theory"
|
|
374
|
+
free_text: false # optional; forces a free-text answer
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`engagement.yaml` stays the **authoritative authoring source** for
|
|
378
|
+
decisions (Non-Goal: dropping it). The engine derives everything below
|
|
379
|
+
from it.
|
|
380
|
+
|
|
381
|
+
### The engine internalizes the projection (P1.1)
|
|
382
|
+
|
|
383
|
+
`renderPacket`, given the parsed config, **ensures a backing decision
|
|
384
|
+
per `decide` item and renders it into `needs_you` exactly as it renders
|
|
385
|
+
a `needs:decision` action** — preserving the `ref` / `refmap` /
|
|
386
|
+
`client_feedback` spine. The pure engine synthesizes the backing item
|
|
387
|
+
**in-memory** from the yaml (it imports nothing — no DB, no fs); a
|
|
388
|
+
pib-db row is materialized separately by `ensureDecisionActions` (below).
|
|
389
|
+
|
|
390
|
+
- **Deterministic backing fid.** Each decide item gets a stable fid
|
|
391
|
+
`decisionFid(engagementSlug, sectionKey, itemKey)` → `act:<8 hex>`
|
|
392
|
+
(FNV-1a). Deterministic so the `ref` is stable across renders
|
|
393
|
+
(`new_since_last` stays a reliable diff) and so an inbound answer's
|
|
394
|
+
`target_fid` always resolves to the same decision — even across a
|
|
395
|
+
re-render (the cross-render gate). It **must satisfy the pib-db
|
|
396
|
+
`FID_PATTERN` (`act:<8 lowercase hex>`)** so the answer's `target_fid`
|
|
397
|
+
passes `addEngagementEvent`'s `validateFid` — the production ingest
|
|
398
|
+
path the loop test's in-memory refmap never exercises (so the loop
|
|
399
|
+
test alone would not catch a malformed fid; this is why the scheme is
|
|
400
|
+
fixed at design time, not later). A collision with a real
|
|
401
|
+
pib-db-generated fid is ~1e-6 and is hard-guarded in the synthesis (an
|
|
402
|
+
unrelated live-fid match throws loudly rather than mis-attributing the
|
|
403
|
+
answer).
|
|
404
|
+
- **Same spine as an action.** `ref = stableRef(fid)`,
|
|
405
|
+
`refmap[ref] = fid`. The client answers `context_ref: <ref>`; the
|
|
406
|
+
consultant inbox resolves `ref → fid` and writes `client_feedback`
|
|
407
|
+
with `target_fid = fid`. A yaml section rendered *raw* (no fid) would
|
|
408
|
+
break this — hence synthesis, not raw rendering (loop-test step D).
|
|
409
|
+
- **No row required to render or round-trip.** `target_fid` has no FK,
|
|
410
|
+
so the answer stores even if no pib-db row exists. The row exists so
|
|
411
|
+
the consultant can *apply* the answer against a real action — see
|
|
412
|
+
`ensureDecisionActions`.
|
|
413
|
+
|
|
414
|
+
### Structural dedup (invariant #3)
|
|
415
|
+
|
|
416
|
+
The engine must **never** emit a decision twice — once natively from the
|
|
417
|
+
yaml and once from a surviving projected action. A backing/projected
|
|
418
|
+
action declares which decide item it covers via an
|
|
419
|
+
`engagement_key: <itemKey>` marker on its own line in `notes`. When
|
|
420
|
+
synthesizing, the engine **skips any decide item whose key appears in
|
|
421
|
+
the `engagement_key` marker of a live, visible `needs:decision`
|
|
422
|
+
action** — that action renders it instead. The marker is the single
|
|
423
|
+
dedup key, shared by the engine synthesis and `ensureDecisionActions`.
|
|
424
|
+
After the Phase-2 cutover (projected actions tag-neutralized), every
|
|
425
|
+
decide item renders natively.
|
|
426
|
+
|
|
427
|
+
### The packet decision item (the structured shape — P1.2)
|
|
428
|
+
|
|
429
|
+
Every decision `needs_you` item carries the fields below. **Note the
|
|
430
|
+
`kind` value is `'decision'`, not `'needs:decision'`** — `parseActionTags`
|
|
431
|
+
strips the `needs:` prefix, so the rendered item's `kind` is the
|
|
432
|
+
post-colon token (`'decision'` / `'credential'`). The TAG is
|
|
433
|
+
`needs:decision`; the item FIELD is `kind: 'decision'`.
|
|
434
|
+
|
|
435
|
+
| Field | Type | Meaning |
|
|
436
|
+
| ----- | ---- | ------- |
|
|
437
|
+
| `ref` | string | opaque stable ref (`refmap` resolves it to the fid) |
|
|
438
|
+
| `title` | string | the question (`prompt` / `client_title`) |
|
|
439
|
+
| `why` | string | the explanation (`help` / `client_body`, + `tradeoff`) |
|
|
440
|
+
| `kind` | `'decision'` | the needs token (the `needs:` prefix is stripped) |
|
|
441
|
+
| `decision_type` | `'choice'` \| `'free_text'` | how it is answered |
|
|
442
|
+
| `options` | `Array<{ value: string, label?: string }>` | the choices |
|
|
443
|
+
|
|
444
|
+
- **`options` is an array of objects, never a comma-joined string.** The
|
|
445
|
+
`value` is the canonical, **comma-safe** answer text, round-tripped
|
|
446
|
+
verbatim — there is no `split(',')` on a value anywhere. `label` is an
|
|
447
|
+
optional short display string (defaults to `value`). This retires the
|
|
448
|
+
lossy `Options: A, B, C` line: a value that itself contains a comma
|
|
449
|
+
survives intact.
|
|
450
|
+
- **Normalization.** A yaml option given as a bare string `"X"`
|
|
451
|
+
normalizes to `{ value: "X", label: "X" }`. An object option passes
|
|
452
|
+
through; a missing `label` defaults to `value`. Whitespace-only / empty
|
|
453
|
+
values are dropped.
|
|
454
|
+
- **`decision_type: 'free_text'`** — an open-ended written answer. Set
|
|
455
|
+
when the decide item declares `free_text: true` OR has no `options`.
|
|
456
|
+
`options` is `[]`. The client collects free text instead of choices.
|
|
457
|
+
- **`decision_type: 'choice'`** — pick from `options` (≥2 required).
|
|
458
|
+
|
|
459
|
+
### Fail-closed (P1.2)
|
|
460
|
+
|
|
461
|
+
`assertPacketInvariants` **throws** on any answerable `needs_you` item
|
|
462
|
+
(`kind === 'decision'` carrying a `ref`) with `decision_type === 'choice'`
|
|
463
|
+
and `options.length < 2`. An unanswerable choice decision can never ship —
|
|
464
|
+
the failure is loud at render time, not a confusing empty dialog for the
|
|
465
|
+
client. The gate is whole-packet (consistent with the leak gate): a
|
|
466
|
+
misconfigured decision holds the entire packet until fixed. Free-text
|
|
467
|
+
decisions legitimately carry zero options and are exempt. Display-only
|
|
468
|
+
roll-ups (`feedbackable: false`, no `ref`) are exempt. A real (projected)
|
|
469
|
+
action with exactly 1 option also trips this — fix by adding a second
|
|
470
|
+
option or dropping the `needs:decision` tag (an action author has no
|
|
471
|
+
`free_text` field).
|
|
472
|
+
|
|
473
|
+
### Two stability caveats
|
|
474
|
+
|
|
475
|
+
- **Decide keys must be unique across sections.** Dedup (and the legacy
|
|
476
|
+
workaround) match a projected action's `engagement_key: <key>` marker —
|
|
477
|
+
which carries no section — against a yaml decide key. If the same key
|
|
478
|
+
appears in two sections and one projected action covers it, both
|
|
479
|
+
sections' synthesis is suppressed and only one renders. Keep decide
|
|
480
|
+
keys unique within an engagement.
|
|
481
|
+
- **A section/slug rename re-keys the ref.** The cross-render ref
|
|
482
|
+
stability holds only while `engagement.slug`, the section key, and the
|
|
483
|
+
item key are all unchanged (they seed `decisionFid`). Renaming any of
|
|
484
|
+
them changes the fid → ref and orphans an in-flight answer keyed to the
|
|
485
|
+
old ref. Don't rename a section or the slug while a decision packet is
|
|
486
|
+
out and unanswered (the refmap-continuity invariant, #5).
|
|
487
|
+
|
|
488
|
+
### `ensureDecisionActions` — the persistent-row materialization (Phase-2 cutover)
|
|
489
|
+
|
|
490
|
+
**Phase 1 ships the render synthesis above; the persistent-row
|
|
491
|
+
materialization lands at the Phase-2 Maginnis cutover (P2.1), where it
|
|
492
|
+
replaces the project-local `project-engagement-decisions.mjs`.** The
|
|
493
|
+
synthesis alone closes the loop (render + refmap + answer round-trip;
|
|
494
|
+
`target_fid` has no FK, so the answer stores without a row). The
|
|
495
|
+
materialized row is what lets the consultant *apply* an answer against a
|
|
496
|
+
tracked action.
|
|
497
|
+
|
|
498
|
+
When built, it is the DB-side counterpart — living at the adapter/skill
|
|
499
|
+
boundary (NOT in the pure engine), called at sync time before render:
|
|
500
|
+
idempotently materializes one pib-db action per decide item, keyed to
|
|
501
|
+
the **same deterministic fid** and carrying the `engagement_key:
|
|
502
|
+
<itemKey>` marker, `client-visible,needs:decision` tags (+
|
|
503
|
+
`audience:<id>` when declared), and the structured copy. Because the row
|
|
504
|
+
carries the marker, the render's structural dedup skips re-synthesizing
|
|
505
|
+
that key and renders the row instead — so in production every decision
|
|
506
|
+
resolves to a real, ingest-valid fid, and the synthesis is the
|
|
507
|
+
no-row / loop-test fallback.
|
|
508
|
+
|
|
509
|
+
### Client render (P1.2 → `/collab-client`)
|
|
510
|
+
|
|
511
|
+
A `choice` decision maps each `options[i]` to one AskUserQuestion option
|
|
512
|
+
(`label: opt.label`, description from context); the chosen `value` is
|
|
513
|
+
the answer text submitted as the `client_feedback` message. A
|
|
514
|
+
`free_text` decision collects an open written answer (no option list).
|
|
515
|
+
The client never sees the `decision_type` enum or verdict jargon.
|
|
516
|
+
|
|
517
|
+
### Answer round-trip (unchanged spine)
|
|
518
|
+
|
|
519
|
+
The answer is a `client_feedback` event with
|
|
520
|
+
`target_fid = refmap[context_ref]` (§6). Because the backing fid is
|
|
521
|
+
deterministic, an answer keyed to a prior packet's ref resolves to the
|
|
522
|
+
same decision across re-renders.
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
340
526
|
## 6. `item_feedback` envelope
|
|
341
527
|
|
|
342
528
|
The contract for inbound client responses. **Keyed by `ref` only.**
|
|
@@ -448,6 +634,60 @@ Default rate **$190/hr**. `renderBilling` sums the `Hours` column;
|
|
|
448
634
|
|
|
449
635
|
---
|
|
450
636
|
|
|
637
|
+
## 8.1. Invoicing (`invoicing` block + the `invoices/` output)
|
|
638
|
+
|
|
639
|
+
`/collab-consultant invoice [YYYY-MM] [--itemized]` renders a clean,
|
|
640
|
+
print-friendly invoice **document** (`renderInvoiceDoc`) from a month's
|
|
641
|
+
tracked hours. It is local-only — no API, no pib-db mutation, no email.
|
|
642
|
+
|
|
643
|
+
### Why a document, not an API
|
|
644
|
+
|
|
645
|
+
The operator's bank (**Relay**) does the actual invoicing. Relay has no
|
|
646
|
+
API but accepts an uploaded PDF/PNG via **Create Invoice → Autofill from
|
|
647
|
+
document**. So the system's only job is to render the document; the
|
|
648
|
+
operator uploads, reviews, and sends in Relay. Validated against Relay's
|
|
649
|
+
autofill (live experiment):
|
|
650
|
+
|
|
651
|
+
- Line items are **Description / Quantity / Unit price / Amount** and
|
|
652
|
+
Relay computes its own totals (Quantity = hours, Unit price = the rate).
|
|
653
|
+
- Relay assigns its own invoice number and pulls the sender (From /
|
|
654
|
+
business name / address / email) from the operator's Relay account.
|
|
655
|
+
**The document therefore carries NO invoice number and NO From / remit
|
|
656
|
+
/ bank / ACH block** — they are ignored or overridden.
|
|
657
|
+
- Relay autofills the customer (bill-to name + email) and the line items
|
|
658
|
+
+ total correctly.
|
|
659
|
+
- Relay does **not** read a net-30 due date — it defaults the due date to
|
|
660
|
+
today. Terms only survive in Relay's free-text Memo, so they go in a
|
|
661
|
+
memo-friendly footer line and the skill tells the operator to set the
|
|
662
|
+
due date in Relay by hand.
|
|
663
|
+
|
|
664
|
+
### The `invoicing:` config block (optional)
|
|
665
|
+
|
|
666
|
+
| Field | Default | Meaning |
|
|
667
|
+
| ----- | ------- | ------- |
|
|
668
|
+
| `mode` | `consolidated` | `consolidated` (one line: scope label + month, Quantity = total hours) or `itemized` (one line per timelog row). `--itemized` on the command line overrides. |
|
|
669
|
+
| `scope_label` | `"Consulting services"` | The line description in consolidated mode (rendered as `<scope_label> — <Month YYYY>`). |
|
|
670
|
+
| `terms_days` | `30` | Net-N payment terms — `dueDate = invoiceDate + terms_days`, shown in the memo footer. |
|
|
671
|
+
|
|
672
|
+
`loadEngagement` normalizes these defaults (mirrors how `billing` /
|
|
673
|
+
`transport` defaults are filled), so downstream code can assume the
|
|
674
|
+
shape. **No bank / remit / From fields are needed** — see above.
|
|
675
|
+
|
|
676
|
+
The bill-to block reads `recipient.name`, optional `recipient.org`, and
|
|
677
|
+
`recipient.email` (org rides through `normalizeRecipient`).
|
|
678
|
+
|
|
679
|
+
### Output convention: `invoices/<YYYY-MM>.html` (+ best-effort `.pdf`)
|
|
680
|
+
|
|
681
|
+
The rendered HTML is written to `invoices/<YYYY-MM>.html` in the working
|
|
682
|
+
dir. The skill then attempts a best-effort PDF conversion using whatever
|
|
683
|
+
tool is already on the machine (weasyprint → headless Chrome →
|
|
684
|
+
wkhtmltopdf), **with no bundled dependency** — if none is available it
|
|
685
|
+
tells the operator to open the HTML and use Cmd-P → Save as PDF. Relay
|
|
686
|
+
needs a PDF or PNG, not HTML. The `invoices/` directory is local runtime
|
|
687
|
+
output (gitignore alongside `engagement-packets/`).
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
451
691
|
## 9. Scope model: multi-project engagements
|
|
452
692
|
|
|
453
693
|
An engagement operates at the scope of **one `pib.db`** — all
|