@soederpop/luca 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { ContainerContext } from '@soederpop/luca'
|
|
3
|
+
import { CommandOptionsSchema } from '@soederpop/luca/schemas'
|
|
4
|
+
import * as readline from 'readline'
|
|
5
|
+
import * as fs from 'fs'
|
|
6
|
+
import { spawn } from 'child_process'
|
|
7
|
+
import * as path from 'path'
|
|
8
|
+
|
|
9
|
+
export const argsSchema = CommandOptionsSchema.extend({
|
|
10
|
+
reset: z.boolean().default(false).describe('Reset all audit progress'),
|
|
11
|
+
module: z.string().optional().describe('Jump directly to a specific module by shortcut'),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
type ItemStatus = 'good' | 'needs_work' | 'skipped' | 'pending'
|
|
17
|
+
|
|
18
|
+
type ModuleProgress = {
|
|
19
|
+
status: 'pending' | 'in_progress' | 'completed'
|
|
20
|
+
items: Record<string, ItemStatus>
|
|
21
|
+
lastItem?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type AuditProgress = {
|
|
25
|
+
modules: Record<string, ModuleProgress>
|
|
26
|
+
lastModule?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type AuditItem = {
|
|
30
|
+
id: string
|
|
31
|
+
kind: 'class_jsdoc' | 'static_description' | 'method_jsdoc' | 'method_param' | 'getter_jsdoc' | 'option_describe' | 'state_describe' | 'event_describe'
|
|
32
|
+
label: string
|
|
33
|
+
currentValue: string
|
|
34
|
+
line: number
|
|
35
|
+
filePath: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type ModuleInfo = {
|
|
39
|
+
shortcut: string
|
|
40
|
+
registryName: string
|
|
41
|
+
filePath: string
|
|
42
|
+
mtime: number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const COLORS = {
|
|
48
|
+
reset: '\x1b[0m',
|
|
49
|
+
bold: '\x1b[1m',
|
|
50
|
+
dim: '\x1b[2m',
|
|
51
|
+
red: '\x1b[31m',
|
|
52
|
+
green: '\x1b[32m',
|
|
53
|
+
yellow: '\x1b[33m',
|
|
54
|
+
blue: '\x1b[34m',
|
|
55
|
+
magenta: '\x1b[35m',
|
|
56
|
+
cyan: '\x1b[36m',
|
|
57
|
+
white: '\x1b[37m',
|
|
58
|
+
bgRed: '\x1b[41m',
|
|
59
|
+
bgGreen: '\x1b[42m',
|
|
60
|
+
bgYellow: '\x1b[43m',
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function c(color: keyof typeof COLORS, text: string): string {
|
|
64
|
+
return `${COLORS[color]}${text}${COLORS.reset}`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function statusIcon(status: ItemStatus): string {
|
|
68
|
+
switch (status) {
|
|
69
|
+
case 'good': return c('green', '✓')
|
|
70
|
+
case 'needs_work': return c('yellow', '~')
|
|
71
|
+
case 'skipped': return c('dim', '–')
|
|
72
|
+
case 'pending': return c('dim', '○')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function moduleStatusIcon(status: ModuleProgress['status']): string {
|
|
77
|
+
switch (status) {
|
|
78
|
+
case 'completed': return c('green', '✓')
|
|
79
|
+
case 'in_progress': return c('yellow', '◐')
|
|
80
|
+
case 'pending': return c('dim', '○')
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function ask(rl: readline.Interface, question: string): Promise<string> {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
rl.question(question, (answer) => resolve(answer.trim().toLowerCase()))
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function clearScreen() {
|
|
91
|
+
process.stdout.write('\x1b[2J\x1b[H')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Source file discovery ────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
function discoverModules(container: any, fm: any): ModuleInfo[] {
|
|
97
|
+
const registries = [
|
|
98
|
+
{ name: 'features', paths: ['src/node/features/*.ts', 'src/web/features/*.ts'] },
|
|
99
|
+
{ name: 'clients', paths: ['src/node/clients/*.ts', 'src/web/clients/*.ts', 'src/clients/*.ts'] },
|
|
100
|
+
{ name: 'servers', paths: ['src/node/servers/*.ts', 'src/servers/*.ts'] },
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
const modules: ModuleInfo[] = []
|
|
104
|
+
const seen = new Set<string>()
|
|
105
|
+
|
|
106
|
+
for (const reg of registries) {
|
|
107
|
+
const available: string[] = container[reg.name]?.available ?? []
|
|
108
|
+
|
|
109
|
+
for (const pattern of reg.paths) {
|
|
110
|
+
const files = fm.match(pattern) as string[]
|
|
111
|
+
for (const relPath of files) {
|
|
112
|
+
const absPath = path.resolve(container.cwd, relPath)
|
|
113
|
+
let content: string
|
|
114
|
+
try {
|
|
115
|
+
content = fs.readFileSync(absPath, 'utf-8')
|
|
116
|
+
} catch {
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Find which shortcut this file registers via the register() call
|
|
121
|
+
// Pattern: .register('shortcut', Class) or .register("shortcut", Class)
|
|
122
|
+
for (const shortcut of available) {
|
|
123
|
+
const fullShortcut = `${reg.name}.${shortcut}`
|
|
124
|
+
if (seen.has(fullShortcut)) continue
|
|
125
|
+
|
|
126
|
+
// Look specifically for the register call or the static shortcut declaration
|
|
127
|
+
const registerPattern = new RegExp(
|
|
128
|
+
`\\.register\\(\\s*['"\`]${shortcut}['"\`]` +
|
|
129
|
+
`|shortcut\\s*=\\s*['"\`]${fullShortcut}['"\`]`
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if (registerPattern.test(content)) {
|
|
133
|
+
let mtime = 0
|
|
134
|
+
try {
|
|
135
|
+
mtime = fs.statSync(absPath).mtimeMs
|
|
136
|
+
} catch {}
|
|
137
|
+
|
|
138
|
+
modules.push({
|
|
139
|
+
shortcut,
|
|
140
|
+
registryName: reg.name,
|
|
141
|
+
filePath: absPath,
|
|
142
|
+
mtime,
|
|
143
|
+
})
|
|
144
|
+
seen.add(fullShortcut)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Sort by mtime descending (most recently edited first)
|
|
152
|
+
modules.sort((a, b) => b.mtime - a.mtime)
|
|
153
|
+
return modules
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Source file parsing for audit items ───────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
function findClassEnd(lines: string[], classStartLine: number): number {
|
|
159
|
+
// Find matching closing brace for the class. classStartLine is 0-indexed.
|
|
160
|
+
let braceDepth = 0
|
|
161
|
+
let foundOpen = false
|
|
162
|
+
for (let i = classStartLine; i < lines.length; i++) {
|
|
163
|
+
for (const ch of lines[i]!) {
|
|
164
|
+
if (ch === '{') { braceDepth++; foundOpen = true }
|
|
165
|
+
if (ch === '}') braceDepth--
|
|
166
|
+
if (foundOpen && braceDepth === 0) return i
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return lines.length - 1
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function extractAuditItems(filePath: string, shortcut: string, registryName: string): AuditItem[] {
|
|
173
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
174
|
+
const lines = content.split('\n')
|
|
175
|
+
const items: AuditItem[] = []
|
|
176
|
+
const prefix = `${registryName}:${shortcut}`
|
|
177
|
+
const fullShortcut = `${registryName}.${shortcut}`
|
|
178
|
+
|
|
179
|
+
// Find the class that declares this shortcut
|
|
180
|
+
let classLine = -1 // 0-indexed
|
|
181
|
+
let className = ''
|
|
182
|
+
for (let i = 0; i < lines.length; i++) {
|
|
183
|
+
const match = lines[i]!.match(/^export\s+class\s+(\w+)\s+extends\s+\w*(?:Feature|Client|Server|Helper)/)
|
|
184
|
+
if (match) {
|
|
185
|
+
// Check if this class declares the shortcut we're looking for
|
|
186
|
+
// by scanning its static shortcut property within the next ~15 lines
|
|
187
|
+
for (let j = i + 1; j < Math.min(i + 15, lines.length); j++) {
|
|
188
|
+
if (
|
|
189
|
+
lines[j]!.includes(`"${shortcut}"`) ||
|
|
190
|
+
lines[j]!.includes(`'${shortcut}'`) ||
|
|
191
|
+
lines[j]!.includes(`"${fullShortcut}"`) ||
|
|
192
|
+
lines[j]!.includes(`'${fullShortcut}'`)
|
|
193
|
+
) {
|
|
194
|
+
classLine = i
|
|
195
|
+
className = match[1]!
|
|
196
|
+
break
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (classLine >= 0) break
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (classLine === -1) return items
|
|
204
|
+
|
|
205
|
+
const classEnd = findClassEnd(lines, classLine)
|
|
206
|
+
|
|
207
|
+
// 1. Class JSDoc
|
|
208
|
+
const classJsdoc = findJSDocAbove(lines, classLine)
|
|
209
|
+
items.push({
|
|
210
|
+
id: `${prefix}:class_jsdoc`,
|
|
211
|
+
kind: 'class_jsdoc',
|
|
212
|
+
label: `${className} class JSDoc`,
|
|
213
|
+
currentValue: classJsdoc.text,
|
|
214
|
+
line: classJsdoc.line > 0 ? classJsdoc.line : classLine + 1,
|
|
215
|
+
filePath,
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// 2. Static description
|
|
219
|
+
for (let i = classLine; i <= classEnd; i++) {
|
|
220
|
+
const descMatch = lines[i]!.match(/static\s+(?:override\s+)?description\s*[=:]\s*["'`](.*)["'`]/)
|
|
221
|
+
if (descMatch) {
|
|
222
|
+
items.push({
|
|
223
|
+
id: `${prefix}:static_description`,
|
|
224
|
+
kind: 'static_description',
|
|
225
|
+
label: `static description`,
|
|
226
|
+
currentValue: descMatch[1] || '',
|
|
227
|
+
line: i + 1,
|
|
228
|
+
filePath,
|
|
229
|
+
})
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 3. Options schema .describe() calls
|
|
235
|
+
findSchemaDescribes(lines, filePath, prefix, 'option', 'Options').forEach(item => items.push(item))
|
|
236
|
+
|
|
237
|
+
// 4. State schema .describe() calls
|
|
238
|
+
findSchemaDescribes(lines, filePath, prefix, 'state', 'State').forEach(item => items.push(item))
|
|
239
|
+
|
|
240
|
+
// 5. Events schema .describe() calls
|
|
241
|
+
findSchemaDescribes(lines, filePath, prefix, 'event', 'Events').forEach(item => items.push(item))
|
|
242
|
+
|
|
243
|
+
// 6. Getters within class bounds
|
|
244
|
+
for (let i = classLine + 1; i <= classEnd; i++) {
|
|
245
|
+
const getterMatch = lines[i]!.match(/^\s+(?:override\s+)?get\s+(\w+)\s*\(/)
|
|
246
|
+
if (getterMatch) {
|
|
247
|
+
const name = getterMatch[1]!
|
|
248
|
+
if (['initialState', 'container', 'options', 'context', 'cacheKey', 'isEnabled', 'shortcut'].includes(name)) continue
|
|
249
|
+
const jsdoc = findJSDocAbove(lines, i)
|
|
250
|
+
items.push({
|
|
251
|
+
id: `${prefix}:getter:${name}`,
|
|
252
|
+
kind: 'getter_jsdoc',
|
|
253
|
+
label: `get ${name}()`,
|
|
254
|
+
currentValue: jsdoc.text,
|
|
255
|
+
line: jsdoc.line > 0 ? jsdoc.line : i + 1,
|
|
256
|
+
filePath,
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 7. Methods within class bounds
|
|
262
|
+
for (let i = classLine + 1; i <= classEnd; i++) {
|
|
263
|
+
const line = lines[i]!
|
|
264
|
+
// Match method declarations (with or without async)
|
|
265
|
+
const methodMatch = line.match(/^\s+(?:async\s+)?(\w+)\s*\(/)
|
|
266
|
+
if (methodMatch) {
|
|
267
|
+
const name = methodMatch[1]!
|
|
268
|
+
if (name === 'constructor' || name.startsWith('_') || name === 'afterInitialize' || name === 'enable') continue
|
|
269
|
+
if (line.includes('private ') || line.includes('static ')) continue
|
|
270
|
+
if (line.match(/^\s+(?:override\s+)?(?:get|set)\s+/)) continue
|
|
271
|
+
|
|
272
|
+
const jsdoc = findJSDocAbove(lines, i)
|
|
273
|
+
items.push({
|
|
274
|
+
id: `${prefix}:method:${name}`,
|
|
275
|
+
kind: 'method_jsdoc',
|
|
276
|
+
label: `${name}()`,
|
|
277
|
+
currentValue: jsdoc.text,
|
|
278
|
+
line: jsdoc.line > 0 ? jsdoc.line : i + 1,
|
|
279
|
+
filePath,
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
// Extract method params and their docs
|
|
283
|
+
const paramNames = extractParamNames(lines, i)
|
|
284
|
+
for (const pName of paramNames) {
|
|
285
|
+
const paramDesc = jsdoc.text ? extractParamDescription(jsdoc.text, pName) : ''
|
|
286
|
+
items.push({
|
|
287
|
+
id: `${prefix}:method:${name}:param:${pName}`,
|
|
288
|
+
kind: 'method_param',
|
|
289
|
+
label: ` @param ${pName}`,
|
|
290
|
+
currentValue: paramDesc,
|
|
291
|
+
line: jsdoc.line > 0 ? jsdoc.line : i + 1,
|
|
292
|
+
filePath,
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return items
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function findJSDocAbove(lines: string[], lineIdx: number): { text: string; line: number } {
|
|
302
|
+
// Walk backwards from lineIdx looking for a JSDoc block ending with */
|
|
303
|
+
let i = lineIdx - 1
|
|
304
|
+
// Skip blank lines
|
|
305
|
+
while (i >= 0 && lines[i]!.trim() === '') i--
|
|
306
|
+
|
|
307
|
+
if (i < 0 || !lines[i]!.trim().endsWith('*/')) {
|
|
308
|
+
return { text: '', line: 0 }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Found end of JSDoc, walk backwards to find /**
|
|
312
|
+
const endLine = i
|
|
313
|
+
while (i >= 0 && !lines[i]!.includes('/**')) i--
|
|
314
|
+
|
|
315
|
+
if (i < 0) return { text: '', line: 0 }
|
|
316
|
+
|
|
317
|
+
const startLine = i
|
|
318
|
+
const jsdocLines = lines.slice(startLine, endLine + 1)
|
|
319
|
+
const text = jsdocLines.join('\n')
|
|
320
|
+
return { text, line: startLine + 1 } // 1-indexed
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function findSchemaDescribes(
|
|
324
|
+
lines: string[],
|
|
325
|
+
filePath: string,
|
|
326
|
+
prefix: string,
|
|
327
|
+
kind: 'option' | 'state' | 'event',
|
|
328
|
+
schemaKeyword: string,
|
|
329
|
+
): AuditItem[] {
|
|
330
|
+
const items: AuditItem[] = []
|
|
331
|
+
const auditKind = `${kind}_describe` as AuditItem['kind']
|
|
332
|
+
|
|
333
|
+
// Look for schema definitions like FooOptionsSchema = ... .extend({
|
|
334
|
+
// and then find individual .describe() calls within
|
|
335
|
+
for (let i = 0; i < lines.length; i++) {
|
|
336
|
+
const line = lines[i]!
|
|
337
|
+
if (!line.includes(schemaKeyword) || !line.includes('Schema')) continue
|
|
338
|
+
if (!line.includes('extend') && !line.includes('object')) continue
|
|
339
|
+
|
|
340
|
+
// Scan forward within the schema block for property .describe() calls
|
|
341
|
+
let braceDepth = 0
|
|
342
|
+
let inSchema = false
|
|
343
|
+
for (let j = i; j < lines.length; j++) {
|
|
344
|
+
const sline = lines[j]!
|
|
345
|
+
for (const ch of sline) {
|
|
346
|
+
if (ch === '{') { braceDepth++; inSchema = true }
|
|
347
|
+
if (ch === '}') braceDepth--
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Look for: propertyName: z.something().describe('...')
|
|
351
|
+
const propMatch = sline.match(/^\s+(\w+)\s*:\s*z\./)
|
|
352
|
+
if (propMatch && inSchema) {
|
|
353
|
+
const propName = propMatch[1]!
|
|
354
|
+
const describeMatch = sline.match(/\.describe\(\s*['"`](.*)['"`]\s*\)/)
|
|
355
|
+
const descValue = describeMatch ? describeMatch[1] || '' : ''
|
|
356
|
+
items.push({
|
|
357
|
+
id: `${prefix}:${kind}:${propName}`,
|
|
358
|
+
kind: auditKind,
|
|
359
|
+
label: `${kind} schema: ${propName}`,
|
|
360
|
+
currentValue: descValue ? `.describe('${descValue}')` : '',
|
|
361
|
+
line: j + 1,
|
|
362
|
+
filePath,
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (inSchema && braceDepth === 0) break
|
|
367
|
+
}
|
|
368
|
+
break // only process first matching schema
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return items
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function extractParamNames(lines: string[], methodLine: number): string[] {
|
|
375
|
+
// Read the method signature (possibly multi-line) and extract parameter names
|
|
376
|
+
const params: string[] = []
|
|
377
|
+
let parenDepth = 0
|
|
378
|
+
let started = false
|
|
379
|
+
|
|
380
|
+
for (let i = methodLine; i < Math.min(methodLine + 15, lines.length); i++) {
|
|
381
|
+
const line = lines[i]!
|
|
382
|
+
for (let j = 0; j < line.length; j++) {
|
|
383
|
+
if (line[j] === '(') { parenDepth++; started = true }
|
|
384
|
+
if (line[j] === ')') parenDepth--
|
|
385
|
+
if (started && parenDepth === 0) {
|
|
386
|
+
// Extract param names from the collected signature
|
|
387
|
+
const sigStart = lines[methodLine]!.indexOf('(')
|
|
388
|
+
let sig = ''
|
|
389
|
+
for (let k = methodLine; k <= i; k++) {
|
|
390
|
+
sig += lines[k]! + '\n'
|
|
391
|
+
}
|
|
392
|
+
const insideParens = sig.substring(sig.indexOf('(') + 1, sig.lastIndexOf(')'))
|
|
393
|
+
// Split by commas (respecting nested generics/objects)
|
|
394
|
+
const paramStrings = splitParams(insideParens)
|
|
395
|
+
for (const ps of paramStrings) {
|
|
396
|
+
const nameMatch = ps.trim().match(/^(\w+)/)
|
|
397
|
+
if (nameMatch && nameMatch[1]) {
|
|
398
|
+
params.push(nameMatch[1])
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return params
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return params
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function splitParams(sig: string): string[] {
|
|
409
|
+
const results: string[] = []
|
|
410
|
+
let depth = 0
|
|
411
|
+
let current = ''
|
|
412
|
+
for (const ch of sig) {
|
|
413
|
+
if (ch === '<' || ch === '{' || ch === '(') depth++
|
|
414
|
+
if (ch === '>' || ch === '}' || ch === ')') depth--
|
|
415
|
+
if (ch === ',' && depth === 0) {
|
|
416
|
+
results.push(current)
|
|
417
|
+
current = ''
|
|
418
|
+
} else {
|
|
419
|
+
current += ch
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (current.trim()) results.push(current)
|
|
423
|
+
return results
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function extractParamDescription(jsdoc: string, paramName: string): string {
|
|
427
|
+
const regex = new RegExp(`@param\\s+(?:\\{[^}]*\\}\\s+)?\\[?${paramName}[^\\n]*`, 'g')
|
|
428
|
+
const match = jsdoc.match(regex)
|
|
429
|
+
if (!match) return ''
|
|
430
|
+
return match[0]!
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ── Display ──────────────────────────────────────────────────────────────────
|
|
434
|
+
|
|
435
|
+
function printHeader(title: string) {
|
|
436
|
+
const line = '─'.repeat(60)
|
|
437
|
+
console.log(`\n${c('cyan', line)}`)
|
|
438
|
+
console.log(c('bold', ` ${title}`))
|
|
439
|
+
console.log(`${c('cyan', line)}\n`)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function printModuleList(modules: ModuleInfo[], progress: AuditProgress) {
|
|
443
|
+
printHeader('Audit Modules (sorted by last edit)')
|
|
444
|
+
|
|
445
|
+
for (let i = 0; i < modules.length; i++) {
|
|
446
|
+
const mod = modules[i]!
|
|
447
|
+
const key = `${mod.registryName}.${mod.shortcut}`
|
|
448
|
+
const mp = progress.modules[key]
|
|
449
|
+
const icon = mp ? moduleStatusIcon(mp.status) : c('dim', '○')
|
|
450
|
+
const date = new Date(mod.mtime).toLocaleDateString()
|
|
451
|
+
|
|
452
|
+
let itemCounts = ''
|
|
453
|
+
if (mp) {
|
|
454
|
+
const total = Object.keys(mp.items).length
|
|
455
|
+
const good = Object.values(mp.items).filter(s => s === 'good').length
|
|
456
|
+
const needs = Object.values(mp.items).filter(s => s === 'needs_work').length
|
|
457
|
+
if (total > 0) {
|
|
458
|
+
itemCounts = c('dim', ` (${good}/${total} good${needs > 0 ? `, ${needs} needs work` : ''})`)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
console.log(` ${icon} ${c('bold', `${i + 1}.`)} ${c('white', key)} ${c('dim', date)}${itemCounts}`)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
console.log()
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function printAuditItem(item: AuditItem, status: ItemStatus, index: number, total: number) {
|
|
469
|
+
const relPath = item.filePath.replace(process.cwd() + '/', '')
|
|
470
|
+
console.log(c('dim', ` ${relPath}:${item.line}`))
|
|
471
|
+
console.log(c('bold', ` [${index + 1}/${total}] ${item.label}`))
|
|
472
|
+
console.log()
|
|
473
|
+
|
|
474
|
+
if (!item.currentValue) {
|
|
475
|
+
console.log(` ${c('red', 'MISSING')} - No documentation found`)
|
|
476
|
+
} else {
|
|
477
|
+
// Display the current value, indented
|
|
478
|
+
const displayLines = item.currentValue.split('\n')
|
|
479
|
+
for (const dl of displayLines) {
|
|
480
|
+
console.log(` ${c('dim', '│')} ${dl}`)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
console.log()
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
487
|
+
|
|
488
|
+
async function auditDocs(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
489
|
+
const { container } = context as any
|
|
490
|
+
|
|
491
|
+
const cache = container.feature('diskCache')
|
|
492
|
+
const fm = container.feature('fileManager')
|
|
493
|
+
await fm.start({ exclude: ['node_modules', 'dist', '.cache', '.git'] })
|
|
494
|
+
|
|
495
|
+
// Reset if requested
|
|
496
|
+
if (options.reset) {
|
|
497
|
+
await cache.rm('audit-docs:progress')
|
|
498
|
+
console.log(c('green', 'Audit progress has been reset.'))
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Load progress
|
|
502
|
+
let progress: AuditProgress
|
|
503
|
+
if (await cache.has('audit-docs:progress')) {
|
|
504
|
+
progress = await cache.get('audit-docs:progress', true) as AuditProgress
|
|
505
|
+
} else {
|
|
506
|
+
progress = { modules: {} }
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Discover all modules
|
|
510
|
+
const modules = discoverModules(container, fm)
|
|
511
|
+
|
|
512
|
+
if (modules.length === 0) {
|
|
513
|
+
console.log(c('red', 'No helper modules found.'))
|
|
514
|
+
return
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const rl = readline.createInterface({
|
|
518
|
+
input: process.stdin,
|
|
519
|
+
output: process.stdout,
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
const saveProgress = async () => {
|
|
523
|
+
await cache.set('audit-docs:progress', JSON.stringify(progress))
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
// If --module flag given, jump straight to it
|
|
528
|
+
if (options.module) {
|
|
529
|
+
const mod = modules.find(m =>
|
|
530
|
+
m.shortcut === options.module ||
|
|
531
|
+
`${m.registryName}.${m.shortcut}` === options.module
|
|
532
|
+
)
|
|
533
|
+
if (!mod) {
|
|
534
|
+
console.log(c('red', `Module "${options.module}" not found.`))
|
|
535
|
+
console.log('Available:', modules.map(m => `${m.registryName}.${m.shortcut}`).join(', '))
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
await auditModule(mod, progress, rl, saveProgress)
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Main menu loop
|
|
543
|
+
while (true) {
|
|
544
|
+
clearScreen()
|
|
545
|
+
printModuleList(modules, progress)
|
|
546
|
+
|
|
547
|
+
const completedCount = Object.values(progress.modules).filter(m => m.status === 'completed').length
|
|
548
|
+
console.log(c('dim', ` Progress: ${completedCount}/${modules.length} modules completed`))
|
|
549
|
+
console.log()
|
|
550
|
+
console.log(c('dim', ' Enter a number to audit, ') + c('cyan', 'r') + c('dim', ' to resume last, ') + c('cyan', 'q') + c('dim', ' to quit'))
|
|
551
|
+
|
|
552
|
+
const answer = await ask(rl, ' > ')
|
|
553
|
+
|
|
554
|
+
if (answer === 'q' || answer === 'quit') {
|
|
555
|
+
break
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (answer === 'r' || answer === 'resume') {
|
|
559
|
+
// Resume last in-progress module
|
|
560
|
+
const lastKey = progress.lastModule
|
|
561
|
+
if (lastKey) {
|
|
562
|
+
const mod = modules.find(m => `${m.registryName}.${m.shortcut}` === lastKey)
|
|
563
|
+
if (mod) {
|
|
564
|
+
await auditModule(mod, progress, rl, saveProgress)
|
|
565
|
+
continue
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
console.log(c('yellow', ' No module to resume.'))
|
|
569
|
+
await ask(rl, ' Press enter to continue...')
|
|
570
|
+
continue
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const num = parseInt(answer, 10)
|
|
574
|
+
if (num >= 1 && num <= modules.length) {
|
|
575
|
+
await auditModule(modules[num - 1]!, progress, rl, saveProgress)
|
|
576
|
+
continue
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
console.log(c('red', ' Invalid input.'))
|
|
580
|
+
await ask(rl, ' Press enter to continue...')
|
|
581
|
+
}
|
|
582
|
+
} finally {
|
|
583
|
+
rl.close()
|
|
584
|
+
await saveProgress()
|
|
585
|
+
console.log(c('dim', '\nProgress saved.\n'))
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async function auditModule(
|
|
590
|
+
mod: ModuleInfo,
|
|
591
|
+
progress: AuditProgress,
|
|
592
|
+
rl: readline.Interface,
|
|
593
|
+
saveProgress: () => Promise<void>,
|
|
594
|
+
) {
|
|
595
|
+
const key = `${mod.registryName}.${mod.shortcut}`
|
|
596
|
+
progress.lastModule = key
|
|
597
|
+
|
|
598
|
+
// Initialize module progress if needed
|
|
599
|
+
if (!progress.modules[key]) {
|
|
600
|
+
progress.modules[key] = { status: 'pending', items: {} }
|
|
601
|
+
}
|
|
602
|
+
const mp = progress.modules[key]!
|
|
603
|
+
mp.status = 'in_progress'
|
|
604
|
+
await saveProgress()
|
|
605
|
+
|
|
606
|
+
// Extract audit items from source
|
|
607
|
+
let items = extractAuditItems(mod.filePath, mod.shortcut, mod.registryName)
|
|
608
|
+
|
|
609
|
+
if (items.length === 0) {
|
|
610
|
+
console.log(c('yellow', `\n No auditable items found in ${mod.filePath}`))
|
|
611
|
+
mp.status = 'completed'
|
|
612
|
+
await saveProgress()
|
|
613
|
+
await ask(rl, ' Press enter to continue...')
|
|
614
|
+
return
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Initialize item statuses
|
|
618
|
+
for (const item of items) {
|
|
619
|
+
if (!mp.items[item.id]) {
|
|
620
|
+
mp.items[item.id] = 'pending'
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Find where to resume (first non-good item, or where we left off)
|
|
625
|
+
let startIdx = 0
|
|
626
|
+
if (mp.lastItem) {
|
|
627
|
+
const lastIdx = items.findIndex(it => it.id === mp.lastItem)
|
|
628
|
+
if (lastIdx >= 0) startIdx = lastIdx
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
for (let i = startIdx; i < items.length; i++) {
|
|
632
|
+
const item = items[i]!
|
|
633
|
+
const status = mp.items[item.id] || 'pending'
|
|
634
|
+
|
|
635
|
+
// Skip already-approved items
|
|
636
|
+
if (status === 'good') continue
|
|
637
|
+
|
|
638
|
+
clearScreen()
|
|
639
|
+
printHeader(`${key}`)
|
|
640
|
+
printAuditItem(item, status, i, items.length)
|
|
641
|
+
|
|
642
|
+
// Show quick status bar
|
|
643
|
+
const good = Object.values(mp.items).filter(s => s === 'good').length
|
|
644
|
+
const total = Object.keys(mp.items).length
|
|
645
|
+
console.log(c('dim', ` ${good}/${total} items approved`))
|
|
646
|
+
console.log()
|
|
647
|
+
console.log(` ${c('green', 'y')}=good ${c('yellow', 'e')}=edit in cursor ${c('dim', 's')}=skip ${c('cyan', 'n')}=next module ${c('red', 'q')}=quit`)
|
|
648
|
+
|
|
649
|
+
const answer = await ask(rl, ' > ')
|
|
650
|
+
|
|
651
|
+
if (answer === 'q' || answer === 'quit') {
|
|
652
|
+
mp.lastItem = item.id
|
|
653
|
+
await saveProgress()
|
|
654
|
+
return
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (answer === 'n' || answer === 'next') {
|
|
658
|
+
mp.lastItem = item.id
|
|
659
|
+
await saveProgress()
|
|
660
|
+
return
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (answer === 'y' || answer === 'yes' || answer === 'g' || answer === 'good') {
|
|
664
|
+
mp.items[item.id] = 'good'
|
|
665
|
+
mp.lastItem = item.id
|
|
666
|
+
await saveProgress()
|
|
667
|
+
continue
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (answer === 's' || answer === 'skip') {
|
|
671
|
+
mp.items[item.id] = 'skipped'
|
|
672
|
+
mp.lastItem = item.id
|
|
673
|
+
await saveProgress()
|
|
674
|
+
continue
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (answer === 'e' || answer === 'edit' || answer === '') {
|
|
678
|
+
mp.items[item.id] = 'needs_work'
|
|
679
|
+
|
|
680
|
+
// Open Cursor editor at the exact line and wait for file close
|
|
681
|
+
await new Promise<void>((resolve) => {
|
|
682
|
+
const child = spawn('cursor', ['--wait', '--goto', `${item.filePath}:${item.line}`], {
|
|
683
|
+
stdio: 'inherit',
|
|
684
|
+
})
|
|
685
|
+
child.on('close', () => resolve())
|
|
686
|
+
child.on('error', (err) => {
|
|
687
|
+
console.log(c('red', ` Failed to open cursor: ${err.message}`))
|
|
688
|
+
console.log(c('dim', ' Make sure "cursor" is in your PATH (Shell Command: Install from Cursor)'))
|
|
689
|
+
resolve()
|
|
690
|
+
})
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
// After vim exits, re-extract this item to show updated value
|
|
694
|
+
items = extractAuditItems(mod.filePath, mod.shortcut, mod.registryName)
|
|
695
|
+
const refreshed = items.find(it => it.id === item.id)
|
|
696
|
+
|
|
697
|
+
if (refreshed) {
|
|
698
|
+
clearScreen()
|
|
699
|
+
printHeader(`${key} (refreshed)`)
|
|
700
|
+
printAuditItem(refreshed, 'needs_work', i, items.length)
|
|
701
|
+
console.log(` ${c('green', 'y')}=good now ${c('yellow', 'e')}=edit again ${c('dim', 's')}=skip`)
|
|
702
|
+
|
|
703
|
+
const answer2 = await ask(rl, ' > ')
|
|
704
|
+
if (answer2 === 'y' || answer2 === 'yes' || answer2 === 'good') {
|
|
705
|
+
mp.items[item.id] = 'good'
|
|
706
|
+
} else if (answer2 === 'e' || answer2 === 'edit') {
|
|
707
|
+
// Re-edit: go back one step
|
|
708
|
+
i--
|
|
709
|
+
} else if (answer2 === 'q') {
|
|
710
|
+
mp.lastItem = item.id
|
|
711
|
+
await saveProgress()
|
|
712
|
+
return
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
mp.lastItem = item.id
|
|
717
|
+
await saveProgress()
|
|
718
|
+
continue
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Default: treat as skip
|
|
722
|
+
mp.lastItem = item.id
|
|
723
|
+
await saveProgress()
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Check if all items are handled
|
|
727
|
+
const allDone = Object.values(mp.items).every(s => s === 'good' || s === 'skipped')
|
|
728
|
+
if (allDone) {
|
|
729
|
+
mp.status = 'completed'
|
|
730
|
+
console.log(c('green', `\n ✓ Module ${key} audit complete!`))
|
|
731
|
+
}
|
|
732
|
+
await saveProgress()
|
|
733
|
+
await ask(rl, ' Press enter to continue...')
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
export default {
|
|
737
|
+
description: 'Begin or resume an audit of the various helper documentation and descriptions.',
|
|
738
|
+
argsSchema,
|
|
739
|
+
handler: auditDocs,
|
|
740
|
+
}
|