@rse/ase 1.0.7 → 2.0.1

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 (279) hide show
  1. package/dst/ase-agent-definition-api.d.ts +184 -0
  2. package/dst/ase-agent-definition-api.js +6 -0
  3. package/dst/ase-agent-definition-builtin/condenser.js +42 -0
  4. package/dst/ase-agent-definition-builtin/editor.js +45 -0
  5. package/dst/ase-agent-definition-builtin/estimator.js +98 -0
  6. package/dst/ase-agent-definition-builtin/implementer.js +121 -0
  7. package/dst/ase-agent-definition-builtin/integrator.js +56 -0
  8. package/dst/ase-agent-definition-builtin/qualifier.js +61 -0
  9. package/dst/ase-agent-definition-builtin/rebooter.js +41 -0
  10. package/dst/ase-agent-definition-builtin/researcher.js +77 -0
  11. package/dst/ase-agent-definition-core.js +827 -0
  12. package/dst/ase-agent-fleet.js +772 -0
  13. package/dst/ase-agent-process.js +232 -0
  14. package/dst/ase-agent-relay-client.js +509 -0
  15. package/dst/ase-agent-relay-core.js +213 -0
  16. package/dst/ase-agent-relay-server-bind.js +242 -0
  17. package/dst/ase-agent-relay-server-cli.js +50 -0
  18. package/dst/ase-agent-runtime-claim.js +189 -0
  19. package/dst/ase-agent-runtime-engine.js +1100 -0
  20. package/dst/ase-agent-runtime-exec.js +275 -0
  21. package/dst/ase-agent-runtime-phase.js +581 -0
  22. package/dst/ase-agent-runtime-request.js +272 -0
  23. package/dst/ase-agent-runtime-tool.js +207 -0
  24. package/dst/ase-agent-runtime.js +1316 -0
  25. package/dst/ase-agent.js +65 -0
  26. package/dst/ase-artifact.js +6 -5
  27. package/dst/ase-config-core.js +465 -0
  28. package/dst/ase-config-schema.js +357 -0
  29. package/dst/ase-config-scope.js +172 -0
  30. package/dst/ase-config.js +25 -585
  31. package/dst/ase-hook.js +228 -111
  32. package/dst/ase-lib-daemon-cli.js +130 -0
  33. package/dst/ase-lib-hash.js +20 -0
  34. package/dst/{ase-log.js → ase-lib-log.js} +40 -7
  35. package/dst/ase-lib-server-bind.js +115 -0
  36. package/dst/ase-lib-server-cli.js +474 -0
  37. package/dst/{ase-table.js → ase-lib-table.js} +1 -1
  38. package/dst/{ase-version.js → ase-lib-version.js} +8 -1
  39. package/dst/ase-man.js +96 -0
  40. package/dst/ase-mcp.js +166 -13
  41. package/dst/{ase-getopt.js → ase-service-getopt.js} +82 -80
  42. package/dst/{ase-markdown.js → ase-service-markdown.js} +18 -11
  43. package/dst/ase-service-otel.js +1246 -0
  44. package/dst/{ase-skills.js → ase-service-skills.js} +29 -64
  45. package/dst/ase-service-state.js +101 -0
  46. package/dst/ase-service.js +269 -81
  47. package/dst/ase-setup-common.js +141 -0
  48. package/dst/ase-setup-mcp.js +359 -0
  49. package/dst/ase-setup-settings.js +837 -0
  50. package/dst/ase-setup.js +113 -869
  51. package/dst/ase-spec.js +31 -19
  52. package/dst/ase-statusline.js +108 -43
  53. package/dst/ase-task-board-core.js +739 -0
  54. package/dst/ase-task-board-filter.js +127 -0
  55. package/dst/ase-task-board-flow.js +237 -0
  56. package/dst/ase-task-board-graph.js +497 -0
  57. package/dst/ase-task-board-stats.js +263 -0
  58. package/dst/ase-task-board-tui-control.js +775 -0
  59. package/dst/ase-task-board-tui-model.js +1008 -0
  60. package/dst/ase-task-board-tui-popup.js +626 -0
  61. package/dst/ase-task-board-tui-style.js +106 -0
  62. package/dst/ase-task-board-tui-view.js +847 -0
  63. package/dst/ase-task-board-tui.js +189 -0
  64. package/dst/ase-task-board-web-client.css +1 -0
  65. package/dst/ase-task-board-web-client.html +20 -0
  66. package/dst/ase-task-board-web-client.js +81 -0
  67. package/dst/ase-task-board-web-server.js +823 -0
  68. package/dst/ase-task-board.js +134 -0
  69. package/dst/ase-task-format.js +757 -0
  70. package/dst/ase-task-request.js +208 -0
  71. package/dst/ase-task-store-client.js +495 -0
  72. package/dst/ase-task-store-core.js +804 -0
  73. package/dst/ase-task-store-issues.js +237 -0
  74. package/dst/ase-task-store-plugin-api.js +6 -0
  75. package/dst/ase-task-store-plugin-ase.js +484 -0
  76. package/dst/ase-task-store-plugin-delegate.js +137 -0
  77. package/dst/ase-task-store-plugin-gitea.js +666 -0
  78. package/dst/ase-task-store-plugin-github.js +696 -0
  79. package/dst/ase-task-store-plugin-gitlab.js +668 -0
  80. package/dst/ase-task-store-server-bind.js +538 -0
  81. package/dst/ase-task-store-server-cli.js +154 -0
  82. package/dst/ase-task.js +1335 -357
  83. package/dst/{ase-compat.js → ase-util-compat.js} +2 -2
  84. package/dst/{ase-diagram.js → ase-util-diagram.js} +107 -32
  85. package/dst/{ase-meta.js → ase-util-meta.js} +2 -2
  86. package/dst/{ase-metric.js → ase-util-metric.js} +2 -2
  87. package/dst/{ase-mint.js → ase-util-mint.js} +19 -12
  88. package/dst/{ase-worktree.js → ase-util-worktree.js} +30 -19
  89. package/dst/ase-util.js +20 -0
  90. package/dst/ase.js +108 -32
  91. package/dst/man/ase-agent-adhoc.1 +515 -0
  92. package/dst/man/ase-agent-condenser.1 +193 -0
  93. package/dst/man/ase-agent-editor.1 +212 -0
  94. package/dst/man/ase-agent-estimator.1 +214 -0
  95. package/dst/man/ase-agent-fleet.1 +185 -0
  96. package/dst/man/ase-agent-implementer.1 +233 -0
  97. package/dst/man/ase-agent-integrator.1 +213 -0
  98. package/dst/man/ase-agent-qualifier.1 +214 -0
  99. package/dst/man/ase-agent-rebooter.1 +193 -0
  100. package/dst/man/ase-agent-relay.1 +128 -0
  101. package/dst/man/ase-agent-researcher.1 +217 -0
  102. package/dst/man/ase-agent.1 +264 -0
  103. package/dst/man/ase-artifact-list.1 +38 -0
  104. package/dst/man/ase-artifact-name.1 +39 -0
  105. package/dst/man/ase-artifact.1 +56 -0
  106. package/dst/man/ase-config-delete.1 +45 -0
  107. package/dst/man/ase-config-edit.1 +48 -0
  108. package/dst/man/ase-config-get.1 +46 -0
  109. package/dst/man/ase-config-init.1 +60 -0
  110. package/dst/man/ase-config-list.1 +42 -0
  111. package/dst/man/ase-config-set.1 +52 -0
  112. package/dst/man/ase-config.1 +231 -0
  113. package/dst/man/ase-hook-permission-request.1 +36 -0
  114. package/dst/man/ase-hook-pre-tool-use.1 +59 -0
  115. package/dst/man/ase-hook-session-end.1 +47 -0
  116. package/dst/man/ase-hook-session-persona.1 +37 -0
  117. package/dst/man/ase-hook-session-start.1 +81 -0
  118. package/dst/man/ase-hook-stop.1 +34 -0
  119. package/dst/man/ase-hook-user-prompt-submit.1 +34 -0
  120. package/dst/man/ase-hook.1 +51 -0
  121. package/dst/man/ase-man.1 +67 -0
  122. package/dst/man/ase-mcp.1 +61 -0
  123. package/dst/man/ase-service-log.1 +43 -0
  124. package/dst/man/ase-service-send.1 +47 -0
  125. package/dst/man/ase-service-start.1 +54 -0
  126. package/dst/man/ase-service-status.1 +46 -0
  127. package/dst/man/ase-service-stop.1 +37 -0
  128. package/dst/man/ase-service.1 +85 -0
  129. package/dst/man/ase-setup-disable.1 +44 -0
  130. package/dst/man/ase-setup-enable.1 +45 -0
  131. package/dst/man/ase-setup-install.1 +73 -0
  132. package/dst/man/ase-setup-mcp.1 +124 -0
  133. package/dst/man/ase-setup-status.1 +60 -0
  134. package/dst/man/ase-setup-statusline.1 +92 -0
  135. package/dst/man/ase-setup-telemetry.1 +92 -0
  136. package/dst/man/ase-setup-uninstall.1 +59 -0
  137. package/dst/man/ase-setup-update.1 +64 -0
  138. package/dst/man/ase-setup.1 +93 -0
  139. package/dst/man/ase-spec-export.1 +62 -0
  140. package/dst/man/ase-spec-lint.1 +40 -0
  141. package/dst/man/ase-spec-preview.1 +40 -0
  142. package/dst/man/ase-spec.1 +46 -0
  143. package/dst/man/ase-statusline.1 +189 -0
  144. package/dst/man/ase-task-assign.1 +71 -0
  145. package/dst/man/ase-task-board.1 +384 -0
  146. package/dst/man/ase-task-delete.1 +35 -0
  147. package/dst/man/ase-task-edit.1 +46 -0
  148. package/dst/man/ase-task-idscheme.1 +65 -0
  149. package/dst/man/ase-task-lifecycle.1 +57 -0
  150. package/dst/man/ase-task-list.1 +47 -0
  151. package/dst/man/ase-task-load.1 +36 -0
  152. package/dst/man/ase-task-newid.1 +61 -0
  153. package/dst/man/ase-task-purge.1 +37 -0
  154. package/dst/man/ase-task-rename.1 +44 -0
  155. package/dst/man/ase-task-request.1 +61 -0
  156. package/dst/man/ase-task-save.1 +48 -0
  157. package/dst/man/ase-task-status.1 +55 -0
  158. package/dst/man/ase-task-store.1 +194 -0
  159. package/dst/man/ase-task-view.1 +40 -0
  160. package/dst/man/ase-task.1 +160 -0
  161. package/dst/man/ase-util-compat.1 +25 -0
  162. package/dst/man/ase-util-diagram.1 +76 -0
  163. package/dst/man/ase-util-meta.1 +45 -0
  164. package/dst/man/ase-util-metric.1 +54 -0
  165. package/dst/man/ase-util-mint.1 +59 -0
  166. package/dst/man/ase-util-worktree.1 +72 -0
  167. package/dst/man/ase-util.1 +50 -0
  168. package/dst/man/ase.1 +124 -0
  169. package/package.json +81 -21
  170. package/plugin/.claude-plugin/plugin.json +1 -1
  171. package/plugin/.codex-plugin/plugin.json +1 -1
  172. package/plugin/.github/plugin/plugin.json +1 -1
  173. package/plugin/agents/ase-meta-diagram.md +12 -8
  174. package/plugin/agents/ase-repo-review.md +322 -0
  175. package/plugin/hooks/hooks-codex.json +5 -0
  176. package/plugin/hooks/hooks-copilot.json +5 -0
  177. package/plugin/hooks/hooks.json +8 -0
  178. package/plugin/meta/ase-common-approach.md +117 -0
  179. package/plugin/meta/ase-common-changeset.md +256 -0
  180. package/plugin/meta/ase-common-code.md +10 -173
  181. package/plugin/meta/ase-common-grill.md +317 -0
  182. package/plugin/meta/ase-common-resolve.md +305 -0
  183. package/plugin/meta/ase-common-task.md +83 -4
  184. package/plugin/meta/ase-constitution.md +0 -2
  185. package/plugin/meta/ase-format-specbook.md +208 -25
  186. package/plugin/meta/ase-format-specbook.yaml +242 -851
  187. package/plugin/meta/ase-format-task.md +669 -125
  188. package/plugin/meta/ase-persona.md +3 -4
  189. package/plugin/meta/ase-skill.md +9 -0
  190. package/plugin/mods/ase-mods.ts +409 -0
  191. package/plugin/mods/claude-code.d.ts +13417 -0
  192. package/plugin/skills/ase-arch-analyze/SKILL.md +1 -1
  193. package/plugin/skills/ase-arch-analyze/help.md +2 -2
  194. package/plugin/skills/ase-code-analyze/SKILL.md +59 -8
  195. package/plugin/skills/ase-code-analyze/help.md +29 -4
  196. package/plugin/skills/ase-code-edit/SKILL.md +107 -181
  197. package/plugin/skills/ase-code-edit/help.md +118 -31
  198. package/plugin/skills/ase-code-lint/SKILL.md +64 -13
  199. package/plugin/skills/ase-code-lint/help.md +32 -2
  200. package/plugin/skills/ase-docs-proofread/SKILL.md +4 -4
  201. package/plugin/skills/ase-docs-proofread/help.md +1 -1
  202. package/plugin/skills/ase-docs-refine/SKILL.md +4 -4
  203. package/plugin/skills/ase-docs-shorten/SKILL.md +4 -4
  204. package/plugin/skills/ase-help-intent/data.md +2164 -1087
  205. package/plugin/skills/ase-help-intent/help.md +1 -1
  206. package/plugin/skills/ase-help-skill/catalog.md +12 -9
  207. package/plugin/skills/ase-meta-brainstorm/SKILL.md +1 -1
  208. package/plugin/skills/ase-meta-config/SKILL.md +3 -4
  209. package/plugin/skills/ase-meta-config/help.md +31 -10
  210. package/plugin/skills/ase-meta-mint/SKILL.md +5 -5
  211. package/plugin/skills/ase-meta-mint/help.md +10 -5
  212. package/plugin/skills/ase-meta-why/help.md +1 -1
  213. package/plugin/skills/ase-meta-workflow/SKILL.md +7 -7
  214. package/plugin/skills/ase-meta-workflow/help.md +4 -4
  215. package/plugin/skills/ase-meta-workflow/sample.md +3 -3
  216. package/plugin/skills/ase-meta-workflow/workflow.txt +22 -18
  217. package/plugin/skills/{ase-meta-changelog → ase-repo-changelog}/SKILL.md +4 -3
  218. package/plugin/skills/{ase-meta-changelog → ase-repo-changelog}/help.md +5 -5
  219. package/plugin/skills/{ase-meta-commit → ase-repo-commit}/SKILL.md +6 -6
  220. package/plugin/skills/{ase-meta-commit → ase-repo-commit}/help.md +5 -5
  221. package/plugin/skills/{ase-meta-diff → ase-repo-diff}/SKILL.md +6 -6
  222. package/plugin/skills/{ase-meta-diff → ase-repo-diff}/help.md +8 -8
  223. package/plugin/skills/{ase-code-dissect → ase-repo-dissect}/SKILL.md +19 -19
  224. package/plugin/skills/{ase-code-dissect → ase-repo-dissect}/help.md +10 -12
  225. package/plugin/skills/ase-repo-merge/SKILL.md +346 -0
  226. package/plugin/skills/ase-repo-merge/help.md +107 -0
  227. package/plugin/skills/ase-repo-resolve/SKILL.md +158 -0
  228. package/plugin/skills/ase-repo-resolve/help.md +102 -0
  229. package/plugin/skills/ase-repo-review/SKILL.md +406 -0
  230. package/plugin/skills/ase-repo-review/help.md +181 -0
  231. package/plugin/skills/ase-spec-edit/SKILL.md +117 -183
  232. package/plugin/skills/ase-spec-edit/help.md +107 -26
  233. package/plugin/skills/ase-sync-reconcile/help.md +1 -1
  234. package/plugin/skills/ase-task-assign/SKILL.md +111 -0
  235. package/plugin/skills/ase-task-assign/help.md +119 -0
  236. package/plugin/skills/ase-task-condense/SKILL.md +21 -21
  237. package/plugin/skills/ase-task-condense/help.md +12 -10
  238. package/plugin/skills/ase-task-delete/SKILL.md +1 -1
  239. package/plugin/skills/ase-task-dissect/SKILL.md +71 -48
  240. package/plugin/skills/ase-task-dissect/help.md +10 -9
  241. package/plugin/skills/ase-task-edit/SKILL.md +379 -94
  242. package/plugin/skills/ase-task-edit/help.md +95 -22
  243. package/plugin/skills/ase-task-estimate/SKILL.md +492 -0
  244. package/plugin/skills/ase-task-estimate/help.md +158 -0
  245. package/plugin/skills/ase-task-grill/SKILL.md +246 -138
  246. package/plugin/skills/ase-task-grill/help.md +129 -26
  247. package/plugin/skills/ase-task-id/SKILL.md +8 -1
  248. package/plugin/skills/ase-task-id/help.md +6 -3
  249. package/plugin/skills/ase-task-implement/SKILL.md +535 -145
  250. package/plugin/skills/ase-task-implement/help.md +138 -47
  251. package/plugin/skills/ase-task-integrate/SKILL.md +549 -0
  252. package/plugin/skills/ase-task-integrate/help.md +187 -0
  253. package/plugin/skills/ase-task-list/SKILL.md +55 -23
  254. package/plugin/skills/ase-task-list/help.md +45 -21
  255. package/plugin/skills/ase-task-reboot/SKILL.md +84 -43
  256. package/plugin/skills/ase-task-reboot/help.md +28 -8
  257. package/plugin/skills/ase-task-rename/SKILL.md +10 -2
  258. package/plugin/skills/ase-task-research/SKILL.md +386 -0
  259. package/plugin/skills/ase-task-research/help.md +102 -0
  260. package/plugin/skills/ase-task-status/SKILL.md +137 -0
  261. package/plugin/skills/ase-task-status/help.md +80 -0
  262. package/plugin/skills/ase-task-view/SKILL.md +49 -24
  263. package/plugin/skills/ase-task-view/help.md +19 -9
  264. package/plugin/agents/ase-meta-review.md +0 -187
  265. package/plugin/skills/ase-code-craft/SKILL.md +0 -264
  266. package/plugin/skills/ase-code-craft/help.md +0 -122
  267. package/plugin/skills/ase-code-refactor/SKILL.md +0 -232
  268. package/plugin/skills/ase-code-refactor/help.md +0 -109
  269. package/plugin/skills/ase-code-resolve/SKILL.md +0 -289
  270. package/plugin/skills/ase-code-resolve/help.md +0 -116
  271. package/plugin/skills/ase-meta-review/SKILL.md +0 -210
  272. package/plugin/skills/ase-meta-review/help.md +0 -96
  273. package/plugin/skills/ase-task-preflight/SKILL.md +0 -166
  274. package/plugin/skills/ase-task-preflight/help.md +0 -82
  275. /package/dst/{ase-ignore.js → ase-lib-ignore.js} +0 -0
  276. /package/dst/{ase-stdio.js → ase-lib-stdio.js} +0 -0
  277. /package/dst/{ase-kv.js → ase-service-kv.js} +0 -0
  278. /package/dst/{ase-sleep.js → ase-service-sleep.js} +0 -0
  279. /package/dst/{ase-timestamp.js → ase-service-timestamp.js} +0 -0
package/dst/ase-spec.js CHANGED
@@ -8,16 +8,23 @@ import fs from "node:fs";
8
8
  import { isScalar } from "yaml";
9
9
  import { z } from "zod";
10
10
  import sourceCodeError from "source-code-error";
11
- import { SpecBook, renderDiagnostic, renderVerbose, formats, parseOutputSpec, previewAddr, previewPort } from "@rse/specbook";
12
- import { Config, configSchema } from "./ase-config.js";
11
+ import { renderDiagnostic } from "@rse/specbook/dst/specbook-diagnostic.js";
12
+ import { renderVerbose } from "@rse/specbook/dst/specbook-verbose.js";
13
+ import { Config } from "./ase-config-core.js";
14
+ import { configSchema } from "./ase-config-schema.js";
13
15
  import { Task } from "./ase-task.js";
14
16
  import { Artifact } from "./ase-artifact.js";
15
- import { Meta } from "./ase-meta.js";
16
- import { writeStdout } from "./ase-stdio.js";
17
+ import { Meta } from "./ase-util-meta.js";
18
+ import { writeStdout } from "./ase-lib-stdio.js";
17
19
  /* reusable functionality: lint, export, and preview the SpecBook-based
18
20
  project specification, located via the "project.artifact.spec.basedir"
19
21
  and "project.artifact.spec.schema" configuration */
20
22
  export class Spec {
23
+ /* the SpecBook export formats, mirrored here (and checked for completeness
24
+ against SpecBook at compile-time) to not load SpecBook for the MCP schema */
25
+ static formats = Object.keys({
26
+ json: true, json5: true, yaml: true, toon: true, html: true, pdf: true, md: true
27
+ });
21
28
  /* resolve the YAML schema configuration files: the whitespace-separated
22
29
  entries of the configured "project.artifact.spec.schema", merged in
23
30
  the given order, where each entry is either the literal "std" for the
@@ -51,8 +58,10 @@ export class Spec {
51
58
  /* create the SpecBook API instance, routing its verbose processing
52
59
  messages into the log according to their verbosity level, while
53
60
  its "none" level messages, for consumers which never see the log,
54
- additionally reach the given collector */
55
- static api(log, notices) {
61
+ additionally reach the given collector
62
+ (SpecBook itself is loaded on first use only) */
63
+ static async api(log, notices) {
64
+ const { SpecBook } = await import("@rse/specbook");
56
65
  return new SpecBook({
57
66
  verbose: (cmd, msg, level) => {
58
67
  const text = renderVerbose(msg);
@@ -93,11 +102,11 @@ export class Spec {
93
102
  /* lint the specification Markdown files below the "spec" artifact
94
103
  base directory against the schema configuration */
95
104
  static async lint(log) {
96
- const result = await Spec.unmarked(Spec.api(log).lint({
105
+ const result = await Spec.unmarked(Spec.api(log).then((api) => api.lint({
97
106
  config: Spec.configFiles(log),
98
107
  basedir: Artifact.basedir(log, "spec"),
99
108
  gitignore: Spec.gitignore
100
- }));
109
+ })));
101
110
  return result.diagnostics.map((d) => ({ ...d, file: Spec.relativize(d.file) }));
102
111
  }
103
112
  /* render a diagnostic as a multi-line message with the affected
@@ -128,38 +137,38 @@ export class Spec {
128
137
  base directory into the requested formats, one buffer per format,
129
138
  collecting the emitted environment notices if requested */
130
139
  static export(log, formats, notices) {
131
- return Spec.unmarked(Spec.api(log, notices).export({
140
+ return Spec.unmarked(Spec.api(log, notices).then((api) => api.export({
132
141
  config: Spec.configFiles(log),
133
142
  basedir: Artifact.basedir(log, "spec"),
134
143
  gitignore: Spec.gitignore,
135
144
  formats
136
- }));
145
+ })));
137
146
  }
138
147
  /* export the specification like "export" and then keep the export
139
148
  in sync with its sources, handing every fresh set of buffers to
140
149
  "onExport", where "outputs" names the files "onExport" writes,
141
150
  so an output which is itself an observed source can be refused */
142
151
  static watch(log, formats, outputs, onExport) {
143
- return Spec.unmarked(Spec.api(log).watch({
152
+ return Spec.unmarked(Spec.api(log).then((api) => api.watch({
144
153
  config: Spec.configFiles(log),
145
154
  basedir: Artifact.basedir(log, "spec"),
146
155
  gitignore: Spec.gitignore,
147
156
  formats,
148
157
  outputs,
149
158
  onExport
150
- }));
159
+ })));
151
160
  }
152
161
  /* serve the HTML export of the specification as a live preview,
153
162
  kept in sync with its sources and pushed into the connected
154
163
  browsers as an in-place document update */
155
164
  static preview(log, addr, port) {
156
- return Spec.unmarked(Spec.api(log).preview({
165
+ return Spec.unmarked(Spec.api(log).then((api) => api.preview({
157
166
  config: Spec.configFiles(log),
158
167
  basedir: Artifact.basedir(log, "spec"),
159
168
  gitignore: Spec.gitignore,
160
169
  addr,
161
170
  port
162
- }));
171
+ })));
163
172
  }
164
173
  }
165
174
  /* CLI command "ase spec" */
@@ -201,6 +210,7 @@ export default class SpecCommand {
201
210
  "(default: \"index.html\" inside the specification base directory)", (value, previous) => previous.concat(value), new Array())
202
211
  .option("-w, --watch", "keep the outputs in sync by re-exporting on every source change")
203
212
  .action(async (opts) => {
213
+ const { parseOutputSpec } = await import("@rse/specbook");
204
214
  const outputs = (opts.output.length > 0 ? opts.output :
205
215
  [path.join(Artifact.basedir(this.log, "spec"), "index.html")]).map(parseOutputSpec);
206
216
  /* a re-export has to land somewhere it can be picked up again,
@@ -232,13 +242,14 @@ export default class SpecCommand {
232
242
  spec
233
243
  .command("preview")
234
244
  .description("Serve the HTML export of the specification Markdown files as a live preview")
235
- .option("-a, --addr <ip-addr>", "IP address to listen on", previewAddr)
236
- .option("-p, --port <tcp-port>", "TCP port to listen on", String(previewPort))
245
+ .option("-a, --addr <ip-addr>", "IP address to listen on (default: SpecBook preview address)")
246
+ .option("-p, --port <tcp-port>", "TCP port to listen on (default: SpecBook preview port)")
237
247
  .action(async (opts) => {
238
- const port = Number(opts.port);
248
+ const { previewAddr, previewPort } = await import("@rse/specbook");
249
+ const port = Number(opts.port ?? previewPort);
239
250
  if (!Number.isInteger(port) || port < 1 || port > 65535)
240
251
  throw new Error(`invalid TCP port "${opts.port}"`);
241
- await Spec.preview(this.log, opts.addr, port);
252
+ await Spec.preview(this.log, opts.addr ?? previewAddr, port);
242
253
  await new Promise(() => { });
243
254
  });
244
255
  }
@@ -314,7 +325,7 @@ export class SpecMCP {
314
325
  "(like a PDF rendering falling back onto a system-installed browser), which are " +
315
326
  "worth reporting to the user verbatim.",
316
327
  inputSchema: {
317
- format: z.enum(formats).optional()
328
+ format: z.enum(Spec.formats).optional()
318
329
  .describe("output format (default: inferred from the `output` file extension, else `json`)"),
319
330
  output: z.string().optional()
320
331
  .describe("output file path (\"-\" or omitted returns the result directly)")
@@ -326,6 +337,7 @@ export class SpecMCP {
326
337
  }, async (args) => {
327
338
  const notices = new Array();
328
339
  try {
340
+ const { parseOutputSpec } = await import("@rse/specbook");
329
341
  /* an explicit format takes the output as a plain file path, while
330
342
  otherwise the output is an "[<format>:]<file>" specification */
331
343
  const spec = args.format !== undefined || args.output === undefined ?
@@ -10,8 +10,10 @@ import { execFileSync } from "node:child_process";
10
10
  import { InvalidArgumentError } from "commander";
11
11
  import { execaSync } from "execa";
12
12
  import { Chalk } from "chalk";
13
- import { Config, configSchema, parseScope } from "./ase-config.js";
14
- import { readStdin, writeStdout } from "./ase-stdio.js";
13
+ import { Config, sessionState } from "./ase-config-core.js";
14
+ import { configSchema } from "./ase-config-schema.js";
15
+ import { parseScope, userStateDir, projectIdOf } from "./ase-config-scope.js";
16
+ import { readStdin, writeStdout } from "./ase-lib-stdio.js";
15
17
  import pkg from "../package.json" with { type: "json" };
16
18
  /* forced-color chalk instance: stdout is a pipe under Anthropic Claude Code CLI,
17
19
  so chalk auto-detection would yield level 0; force level 1 to keep
@@ -181,6 +183,69 @@ const memoize = (fn) => {
181
183
  let cache = null;
182
184
  return () => (cache ??= { value: fn() }).value;
183
185
  };
186
+ /* the ChatGPT backend endpoint providing the OpenAI Codex subscription usage */
187
+ const CODEX_USAGE = "https://chatgpt.com/backend-api/wham/usage";
188
+ /* time-to-live (in milliseconds) of the cached OpenAI Codex subscription usage */
189
+ const CODEX_USAGE_TTL = 60 * 1000;
190
+ /* fetch the rate-limit windows directly from the ChatGPT backend with the credential
191
+ stored by the OpenAI Codex CLI; the result (even a failed one) is cached for a short
192
+ time, as the statusline is rendered frequently, and no failure is ever raised */
193
+ const fetchCodexLimits = async () => {
194
+ /* reuse the cached result while it is still fresh */
195
+ const cacheFile = path.join(userStateDir(), "statusline-codex-usage.json");
196
+ let cache = {};
197
+ try {
198
+ cache = (JSON.parse(fs.readFileSync(cacheFile, "utf8")) ?? {});
199
+ }
200
+ catch (_e) {
201
+ /* no (valid) cache yet */
202
+ }
203
+ if (typeof cache.time === "number" && Math.abs(Date.now() - cache.time) < CODEX_USAGE_TTL)
204
+ return cache.limits;
205
+ /* query the usage endpoint (keeping the stale result on any failure) */
206
+ let limits = cache.limits;
207
+ try {
208
+ const home = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
209
+ const auth = JSON.parse(fs.readFileSync(path.join(home, "auth.json"), "utf8"));
210
+ const token = auth?.tokens?.access_token;
211
+ if (typeof token === "string" && token !== "") {
212
+ const res = await fetch(CODEX_USAGE, {
213
+ headers: {
214
+ "Authorization": `Bearer ${token}`,
215
+ "ChatGPT-Account-ID": auth?.tokens?.account_id ?? "",
216
+ "User-Agent": `ase/${pkg.version}`
217
+ },
218
+ signal: AbortSignal.timeout(3 * 1000)
219
+ });
220
+ if (res.ok) {
221
+ /* classify windows by their length, as plans without a 5h limit
222
+ report the 7d window in the primary slot */
223
+ const usage = await res.json();
224
+ const wins = [usage?.rate_limit?.primary_window, usage?.rate_limit?.secondary_window]
225
+ .filter((w) => w !== undefined && w !== null);
226
+ const win5h = wins.find((w) => (w.limit_window_seconds ?? 5 * 3600) < 24 * 3600);
227
+ const win7d = wins.find((w) => (w.limit_window_seconds ?? 0) >= 24 * 3600);
228
+ limits = {};
229
+ if (win5h !== undefined)
230
+ limits.five_hour = { used_percentage: win5h.used_percent, resets_at: win5h.reset_at };
231
+ if (win7d !== undefined)
232
+ limits.seven_day = { used_percentage: win7d.used_percent, resets_at: win7d.reset_at };
233
+ }
234
+ }
235
+ }
236
+ catch (_e) {
237
+ /* credential unreadable or endpoint unreachable */
238
+ }
239
+ /* update the cache */
240
+ try {
241
+ fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
242
+ fs.writeFileSync(cacheFile, JSON.stringify({ time: Date.now(), limits }) + "\n", "utf8");
243
+ }
244
+ catch (_e) {
245
+ /* best-effort */
246
+ }
247
+ return limits;
248
+ };
184
249
  /* command-line handling */
185
250
  export default class StatuslineCommand {
186
251
  log;
@@ -209,7 +274,7 @@ export default class StatuslineCommand {
209
274
  .option("-p, --padding <n>", "pad each rendered line with <n> spaces on each side", parseInteger("--padding"), 0)
210
275
  .option("--no-icons", "disable icons in placeholder rendering")
211
276
  .option("--no-labels", "disable labels in front of bold values")
212
- .argument("[lines...]", "one or more template lines with %u %p %T %s %m %e %t %O %P %h %B %c %C %a %r " +
277
+ .argument("[lines...]", "one or more template lines with %u %p %T %s %m %e %t %O %P %h %B %L %c %C %a %r " +
213
278
  "%S %D %W %Q %H %X %b %g %G %d %M %V placeholders and <color>...</color> markup " +
214
279
  "(color: black, red, green, yellow, blue, magenta, cyan, white, default) " +
215
280
  "(default: single line \"%m %e %t\")")
@@ -282,30 +347,32 @@ export default class StatuslineCommand {
282
347
  post-loop tmux publish for the Config cascade) */
283
348
  const getSession = memoize(() => data.session_id ?? "unknown");
284
349
  const getCfg = memoize(() => {
285
- let taskId = process.env.ASE_TASK_ID ?? "";
350
+ let taskId = process.env.ASE_TASK_ID ?? "default";
286
351
  let persona = process.env.ASE_PERSONA_STYLE ?? "";
287
352
  let guidance = process.env.ASE_GUIDANCE_LEVEL ?? "";
288
353
  let boxing = process.env.ASE_PROJECT_BOXING ?? "";
354
+ let lifecycle = process.env.ASE_PROJECT_TASK_LIFECYCLE ?? "";
289
355
  try {
290
356
  const cfg = new Config("config", configSchema, this.log, parseScope(`session:${getSession()}`));
291
357
  cfg.read("lenient");
292
- const t = String(cfg.get("agent.task") ?? "").trim();
293
- const p = String(cfg.get("agent.persona") ?? "").trim();
294
- const g = String(cfg.get("agent.guidance") ?? "").trim();
295
- const b = String(cfg.get("project.boxing") ?? "").trim();
296
- if (t !== "")
297
- taskId = t;
298
- if (p !== "")
299
- persona = p;
300
- if (g !== "")
301
- guidance = g;
302
- if (b !== "")
303
- boxing = b;
358
+ const get = (key, fallback) => String(cfg.get(key) ?? "").trim() || fallback;
359
+ persona = get("agent.persona", persona);
360
+ guidance = get("agent.guidance", guidance);
361
+ boxing = get("project.boxing", boxing);
362
+ lifecycle = get("project.task.lifecycle", lifecycle);
304
363
  }
305
364
  catch (_e) {
306
365
  /* cascade unavailable; keep env-var fallbacks */
307
366
  }
308
- return { taskId, persona, guidance, boxing };
367
+ try {
368
+ const state = sessionState(this.log, getSession());
369
+ state.read("lenient");
370
+ taskId = String(state.get("task") ?? "").trim() || taskId;
371
+ }
372
+ catch (_e) {
373
+ /* session state unavailable; keep env-var fallback */
374
+ }
375
+ return { taskId, persona, guidance, boxing, lifecycle };
309
376
  });
310
377
  const getModel = memoize(() => {
311
378
  const display = data.model?.display_name ?? "";
@@ -318,6 +385,19 @@ export default class StatuslineCommand {
318
385
  });
319
386
  const getGit = memoize(() => probeGit(data.workspace?.current_dir ?? ""));
320
387
  const getMem = memoize(() => probeMemory());
388
+ /* determine the rate-limit windows: regularly supplied by the harness on stdin,
389
+ but for OpenAI GPT models fetched directly as a fallback (and only on demand) */
390
+ let limits = data.rate_limits;
391
+ if (limits?.five_hour === undefined && limits?.seven_day === undefined
392
+ && /^gpt-.+/i.test(getModel().name)
393
+ && tmpl.some((line) => /%[SDWQ]/.test(line)))
394
+ limits = await fetchCodexLimits();
395
+ /* render a configuration value (if non-empty) with its icon and label */
396
+ const cfgValue = (key, icon, label) => () => {
397
+ const value = getCfg()[key];
398
+ if (value !== "")
399
+ emit(`${prefix(icon, label)}${c.bold(value)}`);
400
+ };
321
401
  /* identifier to renderer map: each callback fetches its own information
322
402
  directly from data (or via the lazy helpers above for shared values) */
323
403
  const renderers = {
@@ -327,14 +407,10 @@ export default class StatuslineCommand {
327
407
  emit(`${prefix("※", "user")}${c.bold(user)}`);
328
408
  },
329
409
  p: () => {
330
- const dir = path.basename(data.workspace?.current_dir ?? "");
331
- emit(`${prefix("⚑", "project")}${c.bold(dir)}`);
332
- },
333
- T: () => {
334
- const { taskId } = getCfg();
335
- if (taskId !== "")
336
- emit(`${prefix("◉", "task")}${c.bold(taskId)}`);
410
+ const id = projectIdOf(data.workspace?.current_dir ?? "");
411
+ emit(`${prefix("⚑", "project")}${c.bold(id)}`);
337
412
  },
413
+ T: cfgValue("taskId", "◉", "task"),
338
414
  s: () => emit(`${prefix("⏻", "session")}${c.bold(getSession())}`),
339
415
  /* ==== MODEL ==== */
340
416
  m: () => {
@@ -355,21 +431,10 @@ export default class StatuslineCommand {
355
431
  if (styleName !== "")
356
432
  emit(`${prefix("≡", "style")}${c.bold(styleName)}`);
357
433
  },
358
- P: () => {
359
- const { persona } = getCfg();
360
- if (persona !== "")
361
- emit(`${prefix("☯", "persona")}${c.bold(persona)}`);
362
- },
363
- h: () => {
364
- const { guidance } = getCfg();
365
- if (guidance !== "")
366
- emit(`${prefix("▶", "guidance")}${c.bold(guidance)}`);
367
- },
368
- B: () => {
369
- const { boxing } = getCfg();
370
- if (boxing !== "")
371
- emit(`${prefix("▢", "boxing")}${c.bold(boxing)}`);
372
- },
434
+ P: cfgValue("persona", "☯", "persona"),
435
+ h: cfgValue("guidance", "▶", "guidance"),
436
+ B: cfgValue("boxing", "▢", "boxing"),
437
+ L: cfgValue("lifecycle", "⟳", "lifecycle"),
373
438
  /* ==== CONTEXT ==== */
374
439
  c: () => {
375
440
  const pct = Math.min(100, Math.max(0, Math.floor(data.context_window?.used_percentage ?? 0)));
@@ -396,22 +461,22 @@ export default class StatuslineCommand {
396
461
  },
397
462
  /* ==== RATE LIMITS ==== */
398
463
  S: () => {
399
- const pct5h = data.rate_limits?.five_hour?.used_percentage;
464
+ const pct5h = limits?.five_hour?.used_percentage;
400
465
  const s = pct5h !== undefined ? `${pct5h.toFixed(1)}%` : "-";
401
466
  emit(`${prefix("⏲", "5h-usage")}${c.bold(s)}`);
402
467
  },
403
468
  D: () => {
404
- const until5h = data.rate_limits?.five_hour?.resets_at;
469
+ const until5h = limits?.five_hour?.resets_at;
405
470
  const s = formatTimeUntil(until5h);
406
471
  emit(`${prefix("⏱", "5h-resets")}${c.bold(s !== "" ? s : "-")}`);
407
472
  },
408
473
  W: () => {
409
- const pctWk = data.rate_limits?.seven_day?.used_percentage;
474
+ const pctWk = limits?.seven_day?.used_percentage;
410
475
  const s = pctWk !== undefined ? `${pctWk.toFixed(1)}%` : "-";
411
476
  emit(`${prefix("⏲", "7d-usage")}${c.bold(s)}`);
412
477
  },
413
478
  Q: () => {
414
- const untilWk = data.rate_limits?.seven_day?.resets_at;
479
+ const untilWk = limits?.seven_day?.resets_at;
415
480
  const s = formatTimeUntil(untilWk);
416
481
  emit(`${prefix("⏱", "7d-resets")}${c.bold(s !== "" ? s : "-")}`);
417
482
  },