@rse/ase 2.0.5 → 2.0.6

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.
Files changed (38) hide show
  1. package/dst/ase-config-schema.js +1 -1
  2. package/dst/ase-setup-settings.js +111 -1
  3. package/dst/ase-setup.js +19 -7
  4. package/dst/ase-spec.js +184 -49
  5. package/dst/ase-task-board-tui-model.js +8 -1
  6. package/dst/ase-task-board-tui.js +26 -2
  7. package/dst/ase-task-board-web-client.js +10 -10
  8. package/dst/man/ase-artifact.md +1 -1
  9. package/dst/man/ase-config.md +5 -3
  10. package/dst/man/ase-mcp.md +1 -1
  11. package/dst/man/ase-setup-install.md +10 -1
  12. package/dst/man/ase-setup-status.md +6 -0
  13. package/dst/man/ase-setup-uninstall.md +3 -1
  14. package/dst/man/ase-setup-update.md +3 -0
  15. package/dst/man/ase-setup.md +4 -4
  16. package/dst/man/ase-spec-export.md +14 -3
  17. package/dst/man/ase-spec-init.md +10 -1
  18. package/dst/man/ase-spec-lint.md +2 -1
  19. package/dst/man/ase-spec-preview.md +10 -1
  20. package/dst/man/ase-spec-query.md +96 -0
  21. package/dst/man/ase-spec.md +32 -11
  22. package/dst/man/ase.md +3 -3
  23. package/package.json +18 -15
  24. package/plugin/.claude-plugin/icon.png +0 -0
  25. package/plugin/.claude-plugin/plugin.json +1 -1
  26. package/plugin/.codex-plugin/plugin.json +1 -1
  27. package/plugin/.github/plugin/plugin.json +1 -1
  28. package/plugin/meta/ase-format-spec.md +29 -5
  29. package/plugin/meta/ase-format-specbook.md +427 -47
  30. package/plugin/meta/ase-format-specbook.yaml +180 -78
  31. package/plugin/mods/ase-mods-common.ts +36 -6
  32. package/plugin/mods/ase-mods-evaluate.ts +227 -0
  33. package/plugin/mods/ase-mods-grill.ts +1 -30
  34. package/plugin/mods/ase-mods-help-browse.ts +18 -13
  35. package/plugin/mods/ase-mods-help-find.ts +17 -9
  36. package/plugin/mods/ase-mods.ts +23 -17
  37. package/plugin/skills/ase-spec-activate/SKILL.md +4 -1
  38. package/plugin/mods/claude-code.d.ts +0 -13440
@@ -13,16 +13,12 @@
13
13
  "n" or "nX" into the prompt. */
14
14
 
15
15
  import type { Elements, Frozen, MatchedEvent, RenderElement } from "claude-code"
16
- import { type Engine, type Char, PICKED, style, width, wrap, split, paint } from "./ase-mods-common.ts"
16
+ import { type Engine, type Char, PICKED, ROW, RULE, cells, style, width, wrap, split, paint } from "./ase-mods-common.ts"
17
17
 
18
18
  /* the marker line above a grilling table (single-round and multi-round,
19
19
  optionally labeled with the perspective and/or grounding) */
20
20
  const MARKER = /⧉ \*{0,2}ASE\*{0,2}: GRILLING(?: ROUND \d+\/\d+)?(?: \([^)]*\))?:/
21
21
 
22
- /* a row and the delimiter row of a Markdown table */
23
- const ROW = /^ *\|.*\| *$/
24
- const RULE = /^ *\|(?: *:?-+:? *\|)+ *$/
25
-
26
22
  /* the separator between two answers, the letter of
27
23
  an answer, and the number of a question */
28
24
  const ANSWER = /,\s+(?=\*{0,2}[A-Z]\*{0,2}\s+▶)/
@@ -48,31 +44,6 @@ type Table = {
48
44
  rows: { number: number, question: string, answers: { letter: string, text: string }[] }[]
49
45
  }
50
46
 
51
- /* split a Markdown table row into its cells */
52
- const cells = (line: string): string[] => {
53
- const result: string[] = []
54
- const body = line.trim().replace(/^\|/, "").replace(/\|$/, "")
55
- let cell = ""
56
- let code = false
57
- for (let i = 0; i < body.length; i++) {
58
- const ch = body[i]!
59
- if (ch === "`")
60
- code = !code
61
- if (!code && ch === "\\" && body[i + 1] === "|") {
62
- cell += "|"
63
- i++
64
- }
65
- else if (!code && ch === "|") {
66
- result.push(cell.trim())
67
- cell = ""
68
- }
69
- else
70
- cell += ch
71
- }
72
- result.push(cell.trim())
73
- return result
74
- }
75
-
76
47
  /* parse the grilling table out of the text of a reply */
77
48
  const parse = (text: string): Table | undefined => {
78
49
  const lines = text.split("\n")
@@ -12,9 +12,10 @@
12
12
  where typed text edits the filter, LEFT/RIGHT (or the wheel over the
13
13
  skills) selects a skill, UP/DOWN (or the wheel over the manual page)
14
14
  scroll its manual page (with the matches of the filter marked), RETURN
15
- takes over its command into the prompt, "!!" switches over to the
16
- intent help for the filter, and "//" leaves the filter in the prompt
17
- (which mirrors the filter while the browser is shown). */
15
+ takes over its command into the prompt, SPACE takes it over into the
16
+ synopsis form, "!!" switches over to the intent help for the filter,
17
+ and "//" leaves the filter in the prompt (which mirrors the filter while
18
+ the browser is shown). */
18
19
 
19
20
  import type { Elements, Frozen, PromptBox, PromptEditInput, RenderElement } from "claude-code"
20
21
  import { type Engine, type Char, type BandRender, type BandScroll, type View, PICKED,
@@ -228,24 +229,26 @@ export const modHelpBrowse = () => {
228
229
  const hold = (current: Browse): PromptBox => held(current.filter.trimEnd())
229
230
 
230
231
  /* take over the command of the selected manual page into the prompt (closing
231
- the browser), if any page matches, and determine the prompt to show */
232
- const take = (current: Browse): PromptBox => {
232
+ the browser), if any page matches, optionally suffixed with a trigger (to
233
+ switch over to its band), and determine the prompt to show */
234
+ const take = (current: Browse, trigger = ""): PromptBox => {
233
235
  const doc = matching(current)[current.select]?.doc
234
236
  if (doc === undefined)
235
237
  return hold(current)
236
238
  reset()
237
- const text = `/${doc.skill} `
239
+ const text = `/${doc.skill} ${trigger}`
238
240
  return { text, cursor: text.length }
239
241
  }
240
242
 
241
243
  /* while the manpage browser is shown, every edit is a key of the browser:
242
244
  LEFT/RIGHT select a manual page, UP/DOWN scroll it by a line, RETURN takes
243
- over the command of the selected page, "//" (two "/" in a row) closes the
244
- browser and leaves the filter in the prompt, "!!" (two "!"
245
- in a row, for a non-empty filter) closes the browser with the filter suffixed
246
- with the trigger of the intent help (to switch over to it), a deletion (like
247
- BACKSPACE) deletes the last character of the filter, and any other typed
248
- text is appended to the filter */
245
+ over the command of the selected page, SPACE takes it over suffixed with
246
+ the trigger of the synopsis form, "//" (two "/" in a row) closes the
247
+ browser and leaves the filter in the prompt, "!!" (two "!" in a row, for
248
+ a non-empty filter) closes the browser with the filter suffixed with the
249
+ trigger of the intent help (both to switch over to the other band), a
250
+ deletion (like BACKSPACE) deletes the last character of the filter, and
251
+ any other typed text is appended to the filter */
249
252
  const edit = (e: Frozen<PromptEditInput>): PromptBox => {
250
253
  const current = browse!
251
254
  const key = decode(e)
@@ -272,6 +275,8 @@ export const modHelpBrowse = () => {
272
275
  current.filter += pending
273
276
  if (key.name === "return")
274
277
  return take(current)
278
+ if (key.text === " ")
279
+ return take(current, "!!")
275
280
  if (key.name === "up" || key.name === "down")
276
281
  current.scroll = Math.max(0, current.scroll + (key.name === "up" ? -1 : 1))
277
282
  else if (key.step !== 0) {
@@ -393,7 +398,7 @@ export const modHelpBrowse = () => {
393
398
  status: `[ ${list.length}/${docs?.length ?? 0} manpages ]`,
394
399
  hints: `←/→${wheeled}: select · ` +
395
400
  `${geometry.overflow ? "wheel or CTRL+x + TAB + ↑/↓/PgUp/PgDn" : "↑/↓"}: scroll · ` +
396
- "//: cancel+close · ⏎: apply+close" +
401
+ "//: cancel+close · ⏎: apply+close · SPACE: compose" +
397
402
  (current.filter.trim() !== "" ? " · !!: Find Skill Command" : "") + " · (type for filtering)",
398
403
  rows: (i) => i === 0 ? field : entry(current.top + i - 2),
399
404
  rule: (i) => i === 1,
@@ -10,9 +10,10 @@
10
10
  the skill "ase-help-intent", but over the session context), and showing
11
11
  the proposed skills in a master/detail view in the band above the
12
12
  prompt, where LEFT/RIGHT (or the wheel over the skills) selects a skill,
13
- UP/DOWN and "b"/"f"/SPACE (or the wheel over the details) scroll its
14
- details, RETURN picks its command into the prompt, and "!!", BACKSPACE, or
15
- any other key restores the prompt as typed without the trigger. */
13
+ UP/DOWN and "b"/"f" (or the wheel over the details) scroll its details,
14
+ RETURN picks its command into the prompt, SPACE picks it into the synopsis
15
+ form, and "!!", BACKSPACE, or any other key restores the prompt as typed
16
+ without the trigger. */
16
17
 
17
18
  import type { Elements, Frozen, PromptBox, PromptEditInput, RenderElement } from "claude-code"
18
19
  import { type Engine, type Char, type BandRender, type BandScroll, type View, PICKED, CODE,
@@ -198,11 +199,13 @@ export const modHelpFind = () => {
198
199
 
199
200
  /* while the intent band is shown, every edit is a key of the band (with the
200
201
  prompt held): LEFT/RIGHT select a proposal, UP/DOWN scroll its details by
201
- a line and "b"/"f"/SPACE by a page (as the engine consumes PAGEUP/PAGEDOWN),
202
+ a line and "b"/"f" by a page (as the engine consumes PAGEUP/PAGEDOWN),
202
203
  RETURN (arriving as a typed newline in the held prompt) operates the band
203
- as at the prompt, "//" (two "/" in a row) closes the band with the prompt
204
- as typed suffixed with the trigger of the manpage browser (to switch over
205
- to it), and "!!" (two "!" in a row), a deletion (like BACKSPACE), or any
204
+ as at the prompt, SPACE closes the band with the command of the selected
205
+ proposal suffixed with the trigger of the synopsis form, "//" (two "/"
206
+ in a row) closes the band with the prompt as typed suffixed with the
207
+ trigger of the manpage browser (both to switch over to the other band),
208
+ and "!!" (two "!" in a row), a deletion (like BACKSPACE), or any
206
209
  other key closes the band and restores the prompt as typed without the
207
210
  trigger (onto whose end the edit of a typed text is still to be applied) */
208
211
  const edit = (e: Frozen<PromptEditInput>): PromptBox | string => {
@@ -222,8 +225,13 @@ export const modHelpFind = () => {
222
225
  const text = submit()
223
226
  return intent !== undefined ? held(draft) : { text, cursor: text.length }
224
227
  }
228
+ if (state === "done" && key.text === " ") {
229
+ reset()
230
+ const text = `${proposals[current.select]!.command} !!`
231
+ return { text, cursor: text.length }
232
+ }
225
233
  const roll = key.name === "up" ? -1 : key.name === "down" ? 1 :
226
- key.text === "b" ? -current.page : key.text === "f" || key.text === " " ? current.page : 0
234
+ key.text === "b" ? -current.page : key.text === "f" ? current.page : 0
227
235
  const step = roll !== 0 ? 0 : key.step
228
236
  if (state === "done" && (roll !== 0 || step !== 0)) {
229
237
  if (step !== 0) {
@@ -406,7 +414,7 @@ export const modHelpFind = () => {
406
414
  (the intent itself stays visible in the prompt) */
407
415
  return panel(ui, geometry, current, {
408
416
  title: TITLE,
409
- hints: current.state === "done" ? "⌫/!!: cancel+close · ⏎: apply+close · ←/→: select · b/f/SPACE: scroll" +
417
+ hints: current.state === "done" ? "⌫/!!: cancel+close · ⏎: apply+close · SPACE: compose · ←/→: select · b/f: scroll" +
410
418
  (geometry.overflow ? " · wheel: select/scroll" : "") + " · //: Browse Skill Manpages" :
411
419
  "⌫/!!/any key: cancel+close · //: Browse Skill Manpages",
412
420
  rows: (i) => {
@@ -6,8 +6,9 @@
6
6
 
7
7
  /* Claude Code function hooks module ("mod", EARLY ACCESS):
8
8
  Hooking infrastructure, registering the function hooks and dispatching
9
- them into the grill table ("ase-mods-grill.ts"), the intent help
10
- ("ase-mods-help-find.ts"), the synopsis form ("ase-mods-help-compose.ts"),
9
+ them into the grill table ("ase-mods-grill.ts"), the decision matrix
10
+ ("ase-mods-evaluate.ts"), the intent help ("ase-mods-help-find.ts"),
11
+ the synopsis form ("ase-mods-help-compose.ts"),
11
12
  the manual band ("ase-mods-help-show.ts"), the manpage browser
12
13
  ("ase-mods-help-browse.ts"), and the hint band ("ase-mods-help-hint.ts").
13
14
  As the engine loads only a module spelling "on", "$", and "next" literally,
@@ -17,6 +18,7 @@
17
18
  import type { Register, PromptBox, Timer } from "claude-code"
18
19
  import { type Band, TRIGGER } from "./ase-mods-common.ts"
19
20
  import { modGrill } from "./ase-mods-grill.ts"
21
+ import { modEvaluate } from "./ase-mods-evaluate.ts"
20
22
  import { modHelpFind } from "./ase-mods-help-find.ts"
21
23
  import { modHelpCompose } from "./ase-mods-help-compose.ts"
22
24
  import { modHelpShow } from "./ase-mods-help-show.ts"
@@ -29,14 +31,15 @@ export const register: Register = (on, options) => {
29
31
  if (options.enabled === false)
30
32
  return
31
33
 
32
- /* the grill table, the intent help, the synopsis form, the manual band,
33
- the manpage browser, and the hint band */
34
- const grill = modGrill()
35
- const intent = modHelpFind()
36
- const skill = modHelpCompose()
37
- const manual = modHelpShow()
38
- const browse = modHelpBrowse()
39
- const hint = modHelpHint()
34
+ /* the grill table, the decision matrix, the intent help, the synopsis form,
35
+ the manual band, the manpage browser, and the hint band */
36
+ const grill = modGrill()
37
+ const evaluate = modEvaluate()
38
+ const intent = modHelpFind()
39
+ const skill = modHelpCompose()
40
+ const manual = modHelpShow()
41
+ const browse = modHelpBrowse()
42
+ const hint = modHelpHint()
40
43
 
41
44
  /* the bands above the prompt (the synopsis form, the intent band, and the
42
45
  manpage browser, opened in this order), the shown one (as at most one is
@@ -198,16 +201,19 @@ export const register: Register = (on, options) => {
198
201
  next.called || shown() === undefined ? next(e) :
199
202
  { drop: "intent band or synopsis form failed" })
200
203
 
201
- /* redraw the replies which contain a grilling table (terminal only), where
202
- the chain beneath runs, although its drawing is replaced by us (but this
203
- at least silences a warning in the debug output of Claude Code) */
204
+ /* redraw the replies which contain a grilling table or a decision matrix
205
+ (terminal only), where the chain beneath runs, although its drawing is
206
+ replaced by us (but this at least silences a warning in the debug output
207
+ of Claude Code) */
204
208
  on("ui.render", { component: "AssistantMessage", surface: "terminal" }, async ($, e, next) => {
205
- const drawn = await next(e)
206
- return grill.render(e, $.ui.resolve(e), {
207
- invalidate: () => $.ui.invalidate("ui.render"),
209
+ const drawn = await next(e)
210
+ const ui = $.ui.resolve(e)
211
+ const invalidate = () => $.ui.invalidate("ui.render")
212
+ return grill.render(e, ui, {
213
+ invalidate,
208
214
  prompt: () => $.prompt.read(),
209
215
  fill: (text) => $.prompt.fill({ text })
210
- }) ?? drawn
216
+ }) ?? evaluate.render(e, ui, { invalidate }) ?? drawn
211
217
  })
212
218
 
213
219
  /* draw the intent band resp. the synopsis form, else the manual page,
@@ -118,7 +118,10 @@ an ad-hoc change, and it *never* touches the artifact kinds `CODE`,
118
118
 
119
119
  Serve <query/> *ad-hoc* under the **Activated Behavior** below:
120
120
  read the `SPEC` artifacts of <artifact-files/> which are related to
121
- <query/>, resolve their `[[xxx]]` references across artifacts, and
121
+ <query/> (or, for a question spanning entire object kinds, property
122
+ values, or reference relations, query them structurally via the
123
+ `ase_specbook_query(selector: "<selector/>")` tool of the `ase` MCP
124
+ server), resolve their `[[xxx]]` references across artifacts, and
122
125
  answer the query grounded in the specification content, citing the
123
126
  artifact file and object id of every statement you rely on. If
124
127
  <query/> asks for a change, apply it as an *ad-hoc modification*