@erclx/aitk 0.89.1 → 0.91.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-address-review/REQUIREMENT.md +5 -0
- package/claude/skills/claude-address-review/SKILL.md +17 -0
- package/claude/skills/claude-groundwork/SKILL.md +1 -1
- package/claude/skills/claude-intake/SKILL.md +8 -1
- package/claude/skills/claude-intake-answer/REQUIREMENT.md +48 -0
- package/claude/skills/claude-intake-answer/SKILL.md +90 -0
- package/claude/skills/claude-orchestrate/REQUIREMENT.md +3 -0
- package/claude/skills/claude-orchestrate/SKILL.md +2 -0
- package/claude/skills/claude-pr-review/REQUIREMENT.md +3 -0
- package/claude/skills/claude-pr-review/SKILL.md +4 -0
- package/claude/skills/claude-tasks/SKILL.md +1 -1
- package/claude/skills/toolkit-feedback/SKILL.md +1 -1
- package/claude/skills/youtube-transcripts/REQUIREMENT.md +1 -1
- package/claude/skills/youtube-transcripts/SKILL.md +1 -1
- package/docs/agents/commands.md +3 -0
- package/docs/agents/index.md +1 -0
- package/docs/agents/intake.md +77 -0
- package/docs/operating-model.md +13 -0
- package/package.json +1 -1
- package/src/cli.ts +4 -0
- package/src/commands/intake.ts +406 -0
- package/src/intake/folder.ts +280 -0
- package/src/intake/items.ts +174 -0
- package/standards/intake.md +9 -0
- package/standards/skill.md +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { bodyLines } from '@/markdown/scan'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The index points at items and answers nothing itself, so it carries no
|
|
5
|
+
* answer slot for this verb to reach. It also displays the item format inside a
|
|
6
|
+
* fence, which the fence walk already masks, so skipping it by name is about
|
|
7
|
+
* what the file is rather than about the block it holds.
|
|
8
|
+
*/
|
|
9
|
+
export const INDEX_FILE = '00-overview.md'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* An item heading, labeled per cluster file rather than per folder.
|
|
13
|
+
*
|
|
14
|
+
* The label carries an optional letter suffix because a pass that splits one
|
|
15
|
+
* finding after the fact numbers the halves `3a` and `3b` rather than
|
|
16
|
+
* renumbering every item below them. A pattern accepting digits alone parses
|
|
17
|
+
* such a file without complaint and drops those items, leaving them
|
|
18
|
+
* unanswerable through this verb with nothing reporting the gap.
|
|
19
|
+
*/
|
|
20
|
+
const HEADING = /^###\s+(\d+[a-z]*)\.\s*(.*)$/i
|
|
21
|
+
|
|
22
|
+
/** A bolded field bullet, which is every line an item carries. */
|
|
23
|
+
const FIELD = /^-\s+\*\*([^*]+):\*\*\s*(.*)$/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Any heading, which ends the item above it.
|
|
27
|
+
*
|
|
28
|
+
* A numbered `###` is tested first and opens the next item, so what reaches
|
|
29
|
+
* here is a heading that is not an item and therefore closes the one open. The
|
|
30
|
+
* narrower test for `##` alone let a `### Notes` block stay inside the item
|
|
31
|
+
* above it, and the stray slot such a block carries then displaced the real
|
|
32
|
+
* one, leaving the item reading as answered while its own slot sat empty and
|
|
33
|
+
* sending a write into the wrong section.
|
|
34
|
+
*/
|
|
35
|
+
const SECTION = /^#{1,6}\s/
|
|
36
|
+
|
|
37
|
+
export interface IntakeItem {
|
|
38
|
+
/** The label as the heading spells it, such as `3` or `3a`. */
|
|
39
|
+
readonly label: string
|
|
40
|
+
readonly title: string
|
|
41
|
+
/** Line the heading sits on, 1-based against the whole file. */
|
|
42
|
+
readonly line: number
|
|
43
|
+
/** Line the answer slot sits on, absent when the item carries no slot. */
|
|
44
|
+
readonly answerLine: number | undefined
|
|
45
|
+
/** Text in the slot, absent when the slot is empty and the item is unread. */
|
|
46
|
+
readonly answer: string | undefined
|
|
47
|
+
readonly open: string | undefined
|
|
48
|
+
readonly suggested: string | undefined
|
|
49
|
+
readonly worth: string | undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface Draft {
|
|
53
|
+
label: string
|
|
54
|
+
title: string
|
|
55
|
+
line: number
|
|
56
|
+
answerLine: number | undefined
|
|
57
|
+
answer: string | undefined
|
|
58
|
+
open: string | undefined
|
|
59
|
+
suggested: string | undefined
|
|
60
|
+
worth: string | undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function seal(draft: Draft): IntakeItem {
|
|
64
|
+
return { ...draft }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Reads every item a cluster file holds, in file order.
|
|
69
|
+
*
|
|
70
|
+
* The walk runs over `bodyLines` rather than a raw split so the item format
|
|
71
|
+
* block a folder copies into its own files parses as the sample it is. A
|
|
72
|
+
* heading counted out of a fence shifts nothing on its own, but it offers an
|
|
73
|
+
* answer slot no reader owns and the write-back would land inside the sample.
|
|
74
|
+
*/
|
|
75
|
+
export function readItems(text: string): IntakeItem[] {
|
|
76
|
+
const items: IntakeItem[] = []
|
|
77
|
+
let draft: Draft | undefined
|
|
78
|
+
|
|
79
|
+
for (const line of bodyLines(text)) {
|
|
80
|
+
if (line.fenced) continue
|
|
81
|
+
|
|
82
|
+
const heading = HEADING.exec(line.text)
|
|
83
|
+
|
|
84
|
+
if (heading) {
|
|
85
|
+
if (draft) items.push(seal(draft))
|
|
86
|
+
draft = {
|
|
87
|
+
label: heading[1].toLowerCase(),
|
|
88
|
+
title: heading[2].trim(),
|
|
89
|
+
line: line.number,
|
|
90
|
+
answerLine: undefined,
|
|
91
|
+
answer: undefined,
|
|
92
|
+
open: undefined,
|
|
93
|
+
suggested: undefined,
|
|
94
|
+
worth: undefined,
|
|
95
|
+
}
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!draft) continue
|
|
100
|
+
|
|
101
|
+
if (SECTION.test(line.text)) {
|
|
102
|
+
items.push(seal(draft))
|
|
103
|
+
draft = undefined
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const field = FIELD.exec(line.text)
|
|
108
|
+
if (!field) continue
|
|
109
|
+
|
|
110
|
+
const value = field[2].trim()
|
|
111
|
+
|
|
112
|
+
switch (field[1].trim().toLowerCase()) {
|
|
113
|
+
case 'you':
|
|
114
|
+
draft.answerLine = line.number
|
|
115
|
+
draft.answer = value === '' ? undefined : value
|
|
116
|
+
break
|
|
117
|
+
case 'open':
|
|
118
|
+
draft.open = value
|
|
119
|
+
break
|
|
120
|
+
case 'suggested':
|
|
121
|
+
draft.suggested = value
|
|
122
|
+
break
|
|
123
|
+
case 'worth it':
|
|
124
|
+
draft.worth = value
|
|
125
|
+
break
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (draft) items.push(seal(draft))
|
|
130
|
+
|
|
131
|
+
return items
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Puts a selection in one item's slot, leaving every other line as it was.
|
|
136
|
+
*
|
|
137
|
+
* The rewrite replaces the whole line rather than patching inside it, which is
|
|
138
|
+
* the reason this is a verb at all. A stream editor expands an unescaped
|
|
139
|
+
* ampersand in the replacement to the whole match and exits zero on a
|
|
140
|
+
* non-match, so an answer carrying one would rewrite the line it anchored to
|
|
141
|
+
* and a missed slot would report success with the answer lost.
|
|
142
|
+
*
|
|
143
|
+
* The answer occupies one line, and the caller owes that guarantee. A line
|
|
144
|
+
* break splices a bare continuation into the item matching none of the patterns
|
|
145
|
+
* the reader tests, so the slot reads back as the text before the break while
|
|
146
|
+
* the item counts as answered, which puts correcting it behind the refusal on
|
|
147
|
+
* an item that already carries one.
|
|
148
|
+
*/
|
|
149
|
+
export function writeAnswerLine(
|
|
150
|
+
text: string,
|
|
151
|
+
answerLine: number,
|
|
152
|
+
answer: string,
|
|
153
|
+
): string {
|
|
154
|
+
const lines = text.split('\n')
|
|
155
|
+
lines[answerLine - 1] = `- **You:** ${answer}`
|
|
156
|
+
return lines.join('\n')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** An item nobody has reached, which is an empty slot rather than a missing one. */
|
|
160
|
+
export function isUnread(item: IntakeItem): boolean {
|
|
161
|
+
return item.answerLine !== undefined && item.answer === undefined
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* An item carrying no slot at all, which the format says ships on every one.
|
|
166
|
+
*
|
|
167
|
+
* Such an item is neither unread nor answered, and counting it as either hides
|
|
168
|
+
* it: as answered it drops out of the work a reader is told remains, and as
|
|
169
|
+
* unread it joins a list whose every entry the answer verb then refuses. It is
|
|
170
|
+
* reported on its own so the file gets fixed.
|
|
171
|
+
*/
|
|
172
|
+
export function isMalformed(item: IntakeItem): boolean {
|
|
173
|
+
return item.answerLine === undefined
|
|
174
|
+
}
|
package/standards/intake.md
CHANGED
|
@@ -89,6 +89,7 @@ Where an item touches a task already on the board, say so in the index rather th
|
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
- `Problem:`, `Fix:`, `Worth it:`, and the empty `You:` slot ship on every item. The other two are conditional.
|
|
92
|
+
- Number items per cluster file, and give a finding split after the fact a letter suffix on the number it came from, as in `3a` beside `3`. Renumbering the items below it instead moves every label a reader or an answer already cited.
|
|
92
93
|
- `Suggested:` is required whenever `Open:` is present. A bare question invites a bare answer, and `ok` against two defensible options carries no information. Where the answer is the operator's preference rather than a technical call, say so in that form rather than inventing a default.
|
|
93
94
|
- `Overlaps:` never replaces `Worth it:`. The items where a live board task might be the thing that is wrong are exactly the ones whose verdict matters most.
|
|
94
95
|
|
|
@@ -106,10 +107,18 @@ That inverts the plan file's contract, where a blank answer slot means accept th
|
|
|
106
107
|
|
|
107
108
|
Never fill a `You:` slot, and never infer a disposition from an empty one. On a resume pass, report unread items by count rather than deciding them.
|
|
108
109
|
|
|
110
|
+
A slot is filled two ways. The operator types into the cluster file, or answers in chat and a verb lands the selection on the item. Both put the answer on the item, which is what keeps retrieval working, and neither lets a session decide one. An answer given in conversation and never written back leaves the item unread, since the file rather than the conversation is the record.
|
|
111
|
+
|
|
112
|
+
An item already carrying an answer is refused rather than overwritten, whichever route the second answer arrives by. A filled slot is a decision already made, and revising one is the operator editing their own line.
|
|
113
|
+
|
|
109
114
|
## Retrieval
|
|
110
115
|
|
|
111
116
|
Answers live on items, so one pass over the folder reports every touched slot.
|
|
112
117
|
|
|
118
|
+
A session with the toolkit CLI on PATH reads the folder through `aitk intake list`, which reports per-folder counts bare and one folder's items with `--json`, and takes `--unread` to keep only the empty slots. It is the surface under test, and it skips the index and every fenced sample, which the greps below cannot do.
|
|
119
|
+
|
|
120
|
+
The greps stay for a reader without the CLI, and they overcount by whatever the folder displays in a fence.
|
|
121
|
+
|
|
113
122
|
```bash
|
|
114
123
|
awk '/^### /{h=FILENAME": "$0} /^- \*\*You:\*\*./{print h; print " "$0}' *.md
|
|
115
124
|
```
|
package/standards/skill.md
CHANGED
|
@@ -208,7 +208,7 @@ Without this skill, a session <observed failure>, <observed failure>.
|
|
|
208
208
|
|
|
209
209
|
### Output and tuning
|
|
210
210
|
|
|
211
|
-
- Skill success lines emit the full relative path from the project root (`<dir>/<file>`) for any file written, updated, or deleted.
|
|
211
|
+
- Skill success lines emit the full relative path from the project root (`<dir>/<file>`) for any file written, updated, or deleted. A bare filename names a file the reader cannot open. The `## Output` section of the project's instruction file sets the form that path takes, so a skill body states which path is emitted and leaves the form to that section.
|
|
212
212
|
- Codify a skill's posted or generated output as a fenced template, and keep the body consistent with every capability the frontmatter description names.
|
|
213
213
|
- When a skill gathers user input or pre-seeds a template, attach a concrete proposed default to every question, derived from project context. Accept "use defaults" as a bulk-confirm.
|
|
214
214
|
- Separate correctness axes (routing, sourcing, escalation, decline) from shape axes (line count, formatting, variant sprawl) when tuning a skill. Tighten only on correctness regressions. Do not convert soft caps to hard caps for aesthetic drift when correctness passes.
|