nubos-pilot 0.7.2 → 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.
Files changed (241) hide show
  1. package/agents/np-executor.md +1 -0
  2. package/bin/install.js +2 -0
  3. package/bin/np-tools/_commands.cjs +98 -64
  4. package/bin/np-tools/dashboard.cjs +11 -5
  5. package/bin/np-tools/dashboard.test.cjs +80 -0
  6. package/bin/np-tools/help.cjs +16 -7
  7. package/bin/np-tools/help.test.cjs +63 -0
  8. package/bin/np-tools/stats.cjs +99 -3
  9. package/bin/np-tools/stats.test.cjs +65 -0
  10. package/lib/dashboard.cjs +41 -10
  11. package/lib/dashboard.test.cjs +83 -0
  12. package/lib/install/runtime-assets.cjs +50 -1
  13. package/lib/install/runtime-assets.test.cjs +190 -0
  14. package/lib/install/runtimes-registry.cjs +1 -0
  15. package/lib/runtime/_readline.cjs +49 -11
  16. package/lib/runtime/_readline.test.cjs +59 -0
  17. package/lib/runtime/claude.cjs +8 -1
  18. package/package.json +2 -1
  19. package/skills/np-composition-patterns/AGENTS.md +946 -0
  20. package/skills/np-composition-patterns/README.md +60 -0
  21. package/skills/np-composition-patterns/SKILL.md +89 -0
  22. package/skills/np-composition-patterns/metadata.json +11 -0
  23. package/skills/np-composition-patterns/rules/_sections.md +29 -0
  24. package/skills/np-composition-patterns/rules/_template.md +24 -0
  25. package/skills/np-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
  26. package/skills/np-composition-patterns/rules/architecture-compound-components.md +112 -0
  27. package/skills/np-composition-patterns/rules/patterns-children-over-render-props.md +87 -0
  28. package/skills/np-composition-patterns/rules/patterns-explicit-variants.md +100 -0
  29. package/skills/np-composition-patterns/rules/react19-no-forwardref.md +42 -0
  30. package/skills/np-composition-patterns/rules/state-context-interface.md +191 -0
  31. package/skills/np-composition-patterns/rules/state-decouple-implementation.md +113 -0
  32. package/skills/np-composition-patterns/rules/state-lift-state.md +125 -0
  33. package/skills/np-council/SKILL.md +300 -0
  34. package/skills/np-design/SKILL.md +679 -0
  35. package/skills/np-frontend-design/LICENSE.txt +177 -0
  36. package/skills/np-frontend-design/SKILL.md +42 -0
  37. package/skills/np-high-end-visual-design/SKILL.md +98 -0
  38. package/skills/np-impeccable/SKILL.md +152 -0
  39. package/skills/np-impeccable/agents/openai.yaml +4 -0
  40. package/skills/np-impeccable/reference/adapt.md +190 -0
  41. package/skills/np-impeccable/reference/animate.md +173 -0
  42. package/skills/np-impeccable/reference/audit.md +134 -0
  43. package/skills/np-impeccable/reference/bolder.md +113 -0
  44. package/skills/np-impeccable/reference/brand.md +104 -0
  45. package/skills/np-impeccable/reference/clarify.md +174 -0
  46. package/skills/np-impeccable/reference/cognitive-load.md +106 -0
  47. package/skills/np-impeccable/reference/color-and-contrast.md +105 -0
  48. package/skills/np-impeccable/reference/colorize.md +154 -0
  49. package/skills/np-impeccable/reference/craft.md +138 -0
  50. package/skills/np-impeccable/reference/critique.md +213 -0
  51. package/skills/np-impeccable/reference/delight.md +302 -0
  52. package/skills/np-impeccable/reference/distill.md +111 -0
  53. package/skills/np-impeccable/reference/document.md +427 -0
  54. package/skills/np-impeccable/reference/extract.md +70 -0
  55. package/skills/np-impeccable/reference/harden.md +347 -0
  56. package/skills/np-impeccable/reference/heuristics-scoring.md +234 -0
  57. package/skills/np-impeccable/reference/interaction-design.md +195 -0
  58. package/skills/np-impeccable/reference/layout.md +141 -0
  59. package/skills/np-impeccable/reference/live.md +513 -0
  60. package/skills/np-impeccable/reference/motion-design.md +99 -0
  61. package/skills/np-impeccable/reference/onboard.md +234 -0
  62. package/skills/np-impeccable/reference/optimize.md +258 -0
  63. package/skills/np-impeccable/reference/overdrive.md +130 -0
  64. package/skills/np-impeccable/reference/personas.md +178 -0
  65. package/skills/np-impeccable/reference/polish.md +232 -0
  66. package/skills/np-impeccable/reference/product.md +62 -0
  67. package/skills/np-impeccable/reference/quieter.md +99 -0
  68. package/skills/np-impeccable/reference/responsive-design.md +114 -0
  69. package/skills/np-impeccable/reference/shape.md +136 -0
  70. package/skills/np-impeccable/reference/spatial-design.md +100 -0
  71. package/skills/np-impeccable/reference/teach.md +137 -0
  72. package/skills/np-impeccable/reference/typeset.md +124 -0
  73. package/skills/np-impeccable/reference/typography.md +159 -0
  74. package/skills/np-impeccable/reference/ux-writing.md +107 -0
  75. package/skills/np-impeccable/scripts/cleanup-deprecated.mjs +284 -0
  76. package/skills/np-impeccable/scripts/command-metadata.json +94 -0
  77. package/skills/np-impeccable/scripts/design-parser.mjs +820 -0
  78. package/skills/np-impeccable/scripts/detect-csp.mjs +198 -0
  79. package/skills/np-impeccable/scripts/is-generated.mjs +69 -0
  80. package/skills/np-impeccable/scripts/live-accept.mjs +465 -0
  81. package/skills/np-impeccable/scripts/live-browser.js +4684 -0
  82. package/skills/np-impeccable/scripts/live-inject.mjs +436 -0
  83. package/skills/np-impeccable/scripts/live-poll.mjs +187 -0
  84. package/skills/np-impeccable/scripts/live-server.mjs +679 -0
  85. package/skills/np-impeccable/scripts/live-wrap.mjs +395 -0
  86. package/skills/np-impeccable/scripts/live.mjs +247 -0
  87. package/skills/np-impeccable/scripts/load-context.mjs +93 -0
  88. package/skills/np-impeccable/scripts/modern-screenshot.umd.js +14 -0
  89. package/skills/np-impeccable/scripts/pin.mjs +214 -0
  90. package/skills/np-industrial-brutalist-ui/SKILL.md +92 -0
  91. package/skills/np-minimalist-ui/SKILL.md +85 -0
  92. package/skills/np-react-best-practices/AGENTS.md +3810 -0
  93. package/skills/np-react-best-practices/README.md +123 -0
  94. package/skills/np-react-best-practices/SKILL.md +149 -0
  95. package/skills/np-react-best-practices/metadata.json +15 -0
  96. package/skills/np-react-best-practices/rules/_sections.md +46 -0
  97. package/skills/np-react-best-practices/rules/_template.md +28 -0
  98. package/skills/np-react-best-practices/rules/advanced-effect-event-deps.md +56 -0
  99. package/skills/np-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  100. package/skills/np-react-best-practices/rules/advanced-init-once.md +42 -0
  101. package/skills/np-react-best-practices/rules/advanced-use-latest.md +39 -0
  102. package/skills/np-react-best-practices/rules/async-api-routes.md +38 -0
  103. package/skills/np-react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
  104. package/skills/np-react-best-practices/rules/async-defer-await.md +82 -0
  105. package/skills/np-react-best-practices/rules/async-dependencies.md +51 -0
  106. package/skills/np-react-best-practices/rules/async-parallel.md +28 -0
  107. package/skills/np-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  108. package/skills/np-react-best-practices/rules/bundle-analyzable-paths.md +63 -0
  109. package/skills/np-react-best-practices/rules/bundle-barrel-imports.md +60 -0
  110. package/skills/np-react-best-practices/rules/bundle-conditional.md +31 -0
  111. package/skills/np-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  112. package/skills/np-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  113. package/skills/np-react-best-practices/rules/bundle-preload.md +50 -0
  114. package/skills/np-react-best-practices/rules/client-event-listeners.md +74 -0
  115. package/skills/np-react-best-practices/rules/client-localstorage-schema.md +71 -0
  116. package/skills/np-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  117. package/skills/np-react-best-practices/rules/client-swr-dedup.md +56 -0
  118. package/skills/np-react-best-practices/rules/js-batch-dom-css.md +107 -0
  119. package/skills/np-react-best-practices/rules/js-cache-function-results.md +80 -0
  120. package/skills/np-react-best-practices/rules/js-cache-property-access.md +28 -0
  121. package/skills/np-react-best-practices/rules/js-cache-storage.md +70 -0
  122. package/skills/np-react-best-practices/rules/js-combine-iterations.md +32 -0
  123. package/skills/np-react-best-practices/rules/js-early-exit.md +50 -0
  124. package/skills/np-react-best-practices/rules/js-flatmap-filter.md +60 -0
  125. package/skills/np-react-best-practices/rules/js-hoist-regexp.md +45 -0
  126. package/skills/np-react-best-practices/rules/js-index-maps.md +37 -0
  127. package/skills/np-react-best-practices/rules/js-length-check-first.md +49 -0
  128. package/skills/np-react-best-practices/rules/js-min-max-loop.md +82 -0
  129. package/skills/np-react-best-practices/rules/js-request-idle-callback.md +105 -0
  130. package/skills/np-react-best-practices/rules/js-set-map-lookups.md +24 -0
  131. package/skills/np-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  132. package/skills/np-react-best-practices/rules/rendering-activity.md +26 -0
  133. package/skills/np-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  134. package/skills/np-react-best-practices/rules/rendering-conditional-render.md +40 -0
  135. package/skills/np-react-best-practices/rules/rendering-content-visibility.md +38 -0
  136. package/skills/np-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  137. package/skills/np-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  138. package/skills/np-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  139. package/skills/np-react-best-practices/rules/rendering-resource-hints.md +85 -0
  140. package/skills/np-react-best-practices/rules/rendering-script-defer-async.md +68 -0
  141. package/skills/np-react-best-practices/rules/rendering-svg-precision.md +28 -0
  142. package/skills/np-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  143. package/skills/np-react-best-practices/rules/rerender-defer-reads.md +39 -0
  144. package/skills/np-react-best-practices/rules/rerender-dependencies.md +45 -0
  145. package/skills/np-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  146. package/skills/np-react-best-practices/rules/rerender-derived-state.md +29 -0
  147. package/skills/np-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  148. package/skills/np-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  149. package/skills/np-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  150. package/skills/np-react-best-practices/rules/rerender-memo.md +44 -0
  151. package/skills/np-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  152. package/skills/np-react-best-practices/rules/rerender-no-inline-components.md +82 -0
  153. package/skills/np-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  154. package/skills/np-react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
  155. package/skills/np-react-best-practices/rules/rerender-transitions.md +40 -0
  156. package/skills/np-react-best-practices/rules/rerender-use-deferred-value.md +59 -0
  157. package/skills/np-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  158. package/skills/np-react-best-practices/rules/server-after-nonblocking.md +73 -0
  159. package/skills/np-react-best-practices/rules/server-auth-actions.md +96 -0
  160. package/skills/np-react-best-practices/rules/server-cache-lru.md +41 -0
  161. package/skills/np-react-best-practices/rules/server-cache-react.md +76 -0
  162. package/skills/np-react-best-practices/rules/server-dedup-props.md +65 -0
  163. package/skills/np-react-best-practices/rules/server-hoist-static-io.md +149 -0
  164. package/skills/np-react-best-practices/rules/server-no-shared-module-state.md +50 -0
  165. package/skills/np-react-best-practices/rules/server-parallel-fetching.md +83 -0
  166. package/skills/np-react-best-practices/rules/server-parallel-nested-fetching.md +34 -0
  167. package/skills/np-react-best-practices/rules/server-serialization.md +38 -0
  168. package/skills/np-react-native-skills/AGENTS.md +2897 -0
  169. package/skills/np-react-native-skills/README.md +165 -0
  170. package/skills/np-react-native-skills/SKILL.md +121 -0
  171. package/skills/np-react-native-skills/metadata.json +16 -0
  172. package/skills/np-react-native-skills/rules/_sections.md +86 -0
  173. package/skills/np-react-native-skills/rules/_template.md +28 -0
  174. package/skills/np-react-native-skills/rules/animation-derived-value.md +53 -0
  175. package/skills/np-react-native-skills/rules/animation-gesture-detector-press.md +95 -0
  176. package/skills/np-react-native-skills/rules/animation-gpu-properties.md +65 -0
  177. package/skills/np-react-native-skills/rules/design-system-compound-components.md +66 -0
  178. package/skills/np-react-native-skills/rules/fonts-config-plugin.md +71 -0
  179. package/skills/np-react-native-skills/rules/imports-design-system-folder.md +68 -0
  180. package/skills/np-react-native-skills/rules/js-hoist-intl.md +61 -0
  181. package/skills/np-react-native-skills/rules/list-performance-callbacks.md +44 -0
  182. package/skills/np-react-native-skills/rules/list-performance-function-references.md +132 -0
  183. package/skills/np-react-native-skills/rules/list-performance-images.md +53 -0
  184. package/skills/np-react-native-skills/rules/list-performance-inline-objects.md +97 -0
  185. package/skills/np-react-native-skills/rules/list-performance-item-expensive.md +94 -0
  186. package/skills/np-react-native-skills/rules/list-performance-item-memo.md +82 -0
  187. package/skills/np-react-native-skills/rules/list-performance-item-types.md +104 -0
  188. package/skills/np-react-native-skills/rules/list-performance-virtualize.md +67 -0
  189. package/skills/np-react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
  190. package/skills/np-react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
  191. package/skills/np-react-native-skills/rules/navigation-native-navigators.md +188 -0
  192. package/skills/np-react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
  193. package/skills/np-react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
  194. package/skills/np-react-native-skills/rules/react-state-dispatcher.md +91 -0
  195. package/skills/np-react-native-skills/rules/react-state-fallback.md +56 -0
  196. package/skills/np-react-native-skills/rules/react-state-minimize.md +65 -0
  197. package/skills/np-react-native-skills/rules/rendering-no-falsy-and.md +74 -0
  198. package/skills/np-react-native-skills/rules/rendering-text-in-text-component.md +36 -0
  199. package/skills/np-react-native-skills/rules/scroll-position-no-state.md +82 -0
  200. package/skills/np-react-native-skills/rules/state-ground-truth.md +80 -0
  201. package/skills/np-react-native-skills/rules/ui-expo-image.md +66 -0
  202. package/skills/np-react-native-skills/rules/ui-image-gallery.md +104 -0
  203. package/skills/np-react-native-skills/rules/ui-measure-views.md +78 -0
  204. package/skills/np-react-native-skills/rules/ui-menus.md +174 -0
  205. package/skills/np-react-native-skills/rules/ui-native-modals.md +77 -0
  206. package/skills/np-react-native-skills/rules/ui-pressable.md +61 -0
  207. package/skills/np-react-native-skills/rules/ui-safe-area-scroll.md +65 -0
  208. package/skills/np-react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
  209. package/skills/np-react-native-skills/rules/ui-styling.md +87 -0
  210. package/skills/np-react-view-transitions/AGENTS.md +955 -0
  211. package/skills/np-react-view-transitions/README.md +42 -0
  212. package/skills/np-react-view-transitions/SKILL.md +320 -0
  213. package/skills/np-react-view-transitions/metadata.json +12 -0
  214. package/skills/np-react-view-transitions/references/css-recipes.md +242 -0
  215. package/skills/np-react-view-transitions/references/implementation.md +182 -0
  216. package/skills/np-react-view-transitions/references/nextjs.md +176 -0
  217. package/skills/np-react-view-transitions/references/patterns.md +262 -0
  218. package/skills/np-redesign-existing-projects/SKILL.md +178 -0
  219. package/skills/np-shadcn/SKILL.md +250 -0
  220. package/skills/np-shadcn/agents/openai.yml +5 -0
  221. package/skills/np-shadcn/assets/shadcn-small.png +0 -0
  222. package/skills/np-shadcn/assets/shadcn.png +0 -0
  223. package/skills/np-shadcn/cli.md +276 -0
  224. package/skills/np-shadcn/customization.md +209 -0
  225. package/skills/np-shadcn/evals/evals.json +47 -0
  226. package/skills/np-shadcn/mcp.md +94 -0
  227. package/skills/np-shadcn/rules/base-vs-radix.md +306 -0
  228. package/skills/np-shadcn/rules/composition.md +195 -0
  229. package/skills/np-shadcn/rules/forms.md +192 -0
  230. package/skills/np-shadcn/rules/icons.md +101 -0
  231. package/skills/np-shadcn/rules/styling.md +162 -0
  232. package/skills/np-stitch-design-taste/DESIGN.md +121 -0
  233. package/skills/np-stitch-design-taste/SKILL.md +184 -0
  234. package/skills/np-web-design-guidelines/SKILL.md +39 -0
  235. package/workflows/add-todo.md +5 -0
  236. package/workflows/discuss-phase.md +2 -0
  237. package/workflows/execute-phase.md +27 -0
  238. package/workflows/note.md +5 -0
  239. package/workflows/plan-phase.md +12 -0
  240. package/workflows/stats.md +27 -90
  241. package/workflows/verify-work.md +12 -0
@@ -20,6 +20,7 @@ If the prompt contains a `<files_to_read>` block, you MUST use the `Read` tool t
20
20
  - Invoke commit-helper ONLY after verification passes.
21
21
  - Never invoke `git` directly — always through the `np-tools.cjs` wrapper so the D-25 gitignore-guard runs.
22
22
  - One task per spawn. One commit per task (D-03).
23
+ - If the spawn prompt contains a `Use the following Nubos skills:` line (injected by `/np:execute-phase` for UI/frontend tasks), load each named skill from `.claude/skills/<skill>/SKILL.md` BEFORE editing source. Apply each skill's quality bar; verification must pass against the skill's rules, not just the test command.
23
24
  </role>
24
25
 
25
26
  ## Inputs
package/bin/install.js CHANGED
@@ -58,6 +58,7 @@ const SOURCE_OPENCODE_DIR = path.join(__dirname, '..', 'templates', 'opencode',
58
58
  const OPENCODE_JSON_TEMPLATE = path.join(__dirname, '..', 'templates', 'opencode', 'opencode.json');
59
59
  const SOURCE_WORKFLOWS_DIR = path.join(__dirname, '..', 'workflows');
60
60
  const SOURCE_AGENTS_DIR = path.join(__dirname, '..', 'agents');
61
+ const SOURCE_SKILLS_DIR = path.join(__dirname, '..', 'skills');
61
62
 
62
63
  function _autoAskUser(spec) {
63
64
  return Promise.resolve({
@@ -382,6 +383,7 @@ async function _runInstallLocked(ctx) {
382
383
  projectRoot,
383
384
  workflowsDir: SOURCE_WORKFLOWS_DIR,
384
385
  agentsDir: SOURCE_AGENTS_DIR,
386
+ skillsDir: SOURCE_SKILLS_DIR,
385
387
  });
386
388
  const assetEntries = runtimeAssetsMod.manifestEntriesForPlans(assetPlans);
387
389
  for (const k of Object.keys(assetEntries)) {
@@ -1,77 +1,111 @@
1
1
  const COMMANDS = [
2
- { name: 'state', category: 'Utility', description: 'Print the current project state snapshot' },
3
- { name: 'help', category: 'Utility', description: 'List available commands' },
4
- { name: 'init', category: 'Utility', description: 'Dispatcher init payload for workflows' },
2
+ { name: 'state', category: 'Utility', description: 'Print the current project state snapshot', description_de: 'Gibt aktuellen Projekt-State-Snapshot aus' },
3
+ { name: 'help', category: 'Utility', description: 'List available commands', description_de: 'Listet verfügbare Commands auf' },
4
+ { name: 'init', category: 'Utility', description: 'Dispatcher init payload for workflows', description_de: 'Dispatcher-Init-Payload für Workflows' },
5
5
 
6
- { name: 'discuss-project', category: 'Planning', description: 'Adaptive project-context interview (writes PROJECT.md decisions)' },
7
- { name: 'discuss-phase', category: 'Planning', description: 'Adaptive milestone-context interview (writes M<NNN>-CONTEXT.md)' },
8
- { name: 'research-phase', category: 'Planning', description: 'Milestone-level research (WebFetch + MCP; offline fallback)' },
9
- { name: 'plan-milestone', category: 'Planning', description: 'Plan a milestone: scaffolds slices + tasks' },
10
- { name: 'new-project', category: 'Planning', description: 'Greenfield project init (PROJECT.md + REQUIREMENTS.md + M001 milestone)' },
11
- { name: 'new-milestone', category: 'Planning', description: 'Append a new milestone (M<NNN>) to an existing project' },
12
- { name: 'propose-milestones', category: 'Planning', description: 'Re-plan all not-yet-done milestones: AI proposes add/update/remove from PROJECT.md + REQUIREMENTS.md' },
13
- { name: 'agent-skills', category: 'Planning', description: 'Print agent_skills config for a given subagent' },
6
+ { name: 'discuss-project', category: 'Planning', description: 'Adaptive project-context interview (writes PROJECT.md decisions)', description_de: 'Adaptives Projekt-Kontext-Interview (schreibt PROJECT.md-Entscheidungen)' },
7
+ { name: 'discuss-phase', category: 'Planning', description: 'Adaptive milestone-context interview (writes M<NNN>-CONTEXT.md)', description_de: 'Adaptives Milestone-Kontext-Interview (schreibt M<NNN>-CONTEXT.md)' },
8
+ { name: 'research-phase', category: 'Planning', description: 'Milestone-level research (WebFetch + MCP; offline fallback)', description_de: 'Milestone-Recherche (WebFetch + MCP; Offline-Fallback)' },
9
+ { name: 'plan-milestone', category: 'Planning', description: 'Plan a milestone: scaffolds slices + tasks', description_de: 'Plant einen Milestone: erzeugt Slices + Tasks' },
10
+ { name: 'new-project', category: 'Planning', description: 'Greenfield project init (PROJECT.md + REQUIREMENTS.md + M001 milestone)', description_de: 'Greenfield-Projekt-Init (PROJECT.md + REQUIREMENTS.md + M001-Milestone)' },
11
+ { name: 'new-milestone', category: 'Planning', description: 'Append a new milestone (M<NNN>) to an existing project', description_de: 'Hängt einen neuen Milestone (M<NNN>) an ein bestehendes Projekt an' },
12
+ { name: 'propose-milestones', category: 'Planning', description: 'Re-plan all not-yet-done milestones: AI proposes add/update/remove from PROJECT.md + REQUIREMENTS.md', description_de: 'Plant offene Milestones neu: KI schlägt add/update/remove aus PROJECT.md + REQUIREMENTS.md vor' },
13
+ { name: 'agent-skills', category: 'Planning', description: 'Print agent_skills config for a given subagent', description_de: 'Gibt agent_skills-Konfiguration für einen Subagent aus' },
14
14
 
15
- { name: 'execute-milestone', category: 'Execution', description: 'Wave-based milestone execution — slice by slice, tasks parallel within a slice' },
16
- { name: 'commit-task', category: 'Execution', description: 'Atomic per-task git commit via lib/git.cjs' },
17
- { name: 'checkpoint', category: 'Execution', description: 'Per-task crash-safety checkpoint CRUD (start/transition/touch/show)' },
18
- { name: 'verify-work', category: 'Execution', description: 'Two-pass goal-backward verification (milestone-level VERIFICATION.md)' },
19
- { name: 'add-tests', category: 'Execution', description: 'Persist VERIFICATION Pass-cases as node:test UAT (Sentinel-preserving)' },
20
- { name: 'pause-work', category: 'Execution', description: 'Stamp STATE.session.stopped_at + resume_file for explicit handoff' },
21
- { name: 'resume-work', category: 'Execution', description: 'Classify session state (resume | orphan | clean) from STATE + checkpoints' },
15
+ { name: 'execute-milestone', category: 'Execution', description: 'Wave-based milestone execution — slice by slice, tasks parallel within a slice', description_de: 'Wave-basierte Milestone-Ausführung — Slice für Slice, Tasks parallel innerhalb einer Slice' },
16
+ { name: 'commit-task', category: 'Execution', description: 'Atomic per-task git commit via lib/git.cjs', description_de: 'Atomarer Per-Task-Git-Commit über lib/git.cjs' },
17
+ { name: 'checkpoint', category: 'Execution', description: 'Per-task crash-safety checkpoint CRUD (start/transition/touch/show)', description_de: 'Per-Task-Checkpoint-CRUD für Crash-Safety (start/transition/touch/show)' },
18
+ { name: 'verify-work', category: 'Execution', description: 'Two-pass goal-backward verification (milestone-level VERIFICATION.md)', description_de: 'Zweistufige Goal-Backward-Verifikation (Milestone-Ebene VERIFICATION.md)' },
19
+ { name: 'add-tests', category: 'Execution', description: 'Persist VERIFICATION Pass-cases as node:test UAT (Sentinel-preserving)', description_de: 'Persistiert VERIFICATION-Pass-Cases als node:test-UAT (Sentinel-erhaltend)' },
20
+ { name: 'pause-work', category: 'Execution', description: 'Stamp STATE.session.stopped_at + resume_file for explicit handoff', description_de: 'Setzt STATE.session.stopped_at + resume_file für expliziten Handoff' },
21
+ { name: 'resume-work', category: 'Execution', description: 'Classify session state (resume | orphan | clean) from STATE + checkpoints', description_de: 'Klassifiziert Session-Zustand (resume | orphan | clean) aus STATE + Checkpoints' },
22
22
 
23
- { name: 'skip', category: 'Execution', description: 'Mark task status skipped (lifecycle CRUD)' },
24
- { name: 'park', category: 'Execution', description: 'Mark task status parked (lifecycle CRUD)' },
25
- { name: 'unpark', category: 'Execution', description: 'Return a parked task to pending (lifecycle CRUD)' },
23
+ { name: 'skip', category: 'Execution', description: 'Mark task status skipped (lifecycle CRUD)', description_de: 'Markiert Task als skipped (Lifecycle-CRUD)' },
24
+ { name: 'park', category: 'Execution', description: 'Mark task status parked (lifecycle CRUD)', description_de: 'Markiert Task als parked (Lifecycle-CRUD)' },
25
+ { name: 'unpark', category: 'Execution', description: 'Return a parked task to pending (lifecycle CRUD)', description_de: 'Setzt parked Task zurück auf pending (Lifecycle-CRUD)' },
26
26
 
27
- { name: 'undo', category: 'Execution', description: 'Revert every task commit of a milestone or slice via git revert (no history rewrite)' },
28
- { name: 'undo-task', category: 'Execution', description: 'Revert a single task commit and reset task status to pending' },
29
- { name: 'reset-slice', category: 'Execution', description: 'Discard in-flight task: restore working tree from HEAD, drop checkpoint, clear STATE.current_task' },
27
+ { name: 'undo', category: 'Execution', description: 'Revert every task commit of a milestone or slice via git revert (no history rewrite)', description_de: 'Revertiert alle Task-Commits eines Milestones oder einer Slice via git revert (kein History-Rewrite)' },
28
+ { name: 'undo-task', category: 'Execution', description: 'Revert a single task commit and reset task status to pending', description_de: 'Revertiert einen einzelnen Task-Commit und setzt Task-Status auf pending zurück' },
29
+ { name: 'reset-slice', category: 'Execution', description: 'Discard in-flight task: restore working tree from HEAD, drop checkpoint, clear STATE.current_task', description_de: 'Verwirft laufenden Task: stellt Working-Tree von HEAD wieder her, löscht Checkpoint, leert STATE.current_task' },
30
30
 
31
- { name: 'doctor', category: 'Install', description: '5-check install-integrity scan (--fix for auto-safe fixes)' },
32
- { name: 'scan-codebase', category: 'Install', description: 'Initial deep codebase inventory → .nubos-pilot/codebase/ skill docs' },
33
- { name: 'update-docs', category: 'Install', description: 'Refresh stale module docs after code changes' },
31
+ { name: 'doctor', category: 'Install', description: '5-check install-integrity scan (--fix for auto-safe fixes)', description_de: '5-Check-Install-Integritäts-Scan (--fix für auto-sichere Fixes)' },
32
+ { name: 'scan-codebase', category: 'Install', description: 'Initial deep codebase inventory → .nubos-pilot/codebase/ skill docs', description_de: 'Initiale tiefe Codebase-Inventur → .nubos-pilot/codebase/ Skill-Docs' },
33
+ { name: 'update-docs', category: 'Install', description: 'Refresh stale module docs after code changes', description_de: 'Aktualisiert veraltete Modul-Docs nach Code-Änderungen' },
34
34
 
35
- { name: 'resolve-model', category: 'Utility', description: 'Resolve agent/tier to model alias or id (Tier×Profile matrix)' },
36
- { name: 'metrics', category: 'Utility', description: 'Record JSONL metrics entry (record | now | start-timestamp | end-timestamp)' },
35
+ { name: 'resolve-model', category: 'Utility', description: 'Resolve agent/tier to model alias or id (Tier×Profile matrix)', description_de: 'Löst Agent/Tier zu Model-Alias oder -ID auf (Tier×Profile-Matrix)' },
36
+ { name: 'metrics', category: 'Utility', description: 'Record JSONL metrics entry (record | now | start-timestamp | end-timestamp)', description_de: 'Schreibt JSONL-Metrics-Eintrag (record | now | start-timestamp | end-timestamp)' },
37
37
 
38
- { name: 'validate-phase', category: 'Review', description: 'Nyquist validation gap-fill via np-nyquist-auditor' },
38
+ { name: 'validate-phase', category: 'Review', description: 'Nyquist validation gap-fill via np-nyquist-auditor', description_de: 'Nyquist-Validierungs-Gap-Fill über np-nyquist-auditor' },
39
39
 
40
- { name: 'add-todo', category: 'Capture', description: 'Capture a pending todo to .nubos-pilot/todos/pending/ + increment STATE count' },
41
- { name: 'note', category: 'Capture', description: 'Capture a free-form note (project default, --global writes to ~/.nubos-pilot/notes/)' },
42
- { name: 'add-backlog', category: 'Capture', description: 'Append backlog item to ROADMAP.md' },
40
+ { name: 'add-todo', category: 'Capture', description: 'Capture a pending todo to .nubos-pilot/todos/pending/ + increment STATE count', description_de: 'Erfasst pending Todo nach .nubos-pilot/todos/pending/ + erhöht STATE-Counter' },
41
+ { name: 'note', category: 'Capture', description: 'Capture a free-form note (project default, --global writes to ~/.nubos-pilot/notes/)', description_de: 'Erfasst freiformige Notiz (Projekt-Default, --global schreibt nach ~/.nubos-pilot/notes/)' },
42
+ { name: 'add-backlog', category: 'Capture', description: 'Append backlog item to ROADMAP.md', description_de: 'Hängt Backlog-Eintrag an ROADMAP.md an' },
43
43
 
44
- { name: 'askuser', category: 'Utility', description: 'Capability-layer prompt wrapper (reads spec JSON, returns chosen label)' },
45
- { name: 'commit', category: 'Utility', description: 'Atomic git commit wrapper with gitignore-guard' },
46
- { name: 'config-get', category: 'Utility', description: 'Read value from .nubos-pilot/config.json by dotted key path' },
47
- { name: 'lang-directive', category: 'Utility', description: 'Print workflow language directive from config.response_language (SSOT)' },
48
- { name: 'text-mode', category: 'Utility', description: 'Print whether text mode is active (config.workflow.text_mode ∨ CLAUDECODE)' },
49
- { name: 'generate-slug', category: 'Utility', description: 'Slugify text via lib/layout.cjs.slugify' },
50
- { name: 'stats', category: 'Utility', description: 'Aggregated project stats (roadmap + STATE + git + metrics JSON shape)' },
51
- { name: 'detect-runtime', category: 'Utility', description: 'Print detected runtime id (claude, codex, gemini, …) — reads config.json ∨ env ∨ default' },
52
- { name: 'template-path', category: 'Utility', description: 'Print absolute path to a package-shipped template by name (e.g. VALIDATION, milestone/CONTEXT)' },
53
- { name: 'update-phase-meta', category: 'Planning', description: 'Update roadmap.yaml phase fields (name/goal/requirements/success_criteria) via JSON patch' },
54
- { name: 'phase-meta', category: 'Planning', description: 'Read roadmap.yaml phase fields as JSON (supports --field NAME and --length for arrays)' },
55
- { name: 'state-dir', category: 'Utility', description: 'Print project-state directory (.nubos-pilot) or a validated subdir via --subdir NAME' },
56
- { name: 'render-template', category: 'Utility', description: 'Render a shipped template by name with --vars JSON (or --vars-file PATH)' },
57
- { name: 'render-todo', category: 'Utility', description: 'Render slice TODO.md rollup (checkbox view of task statuses) for a slice full-id' },
58
- { name: 'handoff-write', category: 'Capture', description: 'Write an agent-to-agent handoff note (milestone-scoped by default, global without --milestone)' },
59
- { name: 'handoff-read', category: 'Capture', description: 'Read a single handoff by id (returns frontmatter + body as JSON)' },
60
- { name: 'handoff-list', category: 'Capture', description: 'List handoffs (JSON array); filter with --for AGENT, --milestone M<NNN>, --status STATUS, --global' },
61
- { name: 'handoff-status', category: 'Capture', description: 'Update a handoff status (open|read|acted|archived)' },
62
- { name: 'worktree-create', category: 'Execution', description: 'Create an isolated git worktree for a slice (branch np/<mid>-<sid> off current HEAD) under .nubos-pilot/worktrees/' },
63
- { name: 'worktree-remove', category: 'Execution', description: 'Remove a slice worktree + delete its branch (--force / --keep-branch)' },
64
- { name: 'worktree-list', category: 'Execution', description: 'List all nubos-pilot-managed slice worktrees (np/<mid>-<sid> only) as JSON' },
65
- { name: 'worktree-ff-merge', category: 'Execution', description: 'Fast-forward merge a slice branch back to its base (fails hard on non-FF)' },
66
- { name: 'dashboard', category: 'Utility', description: 'One-shot console dashboard of milestones, slices, and tasks. Read-only; flags: --json, --no-color' },
67
- { name: 'thread-resume', category: 'Utility', description: 'Bump a thread markdown on resume (status OPEN→IN_PROGRESS, refresh last_resumed) via atomic write' },
68
- { name: 'state-incr', category: 'Capture', description: 'Increment a whitelisted STATE.md counter (e.g. pending_todos) under withFileLock' },
44
+ { name: 'askuser', category: 'Utility', description: 'Capability-layer prompt wrapper (reads spec JSON, returns chosen label)', description_de: 'Capability-Layer-Prompt-Wrapper (liest Spec-JSON, gibt gewähltes Label zurück)' },
45
+ { name: 'commit', category: 'Utility', description: 'Atomic git commit wrapper with gitignore-guard', description_de: 'Atomarer Git-Commit-Wrapper mit Gitignore-Guard' },
46
+ { name: 'config-get', category: 'Utility', description: 'Read value from .nubos-pilot/config.json by dotted key path', description_de: 'Liest Wert aus .nubos-pilot/config.json über Dotted-Key-Pfad' },
47
+ { name: 'lang-directive', category: 'Utility', description: 'Print workflow language directive from config.response_language (SSOT)', description_de: 'Gibt Workflow-Sprachdirektive aus config.response_language aus (SSOT)' },
48
+ { name: 'text-mode', category: 'Utility', description: 'Print whether text mode is active (config.workflow.text_mode ∨ CLAUDECODE)', description_de: 'Gibt aus, ob Text-Mode aktiv ist (config.workflow.text_mode ∨ CLAUDECODE)' },
49
+ { name: 'generate-slug', category: 'Utility', description: 'Slugify text via lib/layout.cjs.slugify', description_de: 'Slugifiziert Text über lib/layout.cjs.slugify' },
50
+ { name: 'stats', category: 'Utility', description: 'Aggregated project stats — json | bar | markdown (markdown labels follow config.response_language)', description_de: 'Aggregierte Projekt-Stats json | bar | markdown (markdown-Labels folgen config.response_language)' },
51
+ { name: 'detect-runtime', category: 'Utility', description: 'Print detected runtime id (claude, codex, gemini, …) — reads config.json ∨ env ∨ default', description_de: 'Gibt erkannte Runtime-ID aus (claude, codex, gemini, …) — liest config.json ∨ env ∨ Default' },
52
+ { name: 'template-path', category: 'Utility', description: 'Print absolute path to a package-shipped template by name (e.g. VALIDATION, milestone/CONTEXT)', description_de: 'Gibt absoluten Pfad zu paketmitgeliefertem Template per Name aus (z.B. VALIDATION, milestone/CONTEXT)' },
53
+ { name: 'update-phase-meta', category: 'Planning', description: 'Update roadmap.yaml phase fields (name/goal/requirements/success_criteria) via JSON patch', description_de: 'Aktualisiert roadmap.yaml-Phase-Felder (name/goal/requirements/success_criteria) via JSON-Patch' },
54
+ { name: 'phase-meta', category: 'Planning', description: 'Read roadmap.yaml phase fields as JSON (supports --field NAME and --length for arrays)', description_de: 'Liest roadmap.yaml-Phase-Felder als JSON (unterstützt --field NAME und --length für Arrays)' },
55
+ { name: 'state-dir', category: 'Utility', description: 'Print project-state directory (.nubos-pilot) or a validated subdir via --subdir NAME', description_de: 'Gibt Projekt-State-Verzeichnis (.nubos-pilot) oder validiertes Subdir per --subdir NAME aus' },
56
+ { name: 'render-template', category: 'Utility', description: 'Render a shipped template by name with --vars JSON (or --vars-file PATH)', description_de: 'Rendert mitgeliefertes Template per Name mit --vars JSON (oder --vars-file PATH)' },
57
+ { name: 'render-todo', category: 'Utility', description: 'Render slice TODO.md rollup (checkbox view of task statuses) for a slice full-id', description_de: 'Rendert Slice-TODO.md-Rollup (Checkbox-Ansicht der Task-Status) für eine Slice-Full-ID' },
58
+ { name: 'handoff-write', category: 'Capture', description: 'Write an agent-to-agent handoff note (milestone-scoped by default, global without --milestone)', description_de: 'Schreibt Agent-zu-Agent-Handoff-Notiz (Milestone-scoped per Default, global ohne --milestone)' },
59
+ { name: 'handoff-read', category: 'Capture', description: 'Read a single handoff by id (returns frontmatter + body as JSON)', description_de: 'Liest einzelnen Handoff per ID (gibt Frontmatter + Body als JSON zurück)' },
60
+ { name: 'handoff-list', category: 'Capture', description: 'List handoffs (JSON array); filter with --for AGENT, --milestone M<NNN>, --status STATUS, --global', description_de: 'Listet Handoffs (JSON-Array); filtert mit --for AGENT, --milestone M<NNN>, --status STATUS, --global' },
61
+ { name: 'handoff-status', category: 'Capture', description: 'Update a handoff status (open|read|acted|archived)', description_de: 'Aktualisiert Handoff-Status (open|read|acted|archived)' },
62
+ { name: 'worktree-create', category: 'Execution', description: 'Create an isolated git worktree for a slice (branch np/<mid>-<sid> off current HEAD) under .nubos-pilot/worktrees/', description_de: 'Erstellt isoliertes Git-Worktree für eine Slice (Branch np/<mid>-<sid> vom aktuellen HEAD) unter .nubos-pilot/worktrees/' },
63
+ { name: 'worktree-remove', category: 'Execution', description: 'Remove a slice worktree + delete its branch (--force / --keep-branch)', description_de: 'Entfernt Slice-Worktree + löscht zugehörigen Branch (--force / --keep-branch)' },
64
+ { name: 'worktree-list', category: 'Execution', description: 'List all nubos-pilot-managed slice worktrees (np/<mid>-<sid> only) as JSON', description_de: 'Listet alle nubos-pilot-verwalteten Slice-Worktrees (nur np/<mid>-<sid>) als JSON' },
65
+ { name: 'worktree-ff-merge', category: 'Execution', description: 'Fast-forward merge a slice branch back to its base (fails hard on non-FF)', description_de: 'Fast-Forward-Merge eines Slice-Branches zurück auf Base (bricht hart ab bei non-FF)' },
66
+ { name: 'dashboard', category: 'Utility', description: 'One-shot console dashboard of milestones, slices, and tasks. Read-only; flags: --json, --no-color', description_de: 'Einmaliges Konsolen-Dashboard für Milestones, Slices und Tasks. Read-only; Flags: --json, --no-color' },
67
+ { name: 'thread-resume', category: 'Utility', description: 'Bump a thread markdown on resume (status OPEN→IN_PROGRESS, refresh last_resumed) via atomic write', description_de: 'Bumpt Thread-Markdown beim Resume (Status OPEN→IN_PROGRESS, aktualisiert last_resumed) via atomic write' },
68
+ { name: 'state-incr', category: 'Capture', description: 'Increment a whitelisted STATE.md counter (e.g. pending_todos) under withFileLock', description_de: 'Erhöht whitelisteten STATE.md-Counter (z.B. pending_todos) unter withFileLock' },
69
69
 
70
- { name: 'thread', category: 'Utility', description: 'Cross-session thread CRUD (create/resume under .nubos-pilot/threads/)' },
71
- { name: 'session-aggregate', category: 'Utility', description: 'Aggregate session metrics under withFileLock; reads pointer .last-session unless --since overrides' },
72
- { name: 'session-pointer-write', category: 'Utility', description: 'Atomic write of .nubos-pilot/reports/.last-session under withFileLock (ISO-8601 UTC)' },
73
- { name: 'workspace-scan', category: 'Install', description: 'Scan a workspace and emit inventory JSON (full result or --summary shape for /np:new-project)' },
74
- { name: 'cleanup', category: 'Utility', description: 'Archive completed milestones to .nubos-pilot/archive/v<X.Y>/' },
70
+ { name: 'thread', category: 'Utility', description: 'Cross-session thread CRUD (create/resume under .nubos-pilot/threads/)', description_de: 'Cross-Session-Thread-CRUD (create/resume unter .nubos-pilot/threads/)' },
71
+ { name: 'session-aggregate', category: 'Utility', description: 'Aggregate session metrics under withFileLock; reads pointer .last-session unless --since overrides', description_de: 'Aggregiert Session-Metriken unter withFileLock; liest Pointer .last-session, außer --since überschreibt' },
72
+ { name: 'session-pointer-write', category: 'Utility', description: 'Atomic write of .nubos-pilot/reports/.last-session under withFileLock (ISO-8601 UTC)', description_de: 'Atomares Schreiben von .nubos-pilot/reports/.last-session unter withFileLock (ISO-8601 UTC)' },
73
+ { name: 'workspace-scan', category: 'Install', description: 'Scan a workspace and emit inventory JSON (full result or --summary shape for /np:new-project)', description_de: 'Scannt einen Workspace und liefert Inventar-JSON (volles Ergebnis oder --summary-Shape für /np:new-project)' },
74
+ { name: 'cleanup', category: 'Utility', description: 'Archive completed milestones to .nubos-pilot/archive/v<X.Y>/', description_de: 'Archiviert abgeschlossene Milestones nach .nubos-pilot/archive/v<X.Y>/' },
75
75
  ];
76
76
 
77
- module.exports = { COMMANDS };
77
+ const CATEGORY_LABELS = Object.freeze({
78
+ en: {
79
+ Utility: 'Utility',
80
+ Planning: 'Planning',
81
+ Execution: 'Execution',
82
+ Install: 'Install',
83
+ Review: 'Review',
84
+ Capture: 'Capture',
85
+ },
86
+ de: {
87
+ Utility: 'Werkzeuge',
88
+ Planning: 'Planung',
89
+ Execution: 'Ausführung',
90
+ Install: 'Installation',
91
+ Review: 'Review',
92
+ Capture: 'Erfassung',
93
+ },
94
+ });
95
+
96
+ function categoryLabel(category, language) {
97
+ const lang = (language === 'de') ? 'de' : 'en';
98
+ const map = CATEGORY_LABELS[lang] || CATEGORY_LABELS.en;
99
+ return map[category] || category;
100
+ }
101
+
102
+ function localizedCommands(language) {
103
+ const useDe = language === 'de';
104
+ return COMMANDS.map((c) => ({
105
+ name: c.name,
106
+ category: c.category,
107
+ description: useDe && c.description_de ? c.description_de : c.description,
108
+ }));
109
+ }
110
+
111
+ module.exports = { COMMANDS, CATEGORY_LABELS, categoryLabel, localizedCommands };
@@ -1,12 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  const { collectSnapshot, renderSnapshot } = require('../../lib/dashboard.cjs');
4
+ const { resolveLanguage, normalizeLanguage } = require('../../lib/language.cjs');
4
5
 
5
6
  function _parseArgs(args) {
6
- const out = { json: false, noColor: false };
7
- for (const a of args) {
8
- if (a === '--json') out.json = true;
7
+ const out = { json: false, noColor: false, lang: null };
8
+ const list = Array.isArray(args) ? args : [];
9
+ for (let i = 0; i < list.length; i++) {
10
+ const a = list[i];
11
+ if (a === '--json') out.json = true;
9
12
  else if (a === '--no-color') out.noColor = true;
13
+ else if (a === '--lang') out.lang = list[++i] || null;
14
+ else if (a.startsWith('--lang=')) out.lang = a.slice('--lang='.length);
10
15
  }
11
16
  return out;
12
17
  }
@@ -15,7 +20,7 @@ function run(args, opts) {
15
20
  const o = opts || {};
16
21
  const cwd = o.cwd || process.cwd();
17
22
  const stdout = o.stdout || process.stdout;
18
- const parsed = _parseArgs(Array.isArray(args) ? args : []);
23
+ const parsed = _parseArgs(args);
19
24
 
20
25
  const snap = collectSnapshot(cwd);
21
26
  if (parsed.json) {
@@ -23,7 +28,8 @@ function run(args, opts) {
23
28
  return 0;
24
29
  }
25
30
  const useColor = !parsed.noColor && Boolean(stdout.isTTY);
26
- stdout.write(renderSnapshot(snap, { color: useColor }) + '\n');
31
+ const language = parsed.lang ? normalizeLanguage(parsed.lang) : resolveLanguage(cwd);
32
+ stdout.write(renderSnapshot(snap, { color: useColor, language }) + '\n');
27
33
  return 0;
28
34
  }
29
35
 
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ const { test, after } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+
9
+ const subcmd = require('./dashboard.cjs');
10
+
11
+ const _roots = [];
12
+
13
+ function _sandbox(configLanguage) {
14
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-dashboard-cli-'));
15
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
16
+ if (configLanguage !== undefined) {
17
+ fs.writeFileSync(
18
+ path.join(root, '.nubos-pilot', 'config.json'),
19
+ JSON.stringify({ response_language: configLanguage }),
20
+ 'utf-8',
21
+ );
22
+ }
23
+ _roots.push(root);
24
+ return root;
25
+ }
26
+
27
+ function _capture() {
28
+ let buf = '';
29
+ return { stub: { write: (s) => { buf += s; }, isTTY: false }, get: () => buf };
30
+ }
31
+
32
+ after(() => {
33
+ while (_roots.length) {
34
+ const r = _roots.pop();
35
+ try { fs.rmSync(r, { recursive: true, force: true }); } catch {}
36
+ }
37
+ });
38
+
39
+ test('CLI-DB-1: parses --lang flag (space form)', () => {
40
+ const parsed = subcmd._parseArgs(['--lang', 'de']);
41
+ assert.equal(parsed.lang, 'de');
42
+ });
43
+
44
+ test('CLI-DB-2: parses --lang=xx flag (equals form)', () => {
45
+ const parsed = subcmd._parseArgs(['--lang=de']);
46
+ assert.equal(parsed.lang, 'de');
47
+ });
48
+
49
+ test('CLI-DB-3: dashboard reads response_language from config.json', () => {
50
+ const root = _sandbox('de');
51
+ const cap = _capture();
52
+ const code = subcmd.run([], { cwd: root, stdout: cap.stub });
53
+ assert.equal(code, 0);
54
+ assert.match(cap.get(), /Noch keine Milestones/);
55
+ });
56
+
57
+ test('CLI-DB-4: --lang overrides config language', () => {
58
+ const root = _sandbox('de');
59
+ const cap = _capture();
60
+ const code = subcmd.run(['--lang', 'en'], { cwd: root, stdout: cap.stub });
61
+ assert.equal(code, 0);
62
+ assert.match(cap.get(), /No milestones yet/);
63
+ });
64
+
65
+ test('CLI-DB-5: missing config falls back to English', () => {
66
+ const root = _sandbox();
67
+ const cap = _capture();
68
+ const code = subcmd.run([], { cwd: root, stdout: cap.stub });
69
+ assert.equal(code, 0);
70
+ assert.match(cap.get(), /No milestones yet/);
71
+ });
72
+
73
+ test('CLI-DB-6: --json snapshot stays language-neutral', () => {
74
+ const root = _sandbox('de');
75
+ const cap = _capture();
76
+ const code = subcmd.run(['--json'], { cwd: root, stdout: cap.stub });
77
+ assert.equal(code, 0);
78
+ const parsed = JSON.parse(cap.get());
79
+ assert.deepEqual(Object.keys(parsed), ['milestones']);
80
+ });
@@ -1,6 +1,7 @@
1
- const { COMMANDS } = require('./_commands.cjs');
1
+ const { COMMANDS, localizedCommands, categoryLabel } = require('./_commands.cjs');
2
+ const { resolveLanguage } = require('../../lib/language.cjs');
2
3
 
3
- function _renderText(commands) {
4
+ function _renderText(commands, language) {
4
5
  const byCat = new Map();
5
6
  for (const c of commands) {
6
7
  if (!byCat.has(c.category)) byCat.set(c.category, []);
@@ -8,7 +9,7 @@ function _renderText(commands) {
8
9
  }
9
10
  const lines = [];
10
11
  for (const [cat, items] of byCat) {
11
- lines.push(cat);
12
+ lines.push(categoryLabel(cat, language));
12
13
  for (const c of items) {
13
14
  lines.push(' ' + c.name.padEnd(10) + c.description);
14
15
  }
@@ -17,12 +18,20 @@ function _renderText(commands) {
17
18
  return lines.join('\n').replace(/\n+$/, '\n');
18
19
  }
19
20
 
20
- function run(args) {
21
+ function _resolveLangForCwd(cwd) {
22
+ try { return resolveLanguage(cwd || process.cwd()); }
23
+ catch { return 'en'; }
24
+ }
25
+
26
+ function run(args, ctx) {
21
27
  const list = Array.isArray(args) ? args : [];
28
+ const cwd = (ctx && ctx.cwd) || process.cwd();
29
+ const language = _resolveLangForCwd(cwd);
30
+ const cmds = localizedCommands(language);
22
31
  if (list.includes('--json')) {
23
- return { commands: COMMANDS.slice() };
32
+ return { commands: cmds };
24
33
  }
25
- return { text: _renderText(COMMANDS) };
34
+ return { text: _renderText(cmds, language) };
26
35
  }
27
36
 
28
- module.exports = { run };
37
+ module.exports = { run, _renderText };
@@ -1,8 +1,24 @@
1
1
  const test = require('node:test');
2
2
  const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
3
6
 
4
7
  const helpCmd = require('./help.cjs');
5
8
 
9
+ function _sandbox(language) {
10
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-help-'));
11
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
12
+ if (language !== undefined) {
13
+ fs.writeFileSync(
14
+ path.join(root, '.nubos-pilot', 'config.json'),
15
+ JSON.stringify({ response_language: language }),
16
+ 'utf-8',
17
+ );
18
+ }
19
+ return root;
20
+ }
21
+
6
22
  test('HELP-CMD-1: run([]) returns rendered text grouped by category with base command names', () => {
7
23
  const out = helpCmd.run([]);
8
24
  assert.ok(out && typeof out.text === 'string');
@@ -25,3 +41,50 @@ test('HELP-CMD-2: run([--json]) returns { commands: [...] } with all registered
25
41
  assert.ok(typeof c.description === 'string' && c.description.length > 0);
26
42
  }
27
43
  });
44
+
45
+ test('HELP-L1: text render uses German category labels when config.response_language=de', () => {
46
+ const root = _sandbox('de');
47
+ try {
48
+ const out = helpCmd.run([], { cwd: root });
49
+ assert.match(out.text, /^Werkzeuge\b/m);
50
+ assert.match(out.text, /^Planung\b/m);
51
+ assert.match(out.text, /^Ausführung\b/m);
52
+ assert.equal(/^Utility\b/m.test(out.text), false, 'must not show English category in de mode');
53
+ } finally {
54
+ fs.rmSync(root, { recursive: true, force: true });
55
+ }
56
+ });
57
+
58
+ test('HELP-L2: text render uses German command descriptions when config language=de', () => {
59
+ const root = _sandbox('de');
60
+ try {
61
+ const out = helpCmd.run([], { cwd: root });
62
+ assert.match(out.text, /Listet verfügbare Commands auf/);
63
+ assert.match(out.text, /Gibt aktuellen Projekt-State-Snapshot aus/);
64
+ } finally {
65
+ fs.rmSync(root, { recursive: true, force: true });
66
+ }
67
+ });
68
+
69
+ test('HELP-L3: --json descriptions are localized but category stays canonical English key', () => {
70
+ const root = _sandbox('de');
71
+ try {
72
+ const out = helpCmd.run(['--json'], { cwd: root });
73
+ const help = out.commands.find((c) => c.name === 'help');
74
+ assert.equal(help.description, 'Listet verfügbare Commands auf');
75
+ assert.equal(help.category, 'Utility', 'category stays English so consumers can switch on it');
76
+ } finally {
77
+ fs.rmSync(root, { recursive: true, force: true });
78
+ }
79
+ });
80
+
81
+ test('HELP-L4: missing config falls back to English', () => {
82
+ const root = _sandbox();
83
+ try {
84
+ const out = helpCmd.run([], { cwd: root });
85
+ assert.match(out.text, /^Utility\b/m);
86
+ assert.match(out.text, /List available commands/);
87
+ } finally {
88
+ fs.rmSync(root, { recursive: true, force: true });
89
+ }
90
+ });
@@ -6,12 +6,50 @@ const { parseRoadmap } = require('../../lib/roadmap.cjs');
6
6
  const { readState } = require('../../lib/state.cjs');
7
7
  const { aggregatePhase } = require('../../lib/metrics-aggregate.cjs');
8
8
  const { extractFrontmatter } = require('../../lib/frontmatter.cjs');
9
+ const { resolveLanguage } = require('../../lib/language.cjs');
9
10
  const layout = require('../../lib/layout.cjs');
10
11
 
11
12
  const SCHEMA_VERSION = 2;
12
13
 
14
+ const MD_LABELS = Object.freeze({
15
+ en: {
16
+ title: '## Project Stats',
17
+ milestone: '**Milestone:**',
18
+ progress: '**Progress:**',
19
+ plans: 'plans',
20
+ last_activity: '**Last activity:**',
21
+ commits: '**Commits:**',
22
+ started: '**Project started:**',
23
+ phases_h: '### Phases',
24
+ metrics_h: '### Metrics by Phase',
25
+ cols_phases: '| Phase | Name | Plans | Completed | Status | % |',
26
+ sep_phases: '|-------|------|-------|-----------|--------|---|',
27
+ cols_metrics: '| Phase | Records | Tokens In | Tokens Out | Avg Opus ms | Avg Sonnet ms | Avg Haiku ms | Errors |',
28
+ sep_metrics: '|-------|---------|-----------|------------|-------------|---------------|--------------|--------|',
29
+ },
30
+ de: {
31
+ title: '## Projekt-Stats',
32
+ milestone: '**Milestone:**',
33
+ progress: '**Fortschritt:**',
34
+ plans: 'Pläne',
35
+ last_activity: '**Letzte Aktivität:**',
36
+ commits: '**Commits:**',
37
+ started: '**Projekt-Start:**',
38
+ phases_h: '### Phasen',
39
+ metrics_h: '### Metriken pro Phase',
40
+ cols_phases: '| Phase | Name | Pläne | Fertig | Status | % |',
41
+ sep_phases: '|-------|------|-------|--------|--------|---|',
42
+ cols_metrics: '| Phase | Records | Tokens In | Tokens Out | Ø Opus ms | Ø Sonnet ms | Ø Haiku ms | Fehler |',
43
+ sep_metrics: '|-------|---------|-----------|------------|-----------|-------------|------------|--------|',
44
+ },
45
+ });
46
+
47
+ function _mdLabelsFor(language) {
48
+ return MD_LABELS[language === 'de' ? 'de' : 'en'];
49
+ }
50
+
13
51
  function _usage() {
14
- return 'Usage:\n np-tools.cjs stats json\n np-tools.cjs stats bar';
52
+ return 'Usage:\n np-tools.cjs stats json\n np-tools.cjs stats bar\n np-tools.cjs stats markdown';
15
53
  }
16
54
 
17
55
  function _percent(num, den) {
@@ -181,6 +219,55 @@ async function _buildStats(cwd) {
181
219
  };
182
220
  }
183
221
 
222
+ function _fmt(v) {
223
+ if (v === null || v === undefined) return '—';
224
+ if (typeof v === 'number') return v.toLocaleString();
225
+ return String(v);
226
+ }
227
+
228
+ function _renderMarkdown(stats, language) {
229
+ const L = _mdLabelsFor(language);
230
+ const filled = Math.round((stats.percent || 0) / 5);
231
+ const bar = '█'.repeat(filled) + '░'.repeat(20 - filled);
232
+ const lines = [];
233
+ lines.push(L.title);
234
+ lines.push('');
235
+ lines.push(L.milestone + ' ' + _fmt(stats.milestone && stats.milestone.version) + ' — ' + _fmt(stats.milestone && stats.milestone.name));
236
+ lines.push(L.progress + ' [' + bar + '] ' + (stats.percent || 0) + '% (' + stats.plans_complete + '/' + stats.plans_total + ' ' + L.plans + ')');
237
+ lines.push(L.last_activity + ' ' + _fmt(stats.last_activity));
238
+ lines.push(L.commits + ' ' + _fmt(stats.git && stats.git.commits));
239
+ lines.push(L.started + ' ' + _fmt(stats.git && stats.git.first_commit_at));
240
+ lines.push('');
241
+ lines.push(L.phases_h);
242
+ lines.push('');
243
+ lines.push(L.cols_phases);
244
+ lines.push(L.sep_phases);
245
+ for (const ph of (stats.phases || [])) {
246
+ const pct = ph.plans_total > 0 ? Math.round(ph.plans_complete / ph.plans_total * 100) : 0;
247
+ lines.push('| ' + ph.number + ' | ' + ph.name + ' | ' + ph.plans_total + ' | ' + ph.plans_complete + ' | ' + ph.status + ' | ' + pct + '% |');
248
+ }
249
+ lines.push('');
250
+ lines.push(L.metrics_h);
251
+ lines.push('');
252
+ lines.push(L.cols_metrics);
253
+ lines.push(L.sep_metrics);
254
+ for (const ph of (stats.phases || [])) {
255
+ const m = (stats.metrics_by_phase || {})[ph.number];
256
+ if (!m || m.record_count === 0) {
257
+ lines.push('| ' + ph.number + ' | — | — | — | — | — | — | — |');
258
+ continue;
259
+ }
260
+ const t = m.avg_duration_ms_by_tier || {};
261
+ lines.push('| ' + ph.number + ' | ' + m.record_count + ' | ' + _fmt(m.total_tokens_in) + ' | ' + _fmt(m.total_tokens_out) + ' | ' + _fmt(t.opus) + ' | ' + _fmt(t.sonnet) + ' | ' + _fmt(t.haiku) + ' | ' + m.error_count + ' |');
262
+ }
263
+ return lines.join('\n') + '\n';
264
+ }
265
+
266
+ function _resolveLangForCwd(cwd) {
267
+ try { return resolveLanguage(cwd || process.cwd()); }
268
+ catch { return 'en'; }
269
+ }
270
+
184
271
  async function run(argv, ctx) {
185
272
  const context = ctx || {};
186
273
  const cwd = context.cwd || process.cwd();
@@ -188,7 +275,7 @@ async function run(argv, ctx) {
188
275
  const stderr = context.stderr || process.stderr;
189
276
  const args = Array.isArray(argv) ? argv.slice() : [];
190
277
  const sub = args.shift();
191
- if (sub !== 'json' && sub !== 'bar') {
278
+ if (sub !== 'json' && sub !== 'bar' && sub !== 'markdown') {
192
279
  stderr.write(_usage() + '\n');
193
280
  return 1;
194
281
  }
@@ -205,6 +292,15 @@ async function run(argv, ctx) {
205
292
  stdout.write(_renderBar('Slices', out.slices.percent) + ' (' + out.slices.complete + '/' + out.slices.total + ')\n');
206
293
  return 0;
207
294
  }
295
+ if (sub === 'markdown') {
296
+ let lang = null;
297
+ const langIdx = args.indexOf('--lang');
298
+ if (langIdx >= 0 && args[langIdx + 1]) lang = args[langIdx + 1];
299
+ else for (const a of args) if (a.startsWith('--lang=')) lang = a.slice('--lang='.length);
300
+ const language = lang || _resolveLangForCwd(cwd);
301
+ stdout.write(_renderMarkdown(out, language));
302
+ return 0;
303
+ }
208
304
  stdout.write(JSON.stringify(out, null, 2) + '\n');
209
305
  return 0;
210
306
  } catch (err) {
@@ -213,7 +309,7 @@ async function run(argv, ctx) {
213
309
  }
214
310
  }
215
311
 
216
- module.exports = { run, _buildStats, _collectPhases, _milestoneEntry, _collectTaskAndSliceStats, _renderBar };
312
+ module.exports = { run, _buildStats, _collectPhases, _milestoneEntry, _collectTaskAndSliceStats, _renderBar, _renderMarkdown, MD_LABELS };
217
313
 
218
314
  if (require.main === module) {
219
315
  run(process.argv.slice(2)).then((code) => process.exit(code)).catch((err) => {