stagent 0.7.0 → 0.8.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/package.json +1 -1
- package/src/app/globals.css +14 -0
- package/src/components/book/book-reader.tsx +62 -9
- package/src/components/book/content-blocks.tsx +6 -1
- package/src/components/tables/table-cell-editor.tsx +3 -3
- package/src/components/tables/table-row-sheet.tsx +271 -0
- package/src/components/tables/table-spreadsheet.tsx +55 -160
- package/src/components/workflows/workflow-form-view.tsx +6 -6
- package/src/lib/agents/profiles/builtins/document-writer/SKILL.md +23 -0
- package/src/lib/agents/profiles/builtins/technical-writer/SKILL.md +10 -0
- package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +1 -1
- package/src/lib/book/chapter-generator.ts +81 -5
- package/src/lib/book/chapter-mapping.ts +58 -24
- package/src/lib/book/content.ts +83 -47
- package/src/lib/book/markdown-parser.ts +1 -1
- package/src/lib/book/reading-paths.ts +8 -8
- package/src/lib/book/types.ts +1 -1
- package/src/lib/book/update-detector.ts +4 -1
- package/src/lib/chat/tools/workflow-tools.ts +9 -1
- package/src/lib/workflows/types.ts +1 -1
|
@@ -14,17 +14,14 @@ import { Button } from "@/components/ui/button";
|
|
|
14
14
|
import { Plus } from "lucide-react";
|
|
15
15
|
import { toast } from "sonner";
|
|
16
16
|
import { cn } from "@/lib/utils";
|
|
17
|
-
import {
|
|
17
|
+
import { CellDisplay } from "./table-cell-editor";
|
|
18
18
|
import { SpreadsheetColumnHeader } from "./table-column-header";
|
|
19
19
|
import { TableColumnSheet } from "./table-column-sheet";
|
|
20
20
|
import { TableToolbar } from "./table-toolbar";
|
|
21
21
|
import { TableImportWizard } from "./table-import-wizard";
|
|
22
|
+
import { TableRowSheet } from "./table-row-sheet";
|
|
22
23
|
import { EmptyState } from "@/components/shared/empty-state";
|
|
23
24
|
import { Table2 } from "lucide-react";
|
|
24
|
-
import {
|
|
25
|
-
useSpreadsheetKeys,
|
|
26
|
-
type CellPosition,
|
|
27
|
-
} from "./use-spreadsheet-keys";
|
|
28
25
|
import { evaluateComputedColumns } from "@/lib/tables/computed";
|
|
29
26
|
import type { ColumnDef, SortSpec } from "@/lib/tables/types";
|
|
30
27
|
import type { UserTableRowRow } from "@/lib/db/schema";
|
|
@@ -58,15 +55,12 @@ export function TableSpreadsheet({
|
|
|
58
55
|
}: TableSpreadsheetProps) {
|
|
59
56
|
const [columns, setColumns] = useState<ColumnDef[]>(initialColumns);
|
|
60
57
|
const [rows, setRows] = useState<ParsedRow[]>(() => parseRows(initialRows));
|
|
61
|
-
const [editingCell, setEditingCell] = useState<CellPosition | null>(null);
|
|
62
|
-
const [editValue, setEditValue] = useState<unknown>(null);
|
|
63
58
|
const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set());
|
|
64
59
|
const [sorts, setSorts] = useState<SortSpec[]>([]);
|
|
65
60
|
const [columnSheetOpen, setColumnSheetOpen] = useState(false);
|
|
66
61
|
const [importOpen, setImportOpen] = useState(false);
|
|
67
|
-
const [
|
|
68
|
-
|
|
69
|
-
const debounceTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
|
62
|
+
const [rowSheetOpen, setRowSheetOpen] = useState(false);
|
|
63
|
+
const [rowSheetRow, setRowSheetRow] = useState<ParsedRow | null>(null);
|
|
70
64
|
|
|
71
65
|
// ── Refresh helpers ─────────────────────────────────────────────────
|
|
72
66
|
|
|
@@ -87,116 +81,33 @@ export function TableSpreadsheet({
|
|
|
87
81
|
const res = await fetch(`/api/tables/${tableId}/rows?limit=500${sortsParam}`);
|
|
88
82
|
if (res.ok) {
|
|
89
83
|
const raw = (await res.json()) as UserTableRowRow[];
|
|
90
|
-
// Evaluate computed columns client-side after refresh
|
|
91
84
|
const enriched = evaluateComputedColumns(columns, raw);
|
|
92
85
|
setRows(parseRows(enriched));
|
|
93
86
|
}
|
|
94
87
|
} catch { /* silent */ }
|
|
95
88
|
}, [tableId, sorts, columns]);
|
|
96
89
|
|
|
97
|
-
// ──
|
|
98
|
-
|
|
99
|
-
const getCellValue = useCallback(
|
|
100
|
-
(pos: CellPosition): unknown => {
|
|
101
|
-
const row = rows[pos.rowIndex];
|
|
102
|
-
const col = columns[pos.colIndex];
|
|
103
|
-
if (!row || !col) return null;
|
|
104
|
-
return row.data[col.name] ?? null;
|
|
105
|
-
},
|
|
106
|
-
[rows, columns]
|
|
107
|
-
);
|
|
90
|
+
// ── Boolean toggle (inline for convenience) ─────────────────────────
|
|
108
91
|
|
|
109
|
-
const
|
|
110
|
-
(rowId: string, colName: string,
|
|
92
|
+
const handleToggleBoolean = useCallback(
|
|
93
|
+
(rowId: string, colName: string, newValue: boolean) => {
|
|
111
94
|
setRows((prev) =>
|
|
112
95
|
prev.map((r) =>
|
|
113
96
|
r.id === rowId
|
|
114
|
-
? { ...r, data: { ...r.data, [colName]:
|
|
97
|
+
? { ...r, data: { ...r.data, [colName]: newValue } }
|
|
115
98
|
: r
|
|
116
99
|
)
|
|
117
100
|
);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const key = `${rowId}:${colName}`;
|
|
125
|
-
|
|
126
|
-
// Clear previous timer
|
|
127
|
-
const existing = debounceTimers.current.get(key);
|
|
128
|
-
if (existing) clearTimeout(existing);
|
|
129
|
-
|
|
130
|
-
setPendingSaves((prev) => new Set(prev).add(key));
|
|
131
|
-
|
|
132
|
-
const timer = setTimeout(async () => {
|
|
133
|
-
try {
|
|
134
|
-
const res = await fetch(`/api/tables/${tableId}/rows/${rowId}`, {
|
|
135
|
-
method: "PATCH",
|
|
136
|
-
headers: { "Content-Type": "application/json" },
|
|
137
|
-
body: JSON.stringify({ data: { [colName]: value } }),
|
|
138
|
-
});
|
|
139
|
-
if (!res.ok) {
|
|
140
|
-
toast.error("Failed to save cell");
|
|
141
|
-
// Revert would require storing previous value — for now just notify
|
|
142
|
-
}
|
|
143
|
-
} catch {
|
|
144
|
-
toast.error("Failed to save cell");
|
|
145
|
-
} finally {
|
|
146
|
-
setPendingSaves((prev) => {
|
|
147
|
-
const next = new Set(prev);
|
|
148
|
-
next.delete(key);
|
|
149
|
-
return next;
|
|
150
|
-
});
|
|
151
|
-
debounceTimers.current.delete(key);
|
|
152
|
-
}
|
|
153
|
-
}, 300);
|
|
154
|
-
|
|
155
|
-
debounceTimers.current.set(key, timer);
|
|
101
|
+
// Save immediately
|
|
102
|
+
fetch(`/api/tables/${tableId}/rows/${rowId}`, {
|
|
103
|
+
method: "PATCH",
|
|
104
|
+
headers: { "Content-Type": "application/json" },
|
|
105
|
+
body: JSON.stringify({ data: { [colName]: newValue } }),
|
|
106
|
+
}).catch(() => toast.error("Failed to save"));
|
|
156
107
|
},
|
|
157
108
|
[tableId]
|
|
158
109
|
);
|
|
159
110
|
|
|
160
|
-
// ── Keyboard hook callbacks ─────────────────────────────────────────
|
|
161
|
-
|
|
162
|
-
const handleStartEdit = useCallback(
|
|
163
|
-
(pos: CellPosition) => {
|
|
164
|
-
setEditingCell(pos);
|
|
165
|
-
setEditValue(getCellValue(pos));
|
|
166
|
-
},
|
|
167
|
-
[getCellValue]
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
const handleConfirmEdit = useCallback(() => {
|
|
171
|
-
if (!editingCell) return;
|
|
172
|
-
const row = rows[editingCell.rowIndex];
|
|
173
|
-
const col = columns[editingCell.colIndex];
|
|
174
|
-
if (!row || !col) return;
|
|
175
|
-
|
|
176
|
-
setCellValue(row.id, col.name, editValue);
|
|
177
|
-
debounceSave(row.id, col.name, editValue);
|
|
178
|
-
setEditingCell(null);
|
|
179
|
-
setEditValue(null);
|
|
180
|
-
}, [editingCell, editValue, rows, columns, setCellValue, debounceSave]);
|
|
181
|
-
|
|
182
|
-
const handleCancelEdit = useCallback(() => {
|
|
183
|
-
setEditingCell(null);
|
|
184
|
-
setEditValue(null);
|
|
185
|
-
}, []);
|
|
186
|
-
|
|
187
|
-
const handleClearCell = useCallback(
|
|
188
|
-
(pos: CellPosition) => {
|
|
189
|
-
const row = rows[pos.rowIndex];
|
|
190
|
-
const col = columns[pos.colIndex];
|
|
191
|
-
if (!row || !col) return;
|
|
192
|
-
if (col.dataType === "computed") return;
|
|
193
|
-
|
|
194
|
-
setCellValue(row.id, col.name, null);
|
|
195
|
-
debounceSave(row.id, col.name, null);
|
|
196
|
-
},
|
|
197
|
-
[rows, columns, setCellValue, debounceSave]
|
|
198
|
-
);
|
|
199
|
-
|
|
200
111
|
// ── Row operations ──────────────────────────────────────────────────
|
|
201
112
|
|
|
202
113
|
const handleAddRow = useCallback(async () => {
|
|
@@ -238,6 +149,19 @@ export function TableSpreadsheet({
|
|
|
238
149
|
}
|
|
239
150
|
}, [tableId, selectedRows, refreshRows]);
|
|
240
151
|
|
|
152
|
+
// ── Row sheet ──────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
const handleOpenRowSheet = useCallback((row: ParsedRow) => {
|
|
155
|
+
setRowSheetRow(row);
|
|
156
|
+
setRowSheetOpen(true);
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
const handleRowUpdated = useCallback((rowId: string, data: Record<string, unknown>) => {
|
|
160
|
+
setRows((prev) =>
|
|
161
|
+
prev.map((r) => (r.id === rowId ? { ...r, data } : r))
|
|
162
|
+
);
|
|
163
|
+
}, []);
|
|
164
|
+
|
|
241
165
|
// ── Column operations ───────────────────────────────────────────────
|
|
242
166
|
|
|
243
167
|
const handleSort = useCallback(
|
|
@@ -261,18 +185,6 @@ export function TableSpreadsheet({
|
|
|
261
185
|
refreshRows();
|
|
262
186
|
}
|
|
263
187
|
|
|
264
|
-
// ── Keyboard navigation ─────────────────────────────────────────────
|
|
265
|
-
|
|
266
|
-
const keyboard = useSpreadsheetKeys({
|
|
267
|
-
rowCount: rows.length,
|
|
268
|
-
colCount: columns.length,
|
|
269
|
-
onStartEdit: handleStartEdit,
|
|
270
|
-
onConfirmEdit: handleConfirmEdit,
|
|
271
|
-
onCancelEdit: handleCancelEdit,
|
|
272
|
-
onClearCell: handleClearCell,
|
|
273
|
-
onAddRow: handleAddRow,
|
|
274
|
-
});
|
|
275
|
-
|
|
276
188
|
// ── Selection helpers ───────────────────────────────────────────────
|
|
277
189
|
|
|
278
190
|
function toggleRowSelect(rowId: string) {
|
|
@@ -328,7 +240,7 @@ export function TableSpreadsheet({
|
|
|
328
240
|
}
|
|
329
241
|
|
|
330
242
|
return (
|
|
331
|
-
<div className="space-y-2"
|
|
243
|
+
<div className="space-y-2">
|
|
332
244
|
<TableToolbar
|
|
333
245
|
tableId={tableId}
|
|
334
246
|
rowCount={rows.length}
|
|
@@ -392,12 +304,14 @@ export function TableSpreadsheet({
|
|
|
392
304
|
</TableCell>
|
|
393
305
|
</TableRow>
|
|
394
306
|
) : (
|
|
395
|
-
rows.map((row
|
|
307
|
+
rows.map((row) => (
|
|
396
308
|
<TableRow
|
|
397
309
|
key={row.id}
|
|
398
310
|
className={cn(
|
|
311
|
+
"cursor-pointer",
|
|
399
312
|
selectedRows.has(row.id) && "bg-muted/50"
|
|
400
313
|
)}
|
|
314
|
+
onClick={() => handleOpenRowSheet(row)}
|
|
401
315
|
>
|
|
402
316
|
<TableCell onClick={(e) => e.stopPropagation()}>
|
|
403
317
|
<Checkbox
|
|
@@ -405,56 +319,26 @@ export function TableSpreadsheet({
|
|
|
405
319
|
onCheckedChange={() => toggleRowSelect(row.id)}
|
|
406
320
|
/>
|
|
407
321
|
</TableCell>
|
|
408
|
-
{columns.map((col
|
|
409
|
-
const isActive =
|
|
410
|
-
keyboard.activeCell?.rowIndex === rowIndex &&
|
|
411
|
-
keyboard.activeCell?.colIndex === colIndex;
|
|
412
|
-
const isEditing =
|
|
413
|
-
isActive &&
|
|
414
|
-
editingCell?.rowIndex === rowIndex &&
|
|
415
|
-
editingCell?.colIndex === colIndex;
|
|
322
|
+
{columns.map((col) => {
|
|
416
323
|
const value = row.data[col.name];
|
|
417
|
-
const saveKey = `${row.id}:${col.name}`;
|
|
418
|
-
const isSaving = pendingSaves.has(saveKey);
|
|
419
324
|
|
|
420
325
|
return (
|
|
421
326
|
<TableCell
|
|
422
327
|
key={col.name}
|
|
423
|
-
className=
|
|
424
|
-
"p-0 cursor-cell relative min-w-[120px]",
|
|
425
|
-
isActive && "ring-2 ring-primary ring-inset",
|
|
426
|
-
isSaving && "bg-primary/5"
|
|
427
|
-
)}
|
|
428
|
-
onClick={() =>
|
|
429
|
-
keyboard.handleCellClick({ rowIndex, colIndex })
|
|
430
|
-
}
|
|
431
|
-
onDoubleClick={() =>
|
|
432
|
-
keyboard.handleCellDoubleClick({ rowIndex, colIndex })
|
|
433
|
-
}
|
|
328
|
+
className="min-w-[120px]"
|
|
434
329
|
>
|
|
435
330
|
<div className="px-2 py-1 min-h-[32px] flex items-center">
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
value={value}
|
|
448
|
-
onToggleBoolean={
|
|
449
|
-
col.dataType === "boolean"
|
|
450
|
-
? (newVal) => {
|
|
451
|
-
setCellValue(row.id, col.name, newVal);
|
|
452
|
-
debounceSave(row.id, col.name, newVal);
|
|
453
|
-
}
|
|
454
|
-
: undefined
|
|
455
|
-
}
|
|
456
|
-
/>
|
|
457
|
-
)}
|
|
331
|
+
<CellDisplay
|
|
332
|
+
column={col}
|
|
333
|
+
value={value}
|
|
334
|
+
onToggleBoolean={
|
|
335
|
+
col.dataType === "boolean"
|
|
336
|
+
? (newVal) => {
|
|
337
|
+
handleToggleBoolean(row.id, col.name, newVal);
|
|
338
|
+
}
|
|
339
|
+
: undefined
|
|
340
|
+
}
|
|
341
|
+
/>
|
|
458
342
|
</div>
|
|
459
343
|
</TableCell>
|
|
460
344
|
);
|
|
@@ -494,6 +378,17 @@ export function TableSpreadsheet({
|
|
|
494
378
|
onOpenChange={setImportOpen}
|
|
495
379
|
onImported={() => { refreshTable(); refreshRows(); }}
|
|
496
380
|
/>
|
|
381
|
+
|
|
382
|
+
{rowSheetRow && (
|
|
383
|
+
<TableRowSheet
|
|
384
|
+
tableId={tableId}
|
|
385
|
+
columns={columns}
|
|
386
|
+
row={rowSheetRow}
|
|
387
|
+
open={rowSheetOpen}
|
|
388
|
+
onOpenChange={setRowSheetOpen}
|
|
389
|
+
onRowUpdated={handleRowUpdated}
|
|
390
|
+
/>
|
|
391
|
+
)}
|
|
497
392
|
</div>
|
|
498
393
|
);
|
|
499
394
|
}
|
|
@@ -155,14 +155,14 @@ function normalizeParallelSteps(
|
|
|
155
155
|
|
|
156
156
|
const normalizedBranches = branches.map((branch, index) => ({
|
|
157
157
|
...branch,
|
|
158
|
-
id: options?.cloneIds ? crypto.randomUUID() : branch.id,
|
|
158
|
+
id: options?.cloneIds ? crypto.randomUUID() : (branch.id || crypto.randomUUID()),
|
|
159
159
|
name: branch.name || `Research Branch ${index + 1}`,
|
|
160
160
|
}));
|
|
161
161
|
|
|
162
162
|
const normalizedSynthesis = rawSynthesis
|
|
163
163
|
? {
|
|
164
164
|
...rawSynthesis,
|
|
165
|
-
id: options?.cloneIds ? crypto.randomUUID() : rawSynthesis.id,
|
|
165
|
+
id: options?.cloneIds ? crypto.randomUUID() : (rawSynthesis.id || crypto.randomUUID()),
|
|
166
166
|
name: rawSynthesis.name || "Synthesize findings",
|
|
167
167
|
}
|
|
168
168
|
: undefined;
|
|
@@ -257,7 +257,7 @@ function normalizeSwarmSteps(
|
|
|
257
257
|
id:
|
|
258
258
|
options?.cloneIds && mayorStep
|
|
259
259
|
? crypto.randomUUID()
|
|
260
|
-
: (mayorStep?.id
|
|
260
|
+
: (mayorStep?.id || crypto.randomUUID()),
|
|
261
261
|
name: mayorStep?.name || "Mayor plan",
|
|
262
262
|
};
|
|
263
263
|
|
|
@@ -268,7 +268,7 @@ function normalizeSwarmSteps(
|
|
|
268
268
|
|
|
269
269
|
const normalizedWorkers = nextWorkers.map((worker, index) => ({
|
|
270
270
|
...worker,
|
|
271
|
-
id: options?.cloneIds ? crypto.randomUUID() : worker.id,
|
|
271
|
+
id: options?.cloneIds ? crypto.randomUUID() : (worker.id || crypto.randomUUID()),
|
|
272
272
|
name: worker.name || `Worker ${index + 1}`,
|
|
273
273
|
}));
|
|
274
274
|
|
|
@@ -277,7 +277,7 @@ function normalizeSwarmSteps(
|
|
|
277
277
|
id:
|
|
278
278
|
options?.cloneIds && refineryStep
|
|
279
279
|
? crypto.randomUUID()
|
|
280
|
-
: (refineryStep?.id
|
|
280
|
+
: (refineryStep?.id || crypto.randomUUID()),
|
|
281
281
|
name: refineryStep?.name || "Refine and merge",
|
|
282
282
|
};
|
|
283
283
|
|
|
@@ -472,7 +472,7 @@ export function WorkflowFormView({
|
|
|
472
472
|
? normalizeParallelSteps(def.steps)
|
|
473
473
|
: def.pattern === "swarm"
|
|
474
474
|
? normalizeSwarmSteps(def.steps)
|
|
475
|
-
: def.steps
|
|
475
|
+
: def.steps.map((s) => ({ ...s, id: s.id || crypto.randomUUID() }))
|
|
476
476
|
);
|
|
477
477
|
}
|
|
478
478
|
}
|
|
@@ -14,3 +14,26 @@ You are a technical writer producing clear, well-structured documents.
|
|
|
14
14
|
- Include a table of contents for documents with 3+ sections
|
|
15
15
|
- Highlight action items or decisions needed in bold
|
|
16
16
|
- If writing from a template, preserve the template's style and structure
|
|
17
|
+
|
|
18
|
+
## Book Chapter Conventions
|
|
19
|
+
|
|
20
|
+
When generating AI Native book chapters:
|
|
21
|
+
|
|
22
|
+
- Preserve existing `> [!authors-note]` blocks unchanged during regeneration
|
|
23
|
+
- Preserve existing `> [!case-study]` blocks — update content only if source material changed
|
|
24
|
+
- Include "Building with Stagent" TypeScript code examples with realistic values
|
|
25
|
+
- Include both "Stagent Today" and "Roadmap Vision" sections
|
|
26
|
+
- Use `> [!case-study]` callout format: name the company, describe their pattern, draw parallel to Stagent
|
|
27
|
+
- Follow the Problem → Solution → Implementation → Lessons narrative arc
|
|
28
|
+
- Target the reading time specified in chapter frontmatter (~250 words/min)
|
|
29
|
+
|
|
30
|
+
## Originality and Attribution Rules
|
|
31
|
+
|
|
32
|
+
When writing chapters that reference external case studies (ai-native-notes/ articles):
|
|
33
|
+
|
|
34
|
+
- **Never copy phrases verbatim** from source articles without quotation marks and explicit attribution
|
|
35
|
+
- **Always credit authors by name** in case-study callouts (e.g., "Geoffrey Huntley" not just "Ralph Wiggum", "Dorsey and Botha" not just "Sequoia")
|
|
36
|
+
- **When structuring content around an external framework** (e.g., 8090's five stations, Block's four pillars), explicitly acknowledge the source: "As [Author] describes in [Work]..." before elaborating
|
|
37
|
+
- **Synthesize from multiple sources** rather than mirroring a single article's structure. If one source dominates a section, bring in at least one additional perspective
|
|
38
|
+
- **Make it Stagent's own**: Every external concept should connect to Stagent's concrete implementation or roadmap. Don't just restate what Stripe/Ramp/Harvey built — explain what Stagent builds differently and why
|
|
39
|
+
- **Use direct quotes sparingly** and only for memorable, well-attributed phrases. The majority of prose should be original analysis
|
|
@@ -29,3 +29,13 @@ Structure documentation according to the type requested:
|
|
|
29
29
|
- **ADR**: Title, status (proposed/accepted/deprecated/superseded), context, decision, consequences
|
|
30
30
|
- **README**: Title, description, prerequisites, installation, usage, configuration, contributing, license
|
|
31
31
|
- **Changelog**: Version header, date, categorized entries with PR/issue references where available
|
|
32
|
+
- **Book Chapter Review**: Quality pass on AI Native book chapters — terminology consistency, API accuracy, case study attribution, code example quality, section presence, grammar/style. Edit in-place; do not rewrite.
|
|
33
|
+
|
|
34
|
+
## Originality Checks (Book Chapter Review)
|
|
35
|
+
|
|
36
|
+
During book chapter quality reviews, specifically check for:
|
|
37
|
+
|
|
38
|
+
- **Verbatim copying**: Flag any passage >15 words matching source articles in `ai-native-notes/` that lacks quotation marks
|
|
39
|
+
- **Author attribution**: Every case-study callout must name the author (e.g., "Geoffrey Huntley" not "Ralph Wiggum", "Dorsey and Botha" not "Sequoia")
|
|
40
|
+
- **Structural mirroring**: If a chapter follows a source article's exact progression without acknowledgment, flag it and suggest an explicit framing line ("As [Author] traces in [Work]...")
|
|
41
|
+
- **Single-source dependency**: If >50% of a section's ideas come from one source, flag it and suggest multi-source synthesis
|
|
@@ -2,7 +2,7 @@ id: technical-writer
|
|
|
2
2
|
name: Technical Writer
|
|
3
3
|
version: "1.0.0"
|
|
4
4
|
domain: work
|
|
5
|
-
tags: [documentation, api-docs, adr, readme, changelog, technical-writing]
|
|
5
|
+
tags: [documentation, api-docs, adr, readme, changelog, technical-writing, book-chapter-review]
|
|
6
6
|
supportedRuntimes: [claude-code, openai-codex-app-server, anthropic-direct, openai-direct, ollama]
|
|
7
7
|
preferredRuntime: anthropic-direct
|
|
8
8
|
allowedTools:
|
|
@@ -18,6 +18,7 @@ interface ChapterContext {
|
|
|
18
18
|
relatedJourney: string | undefined;
|
|
19
19
|
currentMarkdown: string | null;
|
|
20
20
|
sourceContents: string[];
|
|
21
|
+
caseStudyContents: string[];
|
|
21
22
|
strategy: string | null;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -50,6 +51,17 @@ export function gatherChapterContext(chapterId: string): ChapterContext {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
// Read case study articles from ai-native-notes/
|
|
55
|
+
const caseStudySlugs = mapping?.caseStudies ?? [];
|
|
56
|
+
const caseStudyContents: string[] = [];
|
|
57
|
+
for (const csSlug of caseStudySlugs) {
|
|
58
|
+
const csPath = join(appRoot, "ai-native-notes", `${csSlug}.md`);
|
|
59
|
+
if (existsSync(csPath)) {
|
|
60
|
+
const content = readFileSync(csPath, "utf-8");
|
|
61
|
+
caseStudyContents.push(`### Case Study: ${csSlug}\n${content}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
// Read the book strategy document
|
|
54
66
|
const strategyPath = join(appRoot, "ai-native-notes", "ai-native-book-strategy.md");
|
|
55
67
|
const strategy = existsSync(strategyPath)
|
|
@@ -69,6 +81,7 @@ export function gatherChapterContext(chapterId: string): ChapterContext {
|
|
|
69
81
|
relatedJourney: chapter.relatedJourney,
|
|
70
82
|
currentMarkdown,
|
|
71
83
|
sourceContents,
|
|
84
|
+
caseStudyContents,
|
|
72
85
|
strategy,
|
|
73
86
|
};
|
|
74
87
|
}
|
|
@@ -96,10 +109,14 @@ export function buildChapterRegenerationPrompt(chapterId: string): string {
|
|
|
96
109
|
`${isNew ? "Write" : "Regenerate"} this book chapter following these rules:`,
|
|
97
110
|
"1. **Narrative voice**: Write in first-person plural ('we') with a technical-but-approachable tone",
|
|
98
111
|
"2. **Structure**: Follow the Problem → Solution → Implementation → Lessons pattern",
|
|
99
|
-
"3. **Code examples**: Include
|
|
100
|
-
"4. **
|
|
101
|
-
"5. **
|
|
102
|
-
|
|
112
|
+
"3. **Code examples**: Include 'Building with Stagent' TypeScript API examples showing how developers use the feature (realistic UUIDs, timestamps, async/await, comments on non-obvious lines)",
|
|
113
|
+
"4. **Case studies**: Weave 2-4 case study references as `> [!case-study]` callout blocks. Name the company, describe their pattern, draw a parallel to Stagent.",
|
|
114
|
+
"5. **Stagent sections**: Each chapter must include a 'Stagent Today' section (current implementation) and a 'Roadmap Vision' section (future direction informed by case studies).",
|
|
115
|
+
"6. **Narrative thread**: Reference the 'machine that builds machines' thesis — how Stagent uses its own capabilities to build/maintain itself.",
|
|
116
|
+
"7. **Originality**: Never copy phrases verbatim from case study material without quotation marks. Always credit authors by name in callouts. Synthesize from multiple sources rather than mirroring one article's structure. Make content Stagent's own — connect every external concept to Stagent's implementation.",
|
|
117
|
+
"8. **Markdown format**: Use the conventions below for content blocks",
|
|
118
|
+
"9. **Reading time**: Target approximately " + ctx.readingTime + " minutes",
|
|
119
|
+
...(isNew ? [] : ["10. **Preserve Author's Notes**: Keep any existing `> [!authors-note]` blocks unchanged"]),
|
|
103
120
|
"",
|
|
104
121
|
"## Tools Available",
|
|
105
122
|
"",
|
|
@@ -111,7 +128,7 @@ export function buildChapterRegenerationPrompt(chapterId: string): string {
|
|
|
111
128
|
"",
|
|
112
129
|
"- Sections: `## Section Title`",
|
|
113
130
|
"- Code blocks: ` ```language ` with `<!-- filename: path -->` before the block",
|
|
114
|
-
"- Callouts: `> [!tip]`, `> [!warning]`, `> [!info]`, `> [!lesson]`, `> [!authors-note]`",
|
|
131
|
+
"- Callouts: `> [!tip]`, `> [!warning]`, `> [!info]`, `> [!lesson]`, `> [!authors-note]`, `> [!case-study]`",
|
|
115
132
|
"- Interactive links: `[Try: label](href)`",
|
|
116
133
|
"- Images: ``",
|
|
117
134
|
"",
|
|
@@ -141,6 +158,19 @@ export function buildChapterRegenerationPrompt(chapterId: string): string {
|
|
|
141
158
|
);
|
|
142
159
|
}
|
|
143
160
|
|
|
161
|
+
if (ctx.caseStudyContents.length > 0) {
|
|
162
|
+
sections.push(
|
|
163
|
+
"## Case Study Material",
|
|
164
|
+
"",
|
|
165
|
+
"Weave these case studies into the narrative as `> [!case-study]` callout blocks.",
|
|
166
|
+
"Each callout should: name the company, describe their specific pattern, and draw a parallel to the Stagent feature being discussed.",
|
|
167
|
+
"Do NOT create a separate 'Case Studies' section — integrate them inline where relevant.",
|
|
168
|
+
"",
|
|
169
|
+
...ctx.caseStudyContents.map(c => c.slice(0, 3000)), // Truncate each to manage context
|
|
170
|
+
""
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
144
174
|
if (ctx.strategy) {
|
|
145
175
|
sections.push(
|
|
146
176
|
"## Book Strategy Reference",
|
|
@@ -179,3 +209,49 @@ export function buildChapterRegenerationPrompt(chapterId: string): string {
|
|
|
179
209
|
return sections.join("\n");
|
|
180
210
|
}
|
|
181
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Build an agent prompt for the technical-writer profile to quality-review
|
|
214
|
+
* a generated book chapter. This is a post-generation pass for consistency,
|
|
215
|
+
* accuracy, and style.
|
|
216
|
+
*/
|
|
217
|
+
export function buildChapterQualityPrompt(chapterId: string): string {
|
|
218
|
+
const ctx = gatherChapterContext(chapterId);
|
|
219
|
+
if (!ctx.currentMarkdown) {
|
|
220
|
+
throw new Error(`No chapter content found for ${chapterId} — generate it first`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const sections: string[] = [
|
|
224
|
+
`# Chapter Quality Review: ${ctx.title}`,
|
|
225
|
+
"",
|
|
226
|
+
`Review Chapter ${ctx.chapterNumber}: "${ctx.title}" — ${ctx.subtitle}`,
|
|
227
|
+
"",
|
|
228
|
+
"## Review Checklist",
|
|
229
|
+
"",
|
|
230
|
+
"1. **Terminology consistency** — verify consistent use of terms across the chapter (e.g., 'agent profile' not 'agent persona', 'workflow' not 'pipeline' when referring to Stagent workflows)",
|
|
231
|
+
"2. **API accuracy** — read referenced source files to confirm code examples use correct API paths, parameter names, and response shapes",
|
|
232
|
+
"3. **Case study attribution** — every `> [!case-study]` callout must name the company, author (if known), and approximate date",
|
|
233
|
+
"4. **Code example quality** — 'Building with Stagent' examples should use realistic values (UUIDs, timestamps), async/await, and brief comments on non-obvious lines",
|
|
234
|
+
"5. **Section presence** — verify the chapter contains 'Stagent Today' and 'Roadmap Vision' sections",
|
|
235
|
+
"6. **Grammar and style** — first-person plural voice ('we'), technical but approachable, no marketing fluff",
|
|
236
|
+
"7. **Reading time** — content should match the target of ~" + ctx.readingTime + " minutes (~250 words/min)",
|
|
237
|
+
"",
|
|
238
|
+
"## Tools Available",
|
|
239
|
+
"",
|
|
240
|
+
"You have access to **Read**, **Grep**, **Glob**, **Write**, and **Edit** tools.",
|
|
241
|
+
"- Use **Read** and **Grep** to verify API paths and source code references",
|
|
242
|
+
"- Use **Edit** to make corrections directly in the chapter file",
|
|
243
|
+
"",
|
|
244
|
+
"## Chapter Content",
|
|
245
|
+
"",
|
|
246
|
+
"```markdown",
|
|
247
|
+
ctx.currentMarkdown,
|
|
248
|
+
"```",
|
|
249
|
+
"",
|
|
250
|
+
"## Output",
|
|
251
|
+
"",
|
|
252
|
+
`Edit the chapter file at: \`book/chapters/${ctx.slug}.md\``,
|
|
253
|
+
"Make corrections in-place. Do not rewrite the entire chapter — only fix issues found in the review checklist.",
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
return sections.join("\n");
|
|
257
|
+
}
|