toga-ai 1.0.803 → 1.0.804
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/.claude/settings.json
CHANGED
|
@@ -70,6 +70,16 @@
|
|
|
70
70
|
"timeout": 3000
|
|
71
71
|
}
|
|
72
72
|
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
76
|
+
"hooks": [
|
|
77
|
+
{
|
|
78
|
+
"type": "command",
|
|
79
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/design-fidelity-reminder.js\"",
|
|
80
|
+
"timeout": 3000
|
|
81
|
+
}
|
|
82
|
+
]
|
|
73
83
|
}
|
|
74
84
|
],
|
|
75
85
|
"PostToolUse": [
|
package/package.json
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* design-fidelity-reminder.js — PRE-EDIT enforcement of the Design Contract discipline.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists: on Desk (and the other 2.0 React apps) the model keeps improvising
|
|
8
|
+
* UI — inventing button text, helper lines, field order and labels instead of matching
|
|
9
|
+
* the designer's on-disk design. That drift ships UIs that "function" but do not match
|
|
10
|
+
* what Matt designed, and the developer has to catch it every time. A rule in a file is
|
|
11
|
+
* passive; over a long session the model slides back to improvising. This is the
|
|
12
|
+
* mechanical backstop, modeled on plain-talk.js.
|
|
13
|
+
*
|
|
14
|
+
* How it works: registered on PreToolUse for Edit|Write|MultiEdit. Just before the model
|
|
15
|
+
* edits a UI source file — a path under a `src/` tree ending .tsx / .jsx / .css — it prints
|
|
16
|
+
* a short <system-reminder> to stdout with the Design Contract rules, so the discipline is
|
|
17
|
+
* in context at the moment the UI is written. On any non-UI file it stays silent.
|
|
18
|
+
*
|
|
19
|
+
* It never blocks. It always exits 0 — a reminder must never stop or deny an edit. It
|
|
20
|
+
* fails open on any error: a crashing nudge must never brick a session. Per team hook
|
|
21
|
+
* convention DENY/error text goes to STDERR — this hook never denies, so it never uses it.
|
|
22
|
+
*
|
|
23
|
+
* Escape hatch: set TOGA_DESIGN_FIDELITY_DISABLED=1 to silence the reminder.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
|
|
28
|
+
/* Read the CC hook payload. PreToolUse delivers the tool input on stdin as
|
|
29
|
+
* { tool_name, tool_input: { file_path, ... } }; fall back to the CLAUDE_TOOL_INPUT env
|
|
30
|
+
* (what the sibling PostToolUse reminders read) and to a top-level file_path. */
|
|
31
|
+
function readFilePath() {
|
|
32
|
+
let raw = '';
|
|
33
|
+
try { raw = fs.readFileSync(0, 'utf8'); } catch (e) { /* no stdin */ }
|
|
34
|
+
|
|
35
|
+
let data = {};
|
|
36
|
+
if (raw && raw.trim()) {
|
|
37
|
+
try { data = JSON.parse(raw); } catch (e) { data = {}; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let fp =
|
|
41
|
+
(data.tool_input && typeof data.tool_input.file_path === 'string' && data.tool_input.file_path) ||
|
|
42
|
+
(typeof data.file_path === 'string' && data.file_path) ||
|
|
43
|
+
'';
|
|
44
|
+
|
|
45
|
+
if (!fp && process.env.CLAUDE_TOOL_INPUT) {
|
|
46
|
+
try {
|
|
47
|
+
const env = JSON.parse(process.env.CLAUDE_TOOL_INPUT);
|
|
48
|
+
if (env && typeof env.file_path === 'string') fp = env.file_path;
|
|
49
|
+
} catch (e) { /* ignore */ }
|
|
50
|
+
}
|
|
51
|
+
return fp;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* A Desk / React-app UI source file: under a `src/` tree AND ending .tsx / .jsx / .css. */
|
|
55
|
+
function isUiSourceFile(filePath) {
|
|
56
|
+
if (!filePath) return false;
|
|
57
|
+
const p = filePath.replace(/\\/g, '/').toLowerCase();
|
|
58
|
+
return /\/src\//.test(p) && /\.(tsx|jsx|css)$/.test(p);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function reminder() {
|
|
62
|
+
return [
|
|
63
|
+
'<system-reminder>',
|
|
64
|
+
'DESIGN CONTRACT — this edit touches a UI source file. Match the designer\'s design,',
|
|
65
|
+
'do not improvise UI. Reusing a shell or making it "function" is NOT fidelity.',
|
|
66
|
+
'',
|
|
67
|
+
' 1. The on-disk design is the authority. Open the exact design source file FIRST and',
|
|
68
|
+
' build from it — match its LAYOUT/placement, not just pixels.',
|
|
69
|
+
' 2. Build to a written Design Contract: every field + its order, each label/placeholder,',
|
|
70
|
+
' required flags, button text, modal size/layout, and the header/footer.',
|
|
71
|
+
' 3. Invent NOTHING user-visible — button text, helper/info lines, labels, required flags.',
|
|
72
|
+
' Copy comes from the design + Surface/settings. If it is not in the design, ASK.',
|
|
73
|
+
' 4. Design-vs-data conflict → STOP and ask. Do not decide it yourself.',
|
|
74
|
+
' 5. No self-sign-off. Hand the developer a labeled diff (mine vs the design). "It',
|
|
75
|
+
' functions" is not fidelity.',
|
|
76
|
+
'</system-reminder>',
|
|
77
|
+
].join('\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function main() {
|
|
81
|
+
if (process.env.TOGA_DESIGN_FIDELITY_DISABLED === '1') process.exit(0);
|
|
82
|
+
|
|
83
|
+
const filePath = readFilePath();
|
|
84
|
+
|
|
85
|
+
if (isUiSourceFile(filePath)) {
|
|
86
|
+
// Inject the reminder into the model's context via the PreToolUse additionalContext
|
|
87
|
+
// channel (raw stdout on PreToolUse only shows in transcript, not context). Non-blocking:
|
|
88
|
+
// additionalContext adds context, it does not deny the edit.
|
|
89
|
+
console.log(JSON.stringify({
|
|
90
|
+
hookSpecificOutput: {
|
|
91
|
+
hookEventName: 'PreToolUse',
|
|
92
|
+
additionalContext: reminder(),
|
|
93
|
+
},
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try { main(); } catch (e) { process.exit(0); } // fail-open: never block or brick an edit
|