@rejot-dev/thalo-cli 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 - present ReJot Nederland B.V., and individual contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # thalo-cli
2
+
3
+ A command-line linter for thalo files. Runs diagnostics on `.thalo` and `.md` files containing thalo
4
+ syntax.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ # From the monorepo root
10
+ pnpm install
11
+ pnpm exec turbo run build --filter=@rejot-dev/thalo-cli
12
+
13
+ # Run directly
14
+ node apps/thalo-cli/dist/mod.js --help
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ thalo [options] [files or directories...]
21
+ ```
22
+
23
+ ### Basic Examples
24
+
25
+ ```bash
26
+ # Check all files in a directory
27
+ thalo notes/
28
+
29
+ # Check specific files
30
+ thalo file.thalo journal.md
31
+
32
+ # Check current directory
33
+ thalo
34
+ ```
35
+
36
+ ## Options
37
+
38
+ | Option | Description |
39
+ | --------------------- | ------------------------------------------------------ |
40
+ | `-h, --help` | Show help message |
41
+ | `-v, --version` | Show version number |
42
+ | `-q, --quiet` | Only show errors, suppress warnings and info |
43
+ | `-f, --format <fmt>` | Output format: `default`, `json`, `compact`, `github` |
44
+ | `--no-color` | Disable colored output |
45
+ | `--severity <level>` | Minimum severity to report: `error`, `warning`, `info` |
46
+ | `--max-warnings <n>` | Exit with error if warnings exceed threshold |
47
+ | `--rule <rule>=<sev>` | Set rule severity (can be repeated) |
48
+ | `--list-rules` | List all available rules |
49
+ | `-w, --watch` | Watch files for changes and re-run |
50
+
51
+ ## Output Formats
52
+
53
+ ### Default
54
+
55
+ Colored output with file location, severity, rule code, and message:
56
+
57
+ ```
58
+ /path/to/file.md:1:1 ERROR [unknown-entity] Unknown entity type 'lore'.
59
+ /path/to/file.md:3:11 WARNING [unresolved-link] Unresolved link '^self'.
60
+ ```
61
+
62
+ ### JSON (`-f json`)
63
+
64
+ Structured JSON output for tooling integration:
65
+
66
+ ```json
67
+ {
68
+ "files": 11,
69
+ "issues": 70,
70
+ "errors": 54,
71
+ "warnings": 16,
72
+ "info": 0,
73
+ "diagnostics": [
74
+ {
75
+ "file": "/path/to/file.md",
76
+ "line": 1,
77
+ "column": 1,
78
+ "endLine": 9,
79
+ "endColumn": 1,
80
+ "severity": "error",
81
+ "code": "unknown-entity",
82
+ "message": "Unknown entity type 'lore'."
83
+ }
84
+ ]
85
+ }
86
+ ```
87
+
88
+ ### Compact (`-f compact`)
89
+
90
+ Minimal single-line format:
91
+
92
+ ```
93
+ /path/to/file.md:1:1: E [unknown-entity] Unknown entity type 'lore'.
94
+ /path/to/file.md:3:11: W [unresolved-link] Unresolved link '^self'.
95
+ ```
96
+
97
+ ### GitHub Actions (`-f github`)
98
+
99
+ Workflow commands for GitHub Actions annotations:
100
+
101
+ ```
102
+ ::error file=/path/to/file.md,line=1,col=1,endLine=9,endColumn=1,title=unknown-entity::Unknown entity type 'lore'.
103
+ ::warning file=/path/to/file.md,line=3,col=11,endLine=3,endColumn=17,title=unresolved-link::Unresolved link '^self'.
104
+ ```
105
+
106
+ ## Rules
107
+
108
+ List all available rules with `--list-rules`:
109
+
110
+ | Rule | Default | Description |
111
+ | -------------------------- | ------- | -------------------------------- |
112
+ | `unknown-entity` | error | Unknown entity type used |
113
+ | `missing-required-field` | error | Required metadata field missing |
114
+ | `unknown-field` | warning | Unknown metadata field used |
115
+ | `invalid-field-type` | error | Field value has wrong type |
116
+ | `missing-required-section` | error | Required section missing |
117
+ | `unknown-section` | warning | Unknown section used |
118
+ | `unresolved-link` | warning | Link reference not found |
119
+ | `duplicate-link-id` | error | Same link ID used multiple times |
120
+
121
+ ### Configuring Rules
122
+
123
+ Override rule severities with `--rule`:
124
+
125
+ ```bash
126
+ # Disable a rule
127
+ thalo --rule unknown-entity=off notes/
128
+
129
+ # Change severity
130
+ thalo --rule unresolved-link=error notes/
131
+
132
+ # Multiple rules
133
+ thalo --rule unknown-entity=off --rule unknown-field=off notes/
134
+ ```
135
+
136
+ Valid severities: `error`, `warning`, `info`, `off`
137
+
138
+ ## CI Integration
139
+
140
+ ### GitHub Actions
141
+
142
+ ```yaml
143
+ - name: Lint thalo files
144
+ run: thalo -f github notes/
145
+ ```
146
+
147
+ ### Warning Threshold
148
+
149
+ Fail if too many warnings:
150
+
151
+ ```bash
152
+ thalo --max-warnings 10 notes/
153
+ ```
154
+
155
+ ### JSON Report
156
+
157
+ Generate a report file:
158
+
159
+ ```bash
160
+ thalo -f json notes/ > thalo-report.json
161
+ ```
162
+
163
+ ## Watch Mode
164
+
165
+ Re-run on file changes:
166
+
167
+ ```bash
168
+ thalo -w notes/
169
+ ```
170
+
171
+ Press `Ctrl+C` to exit.
172
+
173
+ ## Exit Codes
174
+
175
+ | Code | Meaning |
176
+ | ---- | ------------------------------------- |
177
+ | `0` | No errors |
178
+ | `1` | Errors found or max warnings exceeded |
179
+ | `2` | Invalid arguments |
package/bin/run.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/mod.js";
package/dist/cli.js ADDED
@@ -0,0 +1,175 @@
1
+ import { parseArgs } from "node:util";
2
+ import pc from "picocolors";
3
+
4
+ //#region src/cli.ts
5
+ const VERSION = "0.1.0";
6
+ /**
7
+ * Convert our option definitions to node:util parseArgs config
8
+ */
9
+ function toParseArgsConfig(options) {
10
+ const config = {};
11
+ for (const [name, def] of Object.entries(options)) {
12
+ const optConfig = { type: def.type };
13
+ if (def.short) optConfig.short = def.short;
14
+ if (def.default !== void 0) optConfig.default = def.default;
15
+ if (def.multiple !== void 0) optConfig.multiple = def.multiple;
16
+ config[name] = optConfig;
17
+ }
18
+ return config;
19
+ }
20
+ /**
21
+ * Global options available to all commands
22
+ */
23
+ const globalOptions = {
24
+ help: {
25
+ type: "boolean",
26
+ short: "h",
27
+ description: "Show help for the command",
28
+ default: false
29
+ },
30
+ version: {
31
+ type: "boolean",
32
+ short: "V",
33
+ description: "Show version number",
34
+ default: false
35
+ },
36
+ "no-color": {
37
+ type: "boolean",
38
+ description: "Disable colored output",
39
+ default: false
40
+ }
41
+ };
42
+ /**
43
+ * Generate help text for a command
44
+ */
45
+ function generateHelp(command, commandPath = []) {
46
+ const lines = [];
47
+ const fullCommandName = commandPath.length > 0 ? commandPath.join(" ") : "thalo";
48
+ lines.push("");
49
+ lines.push(pc.bold(command.description));
50
+ lines.push("");
51
+ lines.push(pc.bold("USAGE"));
52
+ let usageLine = ` ${fullCommandName}`;
53
+ if (command.subcommands && Object.keys(command.subcommands).length > 0) usageLine += " <command>";
54
+ if (command.options && Object.keys(command.options).length > 0) usageLine += " [options]";
55
+ if (command.args) {
56
+ const argName = command.args.multiple ? `<${command.args.name}...>` : `<${command.args.name}>`;
57
+ usageLine += command.args.required ? ` ${argName}` : ` [${argName}]`;
58
+ }
59
+ if (command.usage) usageLine = ` ${fullCommandName} ${command.usage}`;
60
+ lines.push(usageLine);
61
+ lines.push("");
62
+ if (command.subcommands && Object.keys(command.subcommands).length > 0) {
63
+ lines.push(pc.bold("COMMANDS"));
64
+ const subcommandNames = Object.keys(command.subcommands);
65
+ const maxLen = Math.max(...subcommandNames.map((n) => n.length));
66
+ for (const [name, subcmd] of Object.entries(command.subcommands)) {
67
+ const paddedName = name.padEnd(maxLen + 2);
68
+ lines.push(` ${pc.cyan(paddedName)}${subcmd.description}`);
69
+ }
70
+ lines.push("");
71
+ lines.push(` Run '${fullCommandName} <command> --help' for more information on a command.`);
72
+ lines.push("");
73
+ }
74
+ const allOptions = {
75
+ ...globalOptions,
76
+ ...command.options
77
+ };
78
+ if (Object.keys(allOptions).length > 0) {
79
+ lines.push(pc.bold("OPTIONS"));
80
+ const optionEntries = Object.entries(allOptions);
81
+ const optionStrings = optionEntries.map(([name, def]) => {
82
+ return `${def.short ? `-${def.short}, ` : " "}${`--${name}`}${def.type === "string" ? ` <${def.choices ? def.choices.join("|") : "value"}>` : ""}`;
83
+ });
84
+ const maxOptLen = Math.max(...optionStrings.map((s) => s.length));
85
+ for (let i = 0; i < optionEntries.length; i++) {
86
+ const [, def] = optionEntries[i];
87
+ const optStr = optionStrings[i].padEnd(maxOptLen + 2);
88
+ let description = def.description;
89
+ if (def.default !== void 0 && def.default !== false && def.default !== "") description += pc.dim(` (default: ${def.default})`);
90
+ lines.push(` ${pc.dim(optStr)}${description}`);
91
+ }
92
+ lines.push("");
93
+ }
94
+ return lines.join("\n");
95
+ }
96
+ /**
97
+ * Find a command by traversing the path
98
+ */
99
+ function findCommand(root, args) {
100
+ let current = root;
101
+ const commandPath = ["thalo"];
102
+ const remainingArgs = [...args];
103
+ while (remainingArgs.length > 0) {
104
+ const nextArg = remainingArgs[0];
105
+ if (nextArg.startsWith("-")) break;
106
+ if (current.subcommands && current.subcommands[nextArg]) {
107
+ current = current.subcommands[nextArg];
108
+ commandPath.push(nextArg);
109
+ remainingArgs.shift();
110
+ } else break;
111
+ }
112
+ return {
113
+ command: current,
114
+ commandPath,
115
+ remainingArgs
116
+ };
117
+ }
118
+ /**
119
+ * Parse and run a command
120
+ */
121
+ function runCli(rootCommand, argv = process.argv.slice(2)) {
122
+ const { command, commandPath, remainingArgs } = findCommand(rootCommand, argv);
123
+ const parseArgsOptions = toParseArgsConfig({
124
+ ...globalOptions,
125
+ ...command.options
126
+ });
127
+ let parsed;
128
+ try {
129
+ parsed = parseArgs({
130
+ args: remainingArgs,
131
+ options: parseArgsOptions,
132
+ allowPositionals: true,
133
+ strict: true
134
+ });
135
+ } catch (err) {
136
+ if (err instanceof Error) {
137
+ console.error(pc.red(`Error: ${err.message}`));
138
+ console.error(`\nRun '${commandPath.join(" ")} --help' for usage information.`);
139
+ }
140
+ process.exit(2);
141
+ }
142
+ const { values, positionals } = parsed;
143
+ if (values["no-color"]) process.env["NO_COLOR"] = "1";
144
+ if (values["version"] && commandPath.length === 1) {
145
+ console.log(`thalo v${VERSION}`);
146
+ process.exit(0);
147
+ }
148
+ if (values["help"]) {
149
+ console.log(generateHelp(command, commandPath));
150
+ process.exit(0);
151
+ }
152
+ if (command.subcommands && Object.keys(command.subcommands).length > 0 && !command.action) {
153
+ console.log(generateHelp(command, commandPath));
154
+ process.exit(0);
155
+ }
156
+ if (command.action) {
157
+ const ctx = {
158
+ options: values,
159
+ args: positionals,
160
+ command,
161
+ commandPath
162
+ };
163
+ Promise.resolve(command.action(ctx)).catch((err) => {
164
+ console.error(pc.red(`Error: ${err instanceof Error ? err.message : err}`));
165
+ process.exit(1);
166
+ });
167
+ } else {
168
+ console.log(generateHelp(command, commandPath));
169
+ process.exit(0);
170
+ }
171
+ }
172
+
173
+ //#endregion
174
+ export { runCli };
175
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":["config: ParseArgsConfig[\"options\"]","optConfig: NonNullable<ParseArgsConfig[\"options\"]>[string]","globalOptions: Record<string, OptionDef>","lines: string[]","parsed: ReturnType<typeof parseArgs>","ctx: CommandContext"],"sources":["../src/cli.ts"],"sourcesContent":["import { parseArgs, type ParseArgsConfig } from \"node:util\";\nimport pc from \"picocolors\";\n\nconst VERSION = \"0.1.0\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Option definition for a command\n */\nexport interface OptionDef {\n type: \"boolean\" | \"string\";\n short?: string;\n description: string;\n default?: string | boolean;\n choices?: string[];\n multiple?: boolean;\n}\n\n/**\n * Command definition\n */\nexport interface CommandDef {\n name: string;\n description: string;\n usage?: string;\n options?: Record<string, OptionDef>;\n args?: {\n name: string;\n description: string;\n required?: boolean;\n multiple?: boolean;\n };\n subcommands?: Record<string, CommandDef>;\n action?: (ctx: CommandContext) => void | Promise<void>;\n}\n\n/**\n * Parsed command context passed to action handlers\n */\nexport interface CommandContext {\n options: Record<string, string | boolean | string[]>;\n args: string[];\n command: CommandDef;\n commandPath: string[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Framework\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Convert our option definitions to node:util parseArgs config\n */\nfunction toParseArgsConfig(options: Record<string, OptionDef>): ParseArgsConfig[\"options\"] {\n const config: ParseArgsConfig[\"options\"] = {};\n\n for (const [name, def] of Object.entries(options)) {\n const optConfig: NonNullable<ParseArgsConfig[\"options\"]>[string] = {\n type: def.type,\n };\n\n if (def.short) {\n optConfig.short = def.short;\n }\n\n if (def.default !== undefined) {\n optConfig.default = def.default;\n }\n\n if (def.multiple !== undefined) {\n optConfig.multiple = def.multiple;\n }\n\n config[name] = optConfig;\n }\n\n return config;\n}\n\n/**\n * Global options available to all commands\n */\nconst globalOptions: Record<string, OptionDef> = {\n help: {\n type: \"boolean\",\n short: \"h\",\n description: \"Show help for the command\",\n default: false,\n },\n version: {\n type: \"boolean\",\n short: \"V\",\n description: \"Show version number\",\n default: false,\n },\n \"no-color\": {\n type: \"boolean\",\n description: \"Disable colored output\",\n default: false,\n },\n};\n\n/**\n * Generate help text for a command\n */\nexport function generateHelp(command: CommandDef, commandPath: string[] = []): string {\n const lines: string[] = [];\n const fullCommandName = commandPath.length > 0 ? commandPath.join(\" \") : \"thalo\";\n\n // Header\n lines.push(\"\");\n lines.push(pc.bold(command.description));\n lines.push(\"\");\n\n // Usage\n lines.push(pc.bold(\"USAGE\"));\n let usageLine = ` ${fullCommandName}`;\n\n if (command.subcommands && Object.keys(command.subcommands).length > 0) {\n usageLine += \" <command>\";\n }\n\n if (command.options && Object.keys(command.options).length > 0) {\n usageLine += \" [options]\";\n }\n\n if (command.args) {\n const argName = command.args.multiple ? `<${command.args.name}...>` : `<${command.args.name}>`;\n usageLine += command.args.required ? ` ${argName}` : ` [${argName}]`;\n }\n\n if (command.usage) {\n usageLine = ` ${fullCommandName} ${command.usage}`;\n }\n\n lines.push(usageLine);\n lines.push(\"\");\n\n // Subcommands\n if (command.subcommands && Object.keys(command.subcommands).length > 0) {\n lines.push(pc.bold(\"COMMANDS\"));\n\n const subcommandNames = Object.keys(command.subcommands);\n const maxLen = Math.max(...subcommandNames.map((n) => n.length));\n\n for (const [name, subcmd] of Object.entries(command.subcommands)) {\n const paddedName = name.padEnd(maxLen + 2);\n lines.push(` ${pc.cyan(paddedName)}${subcmd.description}`);\n }\n\n lines.push(\"\");\n lines.push(` Run '${fullCommandName} <command> --help' for more information on a command.`);\n lines.push(\"\");\n }\n\n // Options\n const allOptions = { ...globalOptions, ...command.options };\n if (Object.keys(allOptions).length > 0) {\n lines.push(pc.bold(\"OPTIONS\"));\n\n const optionEntries = Object.entries(allOptions);\n const optionStrings = optionEntries.map(([name, def]) => {\n const shortFlag = def.short ? `-${def.short}, ` : \" \";\n const longFlag = `--${name}`;\n const valueHint =\n def.type === \"string\" ? ` <${def.choices ? def.choices.join(\"|\") : \"value\"}>` : \"\";\n return `${shortFlag}${longFlag}${valueHint}`;\n });\n const maxOptLen = Math.max(...optionStrings.map((s) => s.length));\n\n for (let i = 0; i < optionEntries.length; i++) {\n const [, def] = optionEntries[i];\n const optStr = optionStrings[i].padEnd(maxOptLen + 2);\n let description = def.description;\n\n if (def.default !== undefined && def.default !== false && def.default !== \"\") {\n description += pc.dim(` (default: ${def.default})`);\n }\n\n lines.push(` ${pc.dim(optStr)}${description}`);\n }\n\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Find a command by traversing the path\n */\nfunction findCommand(\n root: CommandDef,\n args: string[],\n): { command: CommandDef; commandPath: string[]; remainingArgs: string[] } {\n let current = root;\n const commandPath = [\"thalo\"];\n const remainingArgs = [...args];\n\n while (remainingArgs.length > 0) {\n const nextArg = remainingArgs[0];\n\n // Stop if it's an option\n if (nextArg.startsWith(\"-\")) {\n break;\n }\n\n // Check if it's a subcommand\n if (current.subcommands && current.subcommands[nextArg]) {\n current = current.subcommands[nextArg];\n commandPath.push(nextArg);\n remainingArgs.shift();\n } else {\n // Not a subcommand, must be a positional arg\n break;\n }\n }\n\n return { command: current, commandPath, remainingArgs };\n}\n\n/**\n * Parse and run a command\n */\nexport function runCli(rootCommand: CommandDef, argv: string[] = process.argv.slice(2)): void {\n // Find the target command\n const { command, commandPath, remainingArgs } = findCommand(rootCommand, argv);\n\n // Merge global options with command options\n const allOptions = { ...globalOptions, ...command.options };\n const parseArgsOptions = toParseArgsConfig(allOptions);\n\n // Parse arguments\n let parsed: ReturnType<typeof parseArgs>;\n try {\n parsed = parseArgs({\n args: remainingArgs,\n options: parseArgsOptions,\n allowPositionals: true,\n strict: true,\n });\n } catch (err) {\n if (err instanceof Error) {\n console.error(pc.red(`Error: ${err.message}`));\n console.error(`\\nRun '${commandPath.join(\" \")} --help' for usage information.`);\n }\n process.exit(2);\n }\n\n const { values, positionals } = parsed;\n\n // Handle no-color by setting env var (picocolors respects NO_COLOR)\n if (values[\"no-color\"]) {\n process.env[\"NO_COLOR\"] = \"1\";\n }\n\n // Handle version (only at root level)\n if (values[\"version\"] && commandPath.length === 1) {\n console.log(`thalo v${VERSION}`);\n process.exit(0);\n }\n\n // Handle help\n if (values[\"help\"]) {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n\n // If command has subcommands but no action and no subcommand was specified, show help\n if (command.subcommands && Object.keys(command.subcommands).length > 0 && !command.action) {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n\n // Run the command action\n if (command.action) {\n const ctx: CommandContext = {\n options: values as Record<string, string | boolean | string[]>,\n args: positionals,\n command,\n commandPath,\n };\n\n Promise.resolve(command.action(ctx)).catch((err) => {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : err}`));\n process.exit(1);\n });\n } else {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import { loadFullWorkspace, relativePath } from "../files.js";
2
+ import pc from "picocolors";
3
+ import { DEFAULT_INSTRUCTIONS_TEMPLATE, generateInstructions, generateTimestamp, runActualize } from "@rejot-dev/thalo";
4
+ import { formatCheckpoint } from "@rejot-dev/thalo/change-tracker";
5
+ import { UncommittedChangesError, createChangeTracker } from "@rejot-dev/thalo/change-tracker/node";
6
+
7
+ //#region src/commands/actualize.ts
8
+ /**
9
+ * Format a synthesis that is up to date.
10
+ */
11
+ function formatUpToDate(synthesis) {
12
+ return pc.green(`✓ ${relativePath(synthesis.file)}: ${synthesis.title} - up to date`);
13
+ }
14
+ /**
15
+ * Format a synthesis with pending entries.
16
+ */
17
+ function formatSynthesisWithEntries(synthesis, instructionsTemplate) {
18
+ const lines = [];
19
+ lines.push("");
20
+ lines.push(pc.bold(pc.cyan(`=== Synthesis: ${synthesis.title} (${relativePath(synthesis.file)}) ===`)));
21
+ lines.push(`Target: ${pc.yellow(`^${synthesis.linkId}`)}`);
22
+ lines.push(`Sources: ${synthesis.sources.join(", ")}`);
23
+ if (synthesis.lastCheckpoint) {
24
+ const markerDisplay = synthesis.lastCheckpoint.type === "git" ? `git:${synthesis.lastCheckpoint.value.slice(0, 7)}` : formatCheckpoint(synthesis.lastCheckpoint);
25
+ lines.push(`Last checkpoint: ${pc.dim(markerDisplay)}`);
26
+ }
27
+ lines.push("");
28
+ lines.push(pc.bold("--- User Prompt ---"));
29
+ lines.push(synthesis.prompt || pc.dim("(no prompt defined)"));
30
+ lines.push("");
31
+ lines.push(pc.bold(`--- Changed Entries (${synthesis.entries.length}) ---`));
32
+ for (const entry of synthesis.entries) {
33
+ lines.push("");
34
+ lines.push(entry.rawText);
35
+ }
36
+ const checkpointValue = formatCheckpoint(synthesis.currentCheckpoint);
37
+ const instructions = generateInstructions(instructionsTemplate, {
38
+ file: relativePath(synthesis.file),
39
+ linkId: synthesis.linkId,
40
+ checkpoint: checkpointValue,
41
+ timestamp: generateTimestamp()
42
+ });
43
+ lines.push("");
44
+ lines.push(pc.bold("--- Instructions ---"));
45
+ lines.push(instructions);
46
+ lines.push("");
47
+ lines.push(pc.dim("─".repeat(60)));
48
+ return lines.join("\n");
49
+ }
50
+ async function actualizeAction(ctx) {
51
+ const { args, options } = ctx;
52
+ const instructionsTemplate = options["instructions"] || DEFAULT_INSTRUCTIONS_TEMPLATE;
53
+ const force = options["force"];
54
+ const { workspace, files } = await loadFullWorkspace();
55
+ if (files.length === 0) {
56
+ console.log("No .thalo or .md files found.");
57
+ process.exit(0);
58
+ }
59
+ const tracker = await createChangeTracker({
60
+ cwd: process.cwd(),
61
+ force
62
+ });
63
+ let result;
64
+ try {
65
+ result = await runActualize(workspace, {
66
+ targetLinkIds: args.length > 0 ? args : void 0,
67
+ tracker
68
+ });
69
+ } catch (error) {
70
+ if (error instanceof UncommittedChangesError) {
71
+ console.error(pc.red("Error: Source files have uncommitted changes:"));
72
+ for (const file of error.files) console.error(pc.yellow(` - ${file}`));
73
+ console.error();
74
+ console.error(pc.dim("Commit your changes or use --force to proceed anyway."));
75
+ console.error(pc.dim("Note: Using --force may cause already-processed entries to appear again."));
76
+ process.exit(1);
77
+ }
78
+ throw error;
79
+ }
80
+ if (result.trackerType === "git") console.log(pc.dim("Using git-based change tracking"));
81
+ for (const id of result.notFoundLinkIds) console.error(pc.yellow(`Warning: No synthesis found with link ID: ^${id}`));
82
+ if (args.length > 0 && result.syntheses.length === 0 && result.notFoundLinkIds.length > 0) process.exit(1);
83
+ if (result.syntheses.length === 0) {
84
+ console.log(pc.dim("No synthesis definitions found."));
85
+ process.exit(0);
86
+ }
87
+ let hasOutput = false;
88
+ for (const synthesis of result.syntheses) if (synthesis.isUpToDate) console.log(formatUpToDate(synthesis));
89
+ else {
90
+ hasOutput = true;
91
+ console.log(formatSynthesisWithEntries(synthesis, instructionsTemplate));
92
+ }
93
+ if (!hasOutput) {
94
+ console.log();
95
+ console.log(pc.green("All syntheses are up to date."));
96
+ }
97
+ }
98
+ const actualizeCommand = {
99
+ name: "actualize",
100
+ description: "Output prompts and entries for pending synthesis updates",
101
+ args: {
102
+ name: "links",
103
+ description: "Link IDs of synthesis definitions to actualize (e.g., ^my-synthesis). If omitted, all syntheses are checked.",
104
+ required: false,
105
+ multiple: true
106
+ },
107
+ options: {
108
+ instructions: {
109
+ type: "string",
110
+ short: "i",
111
+ description: "Custom instructions template. Use placeholders: {file}, {linkId}, {checkpoint}"
112
+ },
113
+ force: {
114
+ type: "boolean",
115
+ short: "f",
116
+ description: "Proceed even if source files have uncommitted changes. May cause duplicate processing."
117
+ }
118
+ },
119
+ action: actualizeAction
120
+ };
121
+
122
+ //#endregion
123
+ export { actualizeCommand };
124
+ //# sourceMappingURL=actualize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actualize.js","names":["lines: string[]","actualizeCommand: CommandDef"],"sources":["../../src/commands/actualize.ts"],"sourcesContent":["import {\n runActualize,\n generateInstructions,\n generateTimestamp,\n DEFAULT_INSTRUCTIONS_TEMPLATE,\n type SynthesisOutputInfo,\n} from \"@rejot-dev/thalo\";\nimport { formatCheckpoint } from \"@rejot-dev/thalo/change-tracker\";\nimport { createChangeTracker, UncommittedChangesError } from \"@rejot-dev/thalo/change-tracker/node\";\nimport pc from \"picocolors\";\nimport type { CommandDef, CommandContext } from \"../cli.js\";\nimport { loadFullWorkspace, relativePath } from \"../files.js\";\n\n/**\n * Format a synthesis that is up to date.\n */\nfunction formatUpToDate(synthesis: SynthesisOutputInfo): string {\n return pc.green(`✓ ${relativePath(synthesis.file)}: ${synthesis.title} - up to date`);\n}\n\n/**\n * Format a synthesis with pending entries.\n */\nfunction formatSynthesisWithEntries(\n synthesis: SynthesisOutputInfo,\n instructionsTemplate: string,\n): string {\n const lines: string[] = [];\n\n // Header\n lines.push(\"\");\n lines.push(\n pc.bold(pc.cyan(`=== Synthesis: ${synthesis.title} (${relativePath(synthesis.file)}) ===`)),\n );\n lines.push(`Target: ${pc.yellow(`^${synthesis.linkId}`)}`);\n lines.push(`Sources: ${synthesis.sources.join(\", \")}`);\n\n if (synthesis.lastCheckpoint) {\n const markerDisplay =\n synthesis.lastCheckpoint.type === \"git\"\n ? `git:${synthesis.lastCheckpoint.value.slice(0, 7)}`\n : formatCheckpoint(synthesis.lastCheckpoint);\n lines.push(`Last checkpoint: ${pc.dim(markerDisplay)}`);\n }\n\n // Prompt\n lines.push(\"\");\n lines.push(pc.bold(\"--- User Prompt ---\"));\n lines.push(synthesis.prompt || pc.dim(\"(no prompt defined)\"));\n\n // Entries\n lines.push(\"\");\n lines.push(pc.bold(`--- Changed Entries (${synthesis.entries.length}) ---`));\n for (const entry of synthesis.entries) {\n lines.push(\"\");\n lines.push(entry.rawText);\n }\n\n // Instructions\n const checkpointValue = formatCheckpoint(synthesis.currentCheckpoint);\n const instructions = generateInstructions(instructionsTemplate, {\n file: relativePath(synthesis.file),\n linkId: synthesis.linkId,\n checkpoint: checkpointValue,\n timestamp: generateTimestamp(),\n });\n\n lines.push(\"\");\n lines.push(pc.bold(\"--- Instructions ---\"));\n lines.push(instructions);\n lines.push(\"\");\n lines.push(pc.dim(\"─\".repeat(60)));\n\n return lines.join(\"\\n\");\n}\n\nasync function actualizeAction(ctx: CommandContext): Promise<void> {\n const { args, options } = ctx;\n const instructionsTemplate = (options[\"instructions\"] as string) || DEFAULT_INSTRUCTIONS_TEMPLATE;\n const force = options[\"force\"] as boolean;\n\n // Load full workspace from CWD\n const { workspace, files } = await loadFullWorkspace();\n if (files.length === 0) {\n console.log(\"No .thalo or .md files found.\");\n process.exit(0);\n }\n\n // Create change tracker (auto-detects git)\n const tracker = await createChangeTracker({ cwd: process.cwd(), force });\n\n // Run actualize command\n let result;\n try {\n result = await runActualize(workspace, {\n targetLinkIds: args.length > 0 ? args : undefined,\n tracker,\n });\n } catch (error) {\n if (error instanceof UncommittedChangesError) {\n console.error(pc.red(\"Error: Source files have uncommitted changes:\"));\n for (const file of error.files) {\n console.error(pc.yellow(` - ${file}`));\n }\n console.error();\n console.error(pc.dim(\"Commit your changes or use --force to proceed anyway.\"));\n console.error(\n pc.dim(\"Note: Using --force may cause already-processed entries to appear again.\"),\n );\n process.exit(1);\n }\n throw error;\n }\n\n // Show tracker type\n if (result.trackerType === \"git\") {\n console.log(pc.dim(\"Using git-based change tracking\"));\n }\n\n // Warn about not found link IDs\n for (const id of result.notFoundLinkIds) {\n console.error(pc.yellow(`Warning: No synthesis found with link ID: ^${id}`));\n }\n\n // Exit early if we filtered to specific IDs but found none\n if (args.length > 0 && result.syntheses.length === 0 && result.notFoundLinkIds.length > 0) {\n process.exit(1);\n }\n\n if (result.syntheses.length === 0) {\n console.log(pc.dim(\"No synthesis definitions found.\"));\n process.exit(0);\n }\n\n // Output results\n let hasOutput = false;\n for (const synthesis of result.syntheses) {\n if (synthesis.isUpToDate) {\n console.log(formatUpToDate(synthesis));\n } else {\n hasOutput = true;\n console.log(formatSynthesisWithEntries(synthesis, instructionsTemplate));\n }\n }\n\n if (!hasOutput) {\n console.log();\n console.log(pc.green(\"All syntheses are up to date.\"));\n }\n}\n\nexport const actualizeCommand: CommandDef = {\n name: \"actualize\",\n description: \"Output prompts and entries for pending synthesis updates\",\n args: {\n name: \"links\",\n description:\n \"Link IDs of synthesis definitions to actualize (e.g., ^my-synthesis). If omitted, all syntheses are checked.\",\n required: false,\n multiple: true,\n },\n options: {\n instructions: {\n type: \"string\",\n short: \"i\",\n description: \"Custom instructions template. Use placeholders: {file}, {linkId}, {checkpoint}\",\n },\n force: {\n type: \"boolean\",\n short: \"f\",\n description:\n \"Proceed even if source files have uncommitted changes. May cause duplicate processing.\",\n },\n },\n action: actualizeAction,\n};\n"],"mappings":"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"}