laconic-skill 0.1.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/README.md ADDED
@@ -0,0 +1,43 @@
1
+ npx laconic-skill check output.txt --receipt
2
+
3
+ # laconic-skill
4
+
5
+ Install and run in one command:
6
+
7
+ ```bash
8
+ npx laconic-skill check output.txt --receipt
9
+ cat output.txt | npx laconic-skill check - --receipt
10
+ ```
11
+
12
+ Or install globally:
13
+
14
+ ```bash
15
+ npm install -g laconic-skill
16
+ laconic check output.txt --receipt
17
+ ```
18
+
19
+ ## What It Does
20
+
21
+ `laconic-skill` checks a text answer for concise Laconic output rules and can emit a tamper-evident CLI receipt.
22
+
23
+ Checks:
24
+
25
+ - direct answer, no filler opening
26
+ - complete answer, no trailing ellipsis
27
+ - brief answer
28
+ - no Markdown bold
29
+
30
+ ## Commands
31
+
32
+ ```bash
33
+ laconic-skill --help
34
+ laconic-skill check output.txt
35
+ laconic-skill check output.txt --receipt
36
+ laconic check output.txt --receipt
37
+ ```
38
+
39
+ `--receipt` implies JSON output with checks, metrics, answer hash, and receipt hash.
40
+
41
+ Failed checks exit `1` but still emit receipt JSON when `--receipt` is used.
42
+
43
+ Receipts are tamper-evident CLI receipts. They are not factual verification and do not prove the answer is true.
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash } from "node:crypto";
4
+ import { readFile } from "node:fs/promises";
5
+ import { basename } from "node:path";
6
+
7
+ const VERSION = "0.1.0";
8
+ const FILLER_PREFIXES = [
9
+ "sure",
10
+ "certainly",
11
+ "of course",
12
+ "absolutely",
13
+ "as an ai",
14
+ "i'd be happy to",
15
+ "i can help",
16
+ ];
17
+
18
+ function printHelp() {
19
+ console.log(`laconic-skill ${VERSION}
20
+
21
+ Usage:
22
+ npx laconic-skill check <file> --receipt
23
+ npx laconic-skill check - --receipt
24
+ laconic check <file> --receipt
25
+
26
+ Commands:
27
+ check <file> Check an answer file for laconic output quality.
28
+ check - Read answer text from stdin.
29
+
30
+ Options:
31
+ --receipt Include a tamper-evident JSON receipt.
32
+ --json Print JSON only.
33
+ -h, --help Show this help.
34
+ -v, --version Show version.
35
+ `);
36
+ }
37
+
38
+ function sha256(text) {
39
+ return createHash("sha256").update(text).digest("hex");
40
+ }
41
+
42
+ function stableJson(value) {
43
+ if (Array.isArray(value)) {
44
+ return `[${value.map(stableJson).join(",")}]`;
45
+ }
46
+ if (value && typeof value === "object") {
47
+ return `{${Object.keys(value)
48
+ .sort()
49
+ .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
50
+ .join(",")}}`;
51
+ }
52
+ return JSON.stringify(value);
53
+ }
54
+
55
+ function countWords(text) {
56
+ const matches = text.trim().match(/\S+/g);
57
+ return matches ? matches.length : 0;
58
+ }
59
+
60
+ function readingTimeSeconds(text) {
61
+ return Math.max(1, Math.ceil((countWords(text) / 225) * 60));
62
+ }
63
+
64
+ function runChecks(answer) {
65
+ const trimmed = answer.trim();
66
+ const normalized = trimmed.toLowerCase();
67
+ const hasMarkdownBold = /\*\*[^*]+\*\*|__[^_]+__/.test(answer);
68
+ const direct = normalized.length > 0 && !FILLER_PREFIXES.some((prefix) => normalized.startsWith(prefix));
69
+ const complete = normalized.length > 0 && !normalized.endsWith("...");
70
+ const brief = countWords(answer) <= 120;
71
+
72
+ return {
73
+ direct,
74
+ complete,
75
+ brief,
76
+ no_markdown_bold: !hasMarkdownBold,
77
+ };
78
+ }
79
+
80
+ function buildReceipt({ file, answer, checks }) {
81
+ const timestamp = new Date().toISOString();
82
+ const canonical = {
83
+ tool: "laconic-skill",
84
+ version: VERSION,
85
+ file: basename(file),
86
+ answer_hash: sha256(answer),
87
+ answer_length: countWords(answer),
88
+ reading_time_seconds: readingTimeSeconds(answer),
89
+ checks,
90
+ timestamp,
91
+ };
92
+
93
+ return {
94
+ ...canonical,
95
+ receipt_hash: sha256(stableJson(canonical)),
96
+ };
97
+ }
98
+
99
+ async function readStdin() {
100
+ const chunks = [];
101
+ for await (const chunk of process.stdin) {
102
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
103
+ }
104
+ return Buffer.concat(chunks).toString("utf8");
105
+ }
106
+
107
+ function getInputTarget(args) {
108
+ return args.find((arg) => arg === "-" || !arg.startsWith("-"));
109
+ }
110
+
111
+ async function checkCommand(args) {
112
+ const file = getInputTarget(args);
113
+ const includeReceipt = args.includes("--receipt");
114
+ const jsonOnly = args.includes("--json");
115
+
116
+ if (!file) {
117
+ throw new Error("Missing file. Usage: laconic-skill check <file> --receipt");
118
+ }
119
+
120
+ const answer = file === "-" ? await readStdin() : await readFile(file, "utf8");
121
+ const checks = runChecks(answer);
122
+ const passed = Object.values(checks).every(Boolean);
123
+ const result = {
124
+ ok: passed,
125
+ file,
126
+ checks,
127
+ metrics: {
128
+ answer_length: countWords(answer),
129
+ reading_time_seconds: readingTimeSeconds(answer),
130
+ },
131
+ };
132
+
133
+ if (includeReceipt) {
134
+ result.receipt = buildReceipt({ file, answer, checks });
135
+ }
136
+
137
+ if (jsonOnly || includeReceipt) {
138
+ console.log(JSON.stringify(result, null, 2));
139
+ } else {
140
+ console.log(passed ? "OK" : "FAILED");
141
+ for (const [name, value] of Object.entries(checks)) {
142
+ console.log(`${name}: ${value}`);
143
+ }
144
+ }
145
+
146
+ process.exitCode = passed ? 0 : 1;
147
+ }
148
+
149
+ async function main() {
150
+ const [, , command, ...args] = process.argv;
151
+
152
+ if (!command || command === "--help" || command === "-h") {
153
+ printHelp();
154
+ return;
155
+ }
156
+ if (command === "--version" || command === "-v") {
157
+ console.log(VERSION);
158
+ return;
159
+ }
160
+ if (command === "check") {
161
+ await checkCommand(args);
162
+ return;
163
+ }
164
+
165
+ throw new Error(`Unknown command: ${command}`);
166
+ }
167
+
168
+ main().catch((error) => {
169
+ console.error(error instanceof Error ? error.message : String(error));
170
+ process.exitCode = 1;
171
+ });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "laconic-skill",
3
+ "version": "0.1.0",
4
+ "description": "A command-line checker for Laconic answers and tamper-evident receipts.",
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "node --test test/*.test.mjs"
8
+ },
9
+ "bin": {
10
+ "laconic-skill": "bin/laconic-skill.mjs",
11
+ "laconic": "bin/laconic-skill.mjs"
12
+ },
13
+ "files": [
14
+ "bin",
15
+ "README.md"
16
+ ],
17
+ "keywords": [
18
+ "laconic",
19
+ "receipt",
20
+ "cli",
21
+ "answer-checker"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/electricwolfemarshmallowhypertext/laco.git",
26
+ "directory": "packages/laconic-skill"
27
+ },
28
+ "homepage": "https://github.com/electricwolfemarshmallowhypertext/laco/tree/master/packages/laconic-skill#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/electricwolfemarshmallowhypertext/laco/issues"
31
+ },
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": ">=18"
35
+ }
36
+ }