@rubytech/create-sitedesk-code 0.1.524 → 0.1.525
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/package.json +1 -1
- package/payload/platform/plugins/admin/skills/superpowers-sprint/SKILL.md +26 -0
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
- package/payload/platform/plugins/memory/PLUGIN.md +10 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js +5 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.js +141 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js +6 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.d.ts +45 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.js +122 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-by-name.test.js +26 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-by-name.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.js +201 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +89 -33
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +6 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.js +148 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +83 -6
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +90 -5
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js +111 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +38 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +116 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +13 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +81 -8
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +39 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +9 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/scripts/__tests__/check-canonical-tool-names.test.sh +162 -0
- package/payload/platform/scripts/__tests__/check-no-task-id-leaks.test.sh +53 -0
- package/payload/platform/scripts/__tests__/task-id-citation.test.sh +72 -0
- package/payload/platform/scripts/check-canonical-tool-names.mjs +36 -4
- package/payload/platform/scripts/check-no-task-id-leaks.mjs +80 -62
- package/payload/platform/scripts/lib/canonical-tool-names.mjs +130 -26
- package/payload/platform/scripts/lib/task-id-citation.mjs +67 -0
- package/payload/platform/.docs/search-surface-contract.md +0 -58
- package/payload/platform/docs/superpowers/plans/2026-06-02-task-610-follower-202-retry.md +0 -372
- package/payload/platform/docs/superpowers/plans/2026-06-04-public-agent-knowledge-delivery.md +0 -230
- package/payload/platform/docs/superpowers/plans/2026-06-23-account-filesystem-schema.md +0 -544
- package/payload/platform/docs/superpowers/plans/2026-07-11-task-1557-operator-on-behalf-preference-attribution.md +0 -542
- package/payload/platform/docs/superpowers/plans/2026-07-13-account-schema-ontology-projection.md +0 -547
- package/payload/platform/docs/superpowers/plans/2026-07-14-graph-top-level-labels-ontology-single-source.md +0 -458
- package/payload/platform/docs/superpowers/plans/2026-07-17-task-1736-retire-mcp-stderr-tee.md +0 -397
- package/payload/platform/docs/superpowers/plans/2026-07-18-graph-native-ledger.md +0 -807
- package/payload/platform/docs/superpowers/plans/2026-07-18-ledger-write-path-fix.md +0 -109
- package/payload/platform/docs/superpowers/plans/2026-07-20-storage-pages-create.md +0 -82
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1704-data-portal-standing-audit.md +0 -673
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +0 -1244
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +0 -462
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +0 -321
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1829-platform-ui-typecheck.md +0 -830
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +0 -2177
- package/payload/platform/docs/superpowers/plans/2026-07-21-graph-caption-resolver.md +0 -124
- package/payload/platform/docs/superpowers/plans/2026-07-21-task-1877-bash-schema-enforcement.md +0 -107
- package/payload/platform/docs/superpowers/plans/2026-07-21-task-1887-reconcile-allowed-top-level.md +0 -205
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1899-reconcile-report-only.md +0 -748
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1910-two-way-portal-exchange.md +0 -1716
- package/payload/platform/docs/superpowers/plans/2026-07-23-task-1930-adherence-enforcement.md +0 -422
- package/payload/platform/docs/superpowers/plans/2026-07-24-task-1942-subaccount-switcher-brand-head.md +0 -327
- package/payload/platform/docs/superpowers/plans/2026-07-25-task-1974-uploads-intake-inbox.md +0 -235
- package/payload/platform/docs/superpowers/plans/2026-07-25-task-1976-intra-folder-hygiene.md +0 -215
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-1902-account-owned-entry-declarations.md +0 -555
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-1926-portal-push-concurrency-guard.md +0 -482
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-2023-sidebar-sessions-async-reads.md +0 -394
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2016-disabled-agent-routing.md +0 -624
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2028-declared-file-write-deny.md +0 -303
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2052-account-machinery-write-fence.md +0 -250
- package/payload/platform/docs/superpowers/plans/2026-07-28-task-2097-email-signature-set.md +0 -65
- package/payload/platform/docs/superpowers/specs/2026-06-02-task-610-follower-202-retry-design.md +0 -116
- package/payload/platform/docs/superpowers/specs/2026-06-23-account-filesystem-schema-design.md +0 -170
- package/payload/platform/docs/superpowers/specs/2026-07-11-task-1557-operator-on-behalf-preference-attribution-design.md +0 -90
- package/payload/platform/docs/superpowers/specs/2026-07-13-account-schema-ontology-projection-design.md +0 -178
- package/payload/platform/docs/superpowers/specs/2026-07-14-graph-top-level-labels-ontology-single-source-design.md +0 -119
- package/payload/platform/docs/superpowers/specs/2026-07-17-task-1736-retire-mcp-stderr-tee-design.md +0 -128
- package/payload/platform/docs/superpowers/specs/2026-07-18-graph-native-ledger-design.md +0 -241
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1704-data-portal-standing-audit-design.md +0 -177
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +0 -201
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +0 -217
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +0 -80
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1820-cpu-triage-admin-tools-design.md +0 -97
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1829-platform-ui-typecheck-design.md +0 -163
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +0 -163
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1899-reconcile-report-only-design.md +0 -121
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1910-two-way-portal-exchange-design.md +0 -103
- package/payload/platform/docs/superpowers/specs/2026-07-23-task-1930-adherence-enforcement-design.md +0 -81
- package/payload/platform/docs/superpowers/specs/2026-07-24-task-1942-subaccount-switcher-brand-head-design.md +0 -35
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-1902-account-owned-entry-declarations-design.md +0 -232
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-1926-portal-push-concurrency-guard-design.md +0 -200
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-2014-ui-suite-nondeterminism-design.md +0 -169
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2016-disabled-agent-routing-design.md +0 -139
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2028-declared-file-write-deny-design.md +0 -127
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2052-account-machinery-write-fence-design.md +0 -150
- package/payload/platform/docs/superpowers/specs/2026-07-28-task-2097-email-signature-set-design.md +0 -155
package/payload/platform/docs/superpowers/plans/2026-07-23-task-1930-adherence-enforcement.md
DELETED
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
# Task 1930 — Schema-and-preference adherence enforcement — Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
-
|
|
5
|
-
**Goal:** Hard-block customer-facing document deliverables that go out without the account's layer-2 preferences being consulted that turn, and make placement drift (`output/` scratch domination) a visible signal.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Two new admin hooks (a PreToolUse gate on document-deliverable sends, a UserPromptSubmit reminder), plus spec edits to `SCHEMA.md` and the `data-manager` reconcile brief, plus registration in the account-provisioning settings writer. The periodic scheduling of the reconcile audit is deferred to a filed Task 1931.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Bash + python3 hooks (Claude Code hook protocol), markdown specs. No TypeScript, no npm build.
|
|
10
|
-
|
|
11
|
-
## Global Constraints
|
|
12
|
-
|
|
13
|
-
- All work under `maxy-code/` (this is a `getmaxy` worktree; the subtree root is `maxy-code/`). Run commands from `maxy-code/`.
|
|
14
|
-
- Hook contract: exit 0 allow, exit 2 block (PreToolUse) / exit 0 always (UserPromptSubmit). Fail OPEN on tty, empty stdin, missing `python3`, or missing/unreadable transcript. A broken hook that blocks everything is worse than the miss.
|
|
15
|
-
- No task numbers or internal refs in any operator-visible (stderr block / injected) string.
|
|
16
|
-
- British English, plain hyphens, no em-dashes in any shipped markdown or block message.
|
|
17
|
-
- New hook tests join the existing tree suite at `platform/plugins/admin/hooks/__tests__/` (they are committed, not ephemeral — the project's hook tests live there).
|
|
18
|
-
- Every commit carries `Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>` and a `Session:` trailer.
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
### Task 1: Preference-consult gate hook
|
|
23
|
-
|
|
24
|
-
**Files:**
|
|
25
|
-
- Create: `platform/plugins/admin/hooks/preference-consult-gate.sh`
|
|
26
|
-
- Test: `platform/plugins/admin/hooks/__tests__/preference-consult-gate.test.sh`
|
|
27
|
-
|
|
28
|
-
**Interfaces:**
|
|
29
|
-
- Consumes: the Claude Code PreToolUse JSON envelope on stdin (`{tool_name, tool_input, transcript_path, ...}`).
|
|
30
|
-
- Produces: exit 0 (allow) / exit 2 (block). Stderr `[preference-gate] op=allow|bypass tool=<name>`.
|
|
31
|
-
|
|
32
|
-
- [ ] **Step 1: Write the failing test**
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
# platform/plugins/admin/hooks/__tests__/preference-consult-gate.test.sh
|
|
36
|
-
#!/usr/bin/env bash
|
|
37
|
-
set -uo pipefail
|
|
38
|
-
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
39
|
-
HOOK="$DIR/../preference-consult-gate.sh"
|
|
40
|
-
PASS=0; FAIL=0; FAILED=()
|
|
41
|
-
run() { printf '%s' "$1" | bash "$HOOK" >/dev/null 2>&1; echo $?; }
|
|
42
|
-
assert_exit() { local got; got=$(run "$2"); if [ "$got" = "$1" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED+=("$3: want exit $1 got $got"); fi; }
|
|
43
|
-
|
|
44
|
-
TMP="$(mktemp -d)"
|
|
45
|
-
# Transcript WITH a profile-read after the last user message.
|
|
46
|
-
CONSULTED="$TMP/consulted.jsonl"
|
|
47
|
-
{
|
|
48
|
-
printf '%s\n' '{"type":"user","message":{"role":"user","content":"make the quote"}}'
|
|
49
|
-
printf '%s\n' '{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","name":"mcp__plugin_memory_memory__profile-read","input":{}}]}}'
|
|
50
|
-
} > "$CONSULTED"
|
|
51
|
-
# Transcript with NO profile-read this turn (one appears BEFORE the last user msg only).
|
|
52
|
-
UNCONSULTED="$TMP/unconsulted.jsonl"
|
|
53
|
-
{
|
|
54
|
-
printf '%s\n' '{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","name":"mcp__plugin_memory_memory__profile-read","input":{}}]}}'
|
|
55
|
-
printf '%s\n' '{"type":"user","message":{"role":"user","content":"send it"}}'
|
|
56
|
-
printf '%s\n' '{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"ok"}]}}'
|
|
57
|
-
} > "$UNCONSULTED"
|
|
58
|
-
|
|
59
|
-
pdf_env() { printf '{"tool_name":"mcp__plugin_browser_browser__browser-pdf-save","tool_input":{"path":"memory/users/+447700900000/documents/quote.pdf"},"transcript_path":"%s"}' "$1"; }
|
|
60
|
-
email_att_env() { printf '{"tool_name":"mcp__plugin_email_email__email-send","tool_input":{"attachments":["memory/users/+44/documents/x.pdf"]},"transcript_path":"%s"}' "$1"; }
|
|
61
|
-
|
|
62
|
-
# Gated tool + consulted → allow.
|
|
63
|
-
assert_exit 0 "$(pdf_env "$CONSULTED")" "pdf consulted allows"
|
|
64
|
-
# Gated tool + not consulted → block.
|
|
65
|
-
assert_exit 2 "$(pdf_env "$UNCONSULTED")" "pdf unconsulted blocks"
|
|
66
|
-
# Email with attachment + not consulted → block.
|
|
67
|
-
assert_exit 2 "$(email_att_env "$UNCONSULTED")" "email+attach unconsulted blocks"
|
|
68
|
-
# Email with NO attachment → not a document deliverable → allow.
|
|
69
|
-
assert_exit 0 "$(printf '{"tool_name":"mcp__plugin_email_email__email-send","tool_input":{},"transcript_path":"%s"}' "$UNCONSULTED")" "email no-attach allows"
|
|
70
|
-
# browser-pdf-save OUTSIDE customer documents scope → allow.
|
|
71
|
-
assert_exit 0 "$(printf '{"tool_name":"mcp__plugin_browser_browser__browser-pdf-save","tool_input":{"path":"output/report.pdf"},"transcript_path":"%s"}' "$UNCONSULTED")" "pdf outside scope allows"
|
|
72
|
-
# Non-send tool → allow.
|
|
73
|
-
assert_exit 0 "$(printf '{"tool_name":"Read","tool_input":{"file_path":"x"},"transcript_path":"%s"}' "$UNCONSULTED")" "read allows"
|
|
74
|
-
# Empty stdin → fail open (allow).
|
|
75
|
-
assert_exit 0 "" "empty stdin fails open"
|
|
76
|
-
|
|
77
|
-
echo "----- $PASS passed, $FAIL failed -----"
|
|
78
|
-
for f in "${FAILED[@]:-}"; do [ -n "$f" ] && echo " $f"; done
|
|
79
|
-
[ "$FAIL" -eq 0 ]
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
- [ ] **Step 2: Run it, verify it fails** — `bash platform/plugins/admin/hooks/__tests__/preference-consult-gate.test.sh` — Expected: FAIL (hook does not exist).
|
|
83
|
-
|
|
84
|
-
- [ ] **Step 3: Write the hook**
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
#!/usr/bin/env bash
|
|
88
|
-
# preference-consult-gate — PreToolUse gate on customer-facing document deliverables.
|
|
89
|
-
#
|
|
90
|
-
# A customer document (a PDF/file to the customer documents scope, or an email/
|
|
91
|
-
# Outlook send carrying an attachment) must not go out until the account's saved
|
|
92
|
-
# layer-2 preferences were consulted this turn, proven by a profile-read tool
|
|
93
|
-
# call after the last user message in the session transcript. Layer-2 records
|
|
94
|
-
# hold the signature policy, header, naming and styling rules the fixed layer-1
|
|
95
|
-
# block does not; a send with no profile-read this turn is the miss that dropped
|
|
96
|
-
# a customer signature.
|
|
97
|
-
#
|
|
98
|
-
# Scope (document-deliverable shape only; every other tool/shape exits 0):
|
|
99
|
-
# *browser-pdf-save path under memory/users/<phone>/documents/
|
|
100
|
-
# SendUserFile path under memory/users/<phone>/documents/ or .pdf/.html/.docx
|
|
101
|
-
# *email-send|reply|draft-send / *outlook-mail-send|reply|draft-send non-empty attachments
|
|
102
|
-
#
|
|
103
|
-
# Exit: 0 allow, 2 block. Fail OPEN on tty/empty stdin/no python3/transcript
|
|
104
|
-
# unreadable. Block log: [preference-gate] op=bypass tool=<name> detail=no profile-read this turn
|
|
105
|
-
set -uo pipefail
|
|
106
|
-
|
|
107
|
-
if [ -t 0 ]; then exit 0; fi
|
|
108
|
-
INPUT=$(cat)
|
|
109
|
-
[ -z "$INPUT" ] && exit 0
|
|
110
|
-
command -v python3 >/dev/null 2>&1 || exit 0
|
|
111
|
-
|
|
112
|
-
DECISION=$(INPUT="$INPUT" python3 - <<'PY' 2>/dev/null || true
|
|
113
|
-
import os, json, re, sys
|
|
114
|
-
try:
|
|
115
|
-
d = json.loads(os.environ["INPUT"])
|
|
116
|
-
except Exception:
|
|
117
|
-
sys.exit(0)
|
|
118
|
-
tool = d.get("tool_name", "") or ""
|
|
119
|
-
ti = d.get("tool_input", {}) or {}
|
|
120
|
-
|
|
121
|
-
def under_customer_docs(p):
|
|
122
|
-
return bool(re.search(r'(^|/)memory/users/[^/]+/documents/', os.path.normpath(p or "")))
|
|
123
|
-
|
|
124
|
-
gated = False
|
|
125
|
-
if tool.endswith("browser-pdf-save"):
|
|
126
|
-
gated = under_customer_docs(ti.get("path", ""))
|
|
127
|
-
elif tool == "SendUserFile" or tool.endswith("__SendUserFile"):
|
|
128
|
-
p = ti.get("path", "") or ti.get("file_path", "")
|
|
129
|
-
gated = under_customer_docs(p) or p.lower().endswith((".pdf", ".html", ".docx"))
|
|
130
|
-
elif re.search(r'(email-send|email-reply|email-draft-send|outlook-mail-send|outlook-mail-reply|outlook-draft-send)$', tool):
|
|
131
|
-
atts = ti.get("attachments")
|
|
132
|
-
gated = isinstance(atts, list) and len(atts) > 0
|
|
133
|
-
|
|
134
|
-
if not gated:
|
|
135
|
-
print("") # not a document deliverable
|
|
136
|
-
sys.exit(0)
|
|
137
|
-
|
|
138
|
-
transcript = d.get("transcript_path", "") or ""
|
|
139
|
-
if not transcript or not os.access(transcript, os.R_OK):
|
|
140
|
-
print("") # cannot inspect consultation → fail open
|
|
141
|
-
sys.exit(0)
|
|
142
|
-
|
|
143
|
-
try:
|
|
144
|
-
with open(transcript) as f:
|
|
145
|
-
lines = f.readlines()
|
|
146
|
-
except Exception:
|
|
147
|
-
print("")
|
|
148
|
-
sys.exit(0)
|
|
149
|
-
|
|
150
|
-
last_user = -1
|
|
151
|
-
for i, ln in enumerate(lines):
|
|
152
|
-
try:
|
|
153
|
-
e = json.loads(ln)
|
|
154
|
-
except Exception:
|
|
155
|
-
continue
|
|
156
|
-
msg = e.get("message", e)
|
|
157
|
-
role = e.get("role") or (msg.get("role") if isinstance(msg, dict) else None)
|
|
158
|
-
if role == "user" or e.get("type") == "user":
|
|
159
|
-
last_user = i
|
|
160
|
-
|
|
161
|
-
consulted = False
|
|
162
|
-
for ln in (lines[last_user + 1:] if last_user >= 0 else lines):
|
|
163
|
-
try:
|
|
164
|
-
e = json.loads(ln)
|
|
165
|
-
except Exception:
|
|
166
|
-
continue
|
|
167
|
-
msg = e.get("message", e)
|
|
168
|
-
content = msg.get("content") if isinstance(msg, dict) else None
|
|
169
|
-
if isinstance(content, list):
|
|
170
|
-
for block in content:
|
|
171
|
-
if isinstance(block, dict) and block.get("type") == "tool_use" and (block.get("name", "") or "").endswith("profile-read"):
|
|
172
|
-
consulted = True
|
|
173
|
-
|
|
174
|
-
print("%s|%s" % (tool, "yes" if consulted else "no"))
|
|
175
|
-
PY
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
[ -z "$DECISION" ] && exit 0
|
|
179
|
-
TOOL="${DECISION%%|*}"
|
|
180
|
-
CONSULTED="${DECISION##*|}"
|
|
181
|
-
|
|
182
|
-
if [ "$CONSULTED" = "yes" ]; then
|
|
183
|
-
echo "[preference-gate] op=allow tool=$TOOL consulted=true" >&2
|
|
184
|
-
exit 0
|
|
185
|
-
fi
|
|
186
|
-
|
|
187
|
-
echo "[preference-gate] op=bypass tool=$TOOL detail=no profile-read this turn" >&2
|
|
188
|
-
echo "Blocked: this customer document is about to go out without the account's saved preferences being checked this turn. Those records hold the signature, header, naming and styling rules. Run profile-read for this account, apply anything relevant, then send again." >&2
|
|
189
|
-
exit 2
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
- [ ] **Step 4: Make it executable and run the test** — `chmod +x platform/plugins/admin/hooks/preference-consult-gate.sh && bash platform/plugins/admin/hooks/__tests__/preference-consult-gate.test.sh` — Expected: `7 passed, 0 failed`.
|
|
193
|
-
|
|
194
|
-
- [ ] **Step 5: Commit**
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
git add platform/plugins/admin/hooks/preference-consult-gate.sh platform/plugins/admin/hooks/__tests__/preference-consult-gate.test.sh
|
|
198
|
-
git commit -m "feat: preference-consult gate blocks customer documents sent without a profile-read this turn"
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
### Task 2: Preference-consult wrapper injector
|
|
204
|
-
|
|
205
|
-
**Files:**
|
|
206
|
-
- Create: `platform/plugins/admin/hooks/preference-consult-directive.sh`
|
|
207
|
-
- Test: `platform/plugins/admin/hooks/__tests__/preference-consult-directive.test.sh`
|
|
208
|
-
|
|
209
|
-
**Interfaces:**
|
|
210
|
-
- Consumes: UserPromptSubmit envelope on stdin (ignored; block is prompt-independent).
|
|
211
|
-
- Produces: stdout JSON `{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"<preference-adherence>...</preference-adherence>"}}`; always exit 0.
|
|
212
|
-
|
|
213
|
-
- [ ] **Step 1: Write the failing test**
|
|
214
|
-
|
|
215
|
-
```bash
|
|
216
|
-
#!/usr/bin/env bash
|
|
217
|
-
set -uo pipefail
|
|
218
|
-
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
219
|
-
HOOK="$DIR/../preference-consult-directive.sh"
|
|
220
|
-
PASS=0; FAIL=0; FAILED=()
|
|
221
|
-
OUT=$(printf '{"session_id":"x","prompt":"hi"}' | bash "$HOOK" 2>/dev/null); RC=$?
|
|
222
|
-
check() { if eval "$1"; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED+=("$2"); fi; }
|
|
223
|
-
check '[ "$RC" = "0" ]' "exit 0"
|
|
224
|
-
check 'printf "%s" "$OUT" | grep -q "hookSpecificOutput"' "emits envelope"
|
|
225
|
-
check 'printf "%s" "$OUT" | grep -q "profile-read"' "names profile-read"
|
|
226
|
-
check 'printf "%s" "$OUT" | grep -q "output/"' "names output scratch rule"
|
|
227
|
-
check 'printf "%s" "$OUT" | grep -qv "—"' "no em-dash"
|
|
228
|
-
echo "----- $PASS passed, $FAIL failed -----"
|
|
229
|
-
for f in "${FAILED[@]:-}"; do [ -n "$f" ] && echo " $f"; done
|
|
230
|
-
[ "$FAIL" -eq 0 ]
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
- [ ] **Step 2: Run it, verify it fails** — Expected: FAIL (hook absent).
|
|
234
|
-
|
|
235
|
-
- [ ] **Step 3: Write the hook**
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
#!/usr/bin/env bash
|
|
239
|
-
# preference-consult-directive — UserPromptSubmit hook. Injects a standing line
|
|
240
|
-
# naming the two-layer preference architecture and two adherence directives:
|
|
241
|
-
# consult layer-2 preferences (profile-read) before any customer document, and
|
|
242
|
-
# promote finished deliverables out of output/. Prompt-independent; fail-open.
|
|
243
|
-
set -uo pipefail
|
|
244
|
-
HOOK_INPUT=$(cat 2>/dev/null || true)
|
|
245
|
-
command -v python3 >/dev/null 2>&1 || exit 0
|
|
246
|
-
python3 - <<'PY' 2>/dev/null || exit 0
|
|
247
|
-
import json
|
|
248
|
-
ctx = (
|
|
249
|
-
"<preference-adherence>\n"
|
|
250
|
-
"Your preferences live in two layers. Layer 1 is the fixed block already in front of you every turn. "
|
|
251
|
-
"Layer 2 is the account's own saved records, read with profile-read; it holds the signature policy, header, "
|
|
252
|
-
"naming conventions and styling that layer 1 does not, and it grows over time. Before any customer-facing "
|
|
253
|
-
"document deliverable (a PDF or file sent to the customer, or an email or Outlook message carrying an "
|
|
254
|
-
"attachment), read the relevant layer-2 preferences with profile-read and apply them. "
|
|
255
|
-
"Store finished deliverables under documents/ or the project folder. output/ is scratch of last resort, "
|
|
256
|
-
"rebuilt on the next tool write, and never the home a graph reference points at.\n"
|
|
257
|
-
"</preference-adherence>"
|
|
258
|
-
)
|
|
259
|
-
print(json.dumps({"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": ctx}}))
|
|
260
|
-
PY
|
|
261
|
-
echo "[pref-wrapper] op=inject" >&2
|
|
262
|
-
exit 0
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
- [ ] **Step 4: Run the test** — `bash platform/plugins/admin/hooks/__tests__/preference-consult-directive.test.sh` — Expected: `5 passed, 0 failed`.
|
|
266
|
-
|
|
267
|
-
- [ ] **Step 5: Commit**
|
|
268
|
-
|
|
269
|
-
```bash
|
|
270
|
-
git add platform/plugins/admin/hooks/preference-consult-directive.sh platform/plugins/admin/hooks/__tests__/preference-consult-directive.test.sh
|
|
271
|
-
git commit -m "feat: preference-consult UserPromptSubmit directive names the two-layer architecture and output-scratch rule"
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
---
|
|
275
|
-
|
|
276
|
-
### Task 3: Register both hooks in account provisioning
|
|
277
|
-
|
|
278
|
-
**Files:**
|
|
279
|
-
- Modify: `platform/scripts/lib/provision-account-dir.sh` (the `SETTINGS_EOF` hooks block, around lines 63-178)
|
|
280
|
-
|
|
281
|
-
**Interfaces:**
|
|
282
|
-
- Consumes: the hook scripts from Tasks 1-2 at `$HOOKS_PATH`.
|
|
283
|
-
- Produces: a `.claude/settings.json` with PreToolUse matchers for the document-send tools routed to `preference-consult-gate.sh`, and `preference-consult-directive.sh` in the UserPromptSubmit list.
|
|
284
|
-
|
|
285
|
-
- [ ] **Step 1: Write the failing test** (asserts the rendered settings block references both hooks)
|
|
286
|
-
|
|
287
|
-
```bash
|
|
288
|
-
#!/usr/bin/env bash
|
|
289
|
-
set -uo pipefail
|
|
290
|
-
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
291
|
-
LIB="$DIR/../lib/provision-account-dir.sh"
|
|
292
|
-
PASS=0; FAIL=0; FAILED=()
|
|
293
|
-
c(){ if grep -qF "$1" "$LIB"; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED+=("$2"); fi; }
|
|
294
|
-
c 'preference-consult-gate.sh' "gate registered"
|
|
295
|
-
c 'preference-consult-directive.sh' "directive registered"
|
|
296
|
-
c 'mcp__plugin_email_email__email-send' "email matcher present"
|
|
297
|
-
c 'mcp__plugin_outlook_outlook__outlook-mail-send' "outlook matcher present"
|
|
298
|
-
echo "----- $PASS passed, $FAIL failed -----"
|
|
299
|
-
for f in "${FAILED[@]:-}"; do [ -n "$f" ] && echo " $f"; done
|
|
300
|
-
[ "$FAIL" -eq 0 ]
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
Save as `platform/scripts/__tests__/preference-hooks-registered.test.sh`.
|
|
304
|
-
|
|
305
|
-
- [ ] **Step 2: Run it, verify it fails** — Expected: FAIL (strings absent).
|
|
306
|
-
|
|
307
|
-
- [ ] **Step 3: Edit the settings block.** In the `"PreToolUse"` array, after the `browser-pdf-save` → `quote-render-gate.sh` entry, add matcher entries (one per document-send tool) routed to `preference-consult-gate.sh`, plus a second `browser-pdf-save` hook entry so both gates run on it. Exact JSON to insert inside `"PreToolUse": [ ... ]`:
|
|
308
|
-
|
|
309
|
-
```json
|
|
310
|
-
{
|
|
311
|
-
"matcher": "mcp__plugin_browser_browser__browser-pdf-save",
|
|
312
|
-
"hooks": [
|
|
313
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/preference-consult-gate.sh" }
|
|
314
|
-
]
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
"matcher": "mcp__plugin_email_email__email-send|mcp__plugin_email_email__email-reply|mcp__plugin_email_email__email-draft-send|mcp__plugin_outlook_outlook__outlook-mail-send|mcp__plugin_outlook_outlook__outlook-mail-reply|mcp__plugin_outlook_outlook__outlook-draft-send|SendUserFile",
|
|
318
|
-
"hooks": [
|
|
319
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/preference-consult-gate.sh" }
|
|
320
|
-
]
|
|
321
|
-
}
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
In the `"UserPromptSubmit"` array's single `hooks` list, append after `datetime-inject.sh`:
|
|
325
|
-
|
|
326
|
-
```json
|
|
327
|
-
,{ "type": "command", "command": "bash $HOOKS_PATH/preference-consult-directive.sh" }
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
- [ ] **Step 4: Run the test + JSON validity check.**
|
|
331
|
-
|
|
332
|
-
Run: `bash platform/scripts/__tests__/preference-hooks-registered.test.sh` — Expected: `4 passed, 0 failed`.
|
|
333
|
-
Then confirm the emitted settings JSON parses. Extract the heredoc body with the placeholders stubbed and pipe through `python3 -m json.tool`:
|
|
334
|
-
|
|
335
|
-
```bash
|
|
336
|
-
HOOKS_PATH=/x PLATFORM_ROOT=/x ACCOUNT_DIR=/x \
|
|
337
|
-
python3 - <<'PY'
|
|
338
|
-
import re, os, json
|
|
339
|
-
s = open("platform/scripts/lib/provision-account-dir.sh").read()
|
|
340
|
-
body = s.split("SETTINGS_EOF", 2)[1] if "SETTINGS_EOF" in s else ""
|
|
341
|
-
body = re.sub(r'\$\{?[A-Z_]+\}?', '/x', body)
|
|
342
|
-
json.loads(body); print("settings JSON valid")
|
|
343
|
-
PY
|
|
344
|
-
```
|
|
345
|
-
Expected: `settings JSON valid`.
|
|
346
|
-
|
|
347
|
-
- [ ] **Step 5: Commit**
|
|
348
|
-
|
|
349
|
-
```bash
|
|
350
|
-
git add platform/scripts/lib/provision-account-dir.sh platform/scripts/__tests__/preference-hooks-registered.test.sh
|
|
351
|
-
git commit -m "feat: register preference-consult gate and directive in account provisioning settings"
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
---
|
|
355
|
-
|
|
356
|
-
### Task 4: Spec edits — SCHEMA.md and data-manager reconcile brief
|
|
357
|
-
|
|
358
|
-
**Files:**
|
|
359
|
-
- Modify: `platform/templates/account-schema/SCHEMA.md` (tool-owned section, lines 29-42)
|
|
360
|
-
- Modify: `platform/templates/specialists/agents/data-manager.md` (reconcile audit brief + output contract)
|
|
361
|
-
|
|
362
|
-
**Interfaces:** documentation only; consumed by the agent and the `data-manager` specialist at runtime.
|
|
363
|
-
|
|
364
|
-
- [ ] **Step 1: Edit `SCHEMA.md`.** In the tool-owned paragraph, after the sentence listing `output/` as recreated on the next write, add:
|
|
365
|
-
|
|
366
|
-
> Finished deliverables must be promoted out of `output/` into `documents/` or `projects/<name>/` through the `data-manager` specialist; `output/` is scratch of last resort, and a graph reference must never point into it (the next tool write rebuilds the tree and the reference dangles).
|
|
367
|
-
|
|
368
|
-
- [ ] **Step 2: Edit `data-manager.md`.** In "The reconcile audit brief" paragraph, extend the counted set: after "(b) graph file-references that do not resolve to a real path", add "(c) graph file-references that resolve into a tool-owned scratch dir (`output/`, `generated/`, `extracted/`, `url-get/`), which the next tool write rebuilds so the reference is fragile by construction, and (d) deliverables a node references that resolve only under a scratch dir and were never promoted to `documents/` or `projects/`." In the "Output contract" paragraph, change the reconcile line to: `unreachable=N broken-refs=M scratch-refs=P stranded=Q`.
|
|
369
|
-
|
|
370
|
-
- [ ] **Step 3: Verify the edits landed**
|
|
371
|
-
|
|
372
|
-
```bash
|
|
373
|
-
grep -q "scratch of last resort" platform/templates/account-schema/SCHEMA.md && \
|
|
374
|
-
grep -q "scratch-refs=P stranded=Q" platform/templates/specialists/agents/data-manager.md && echo "spec edits present"
|
|
375
|
-
```
|
|
376
|
-
Expected: `spec edits present`.
|
|
377
|
-
|
|
378
|
-
- [ ] **Step 4: Commit**
|
|
379
|
-
|
|
380
|
-
```bash
|
|
381
|
-
git add platform/templates/account-schema/SCHEMA.md platform/templates/specialists/agents/data-manager.md
|
|
382
|
-
git commit -m "docs: SCHEMA promote-out-of-output rule and data-manager reconcile counts scratch-refs and stranded deliverables"
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
---
|
|
386
|
-
|
|
387
|
-
### Task 5: Plugin and reference docs + file deferred Task 1931
|
|
388
|
-
|
|
389
|
-
**Files:**
|
|
390
|
-
- Modify: `platform/plugins/admin/PLUGIN.md` (hooks section — add both hooks)
|
|
391
|
-
- Modify: `.docs/` (the relevant enforcement/hooks doc) and `platform/plugins/docs/references/` (the shipped guide) as the sprint doc-gate requires
|
|
392
|
-
- Create: `.tasks/backlog/1931-make-reconcile-audit-run-on-a-standing-periodic-cadence.md`
|
|
393
|
-
- Modify: `.tasks/LANES.md` (Ready row for 1931)
|
|
394
|
-
|
|
395
|
-
- [ ] **Step 1: PLUGIN.md.** In the hooks list (near the `quote-render-gate` entry ~line 190), add an entry for `preference-consult-gate.sh` (PreToolUse matcher set = the document-send tools; block condition = document deliverable with no `profile-read` after the last user message; block message and `[preference-gate] op=bypass` log line; fail-open cases) and for `preference-consult-directive.sh` (UserPromptSubmit; injects the two-layer directive; `[pref-wrapper] op=inject`).
|
|
396
|
-
|
|
397
|
-
- [ ] **Step 2: `.docs/` + references.** Add the two hooks to whichever `.docs/` file documents the admin hook surface and to the shipped `platform/plugins/docs/references/` guide that lists enforcement behaviour. Locate them by grep: `grep -rl "fs-schema-guard\|quote-render-gate" .docs platform/plugins/docs/references`.
|
|
398
|
-
|
|
399
|
-
- [ ] **Step 3: File Task 1931.** Create `.tasks/backlog/1931-make-reconcile-audit-run-on-a-standing-periodic-cadence.md`: problem (the extended reconcile counts scratch-refs and stranded deliverables but only runs on ad-hoc `data-manager` dispatch, so the no-event placement drift is not caught on a standing basis); success (the audit runs on a periodic cadence per account and emits `[reconcile] op=run unreachable=… broken-refs=… scratch-refs=… stranded=…`, with absence-of-run itself a detectable failure); scope in (a scheduler mechanism for the read-only audit, deterministic where possible); scope out (the counts themselves — landed in 1930; auto-promotion). Add its Ready row to `.tasks/LANES.md`.
|
|
400
|
-
|
|
401
|
-
- [ ] **Step 4: Verify** — `test -f .tasks/backlog/1931-make-reconcile-audit-run-on-a-standing-periodic-cadence.md && grep -q "1931" .tasks/LANES.md && grep -q "preference-consult-gate" platform/plugins/admin/PLUGIN.md && echo "docs + task filed"` — Expected: `docs + task filed`.
|
|
402
|
-
|
|
403
|
-
- [ ] **Step 5: Commit**
|
|
404
|
-
|
|
405
|
-
```bash
|
|
406
|
-
git add platform/plugins/admin/PLUGIN.md .docs platform/plugins/docs/references .tasks/backlog/1931-make-reconcile-audit-run-on-a-standing-periodic-cadence.md .tasks/LANES.md
|
|
407
|
-
git commit -m "docs: document preference-consult hooks; file Task 1931 for standing reconcile cadence"
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
---
|
|
411
|
-
|
|
412
|
-
## Verification (Phase 4)
|
|
413
|
-
|
|
414
|
-
- All hook tests pass: `for t in platform/plugins/admin/hooks/__tests__/*.test.sh platform/scripts/__tests__/preference-hooks-registered.test.sh; do bash "$t" | tail -1; done`. The 22 `fs-schema-guard` tests and `quote-render-gate` tests stay green.
|
|
415
|
-
- Settings JSON parses (Task 3 Step 4).
|
|
416
|
-
- **Pi signal verification (mandatory — this sprint touches hook input handling).** After deploy, trigger a customer-document send in admin chat without a `profile-read` that turn and confirm the block; read the transcript JSONL structure on the Pi and confirm the `role:user` boundary and `tool_use` `name` fields match what the test pipes. If the live envelope's `tool_input` field names diverge (e.g. `attachments` vs another key, or the transcript entry shape), fix the hook and the test before proceeding.
|
|
417
|
-
|
|
418
|
-
## Self-Review
|
|
419
|
-
|
|
420
|
-
- **Spec coverage:** gate (Task 1), wrapper (Task 2), registration (Task 3), SCHEMA + data-manager (Task 4), PLUGIN/docs + deferred 1931 (Task 5). All five spec units mapped.
|
|
421
|
-
- **Placeholder scan:** none; every hook and test carries full code.
|
|
422
|
-
- **Type consistency:** the gate emits `op=allow`/`op=bypass` and the wrapper `op=inject` consistently across plan, spec, and PLUGIN entry. The reconcile contract `unreachable=N broken-refs=M scratch-refs=P stranded=Q` is identical in Task 4 and the spec.
|