@dittowords/spec-cli 0.0.1-alpha.1 → 0.0.1-alpha.11
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 +95 -26
- package/dist/api.d.ts +82 -0
- package/dist/api.js +103 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +4 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +87 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.js +57 -0
- package/dist/commands/create-rule.d.ts +9 -0
- package/dist/commands/create-rule.js +33 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +237 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +51 -0
- package/dist/commands/pull.d.ts +5 -0
- package/dist/commands/pull.js +168 -0
- package/dist/commands/scaffold.d.ts +6 -0
- package/dist/commands/scaffold.js +51 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.js +101 -0
- package/dist/discover.d.ts +5 -0
- package/dist/discover.js +24 -0
- package/dist/parse.d.ts +24 -0
- package/dist/parse.js +42 -0
- package/dist/serialize.d.ts +5 -0
- package/dist/serialize.js +60 -0
- package/dist/skill-content.d.ts +1 -0
- package/dist/skill-content.js +156 -0
- package/dist/skills.d.ts +4 -0
- package/dist/skills.js +284 -0
- package/package.json +19 -5
- package/src/api.ts +0 -45
- package/src/bin.js +0 -3
- package/src/cli.ts +0 -70
- package/src/commands/check.ts +0 -48
- package/src/commands/init.ts +0 -219
- package/src/commands/list.ts +0 -59
- package/src/commands/pull.ts +0 -140
- package/src/commands/scaffold.ts +0 -40
- package/src/config.ts +0 -55
- package/src/discover.ts +0 -23
- package/src/parse.ts +0 -45
- package/src/serialize.ts +0 -38
package/src/config.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import dotenv from "dotenv";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
|
|
5
|
-
export interface Config {
|
|
6
|
-
apiBase: string;
|
|
7
|
-
workspaceId: string;
|
|
8
|
-
roots?: string[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const DEFAULT_CONFIG_NAME = "dittospec.config.json";
|
|
12
|
-
|
|
13
|
-
export function loadConfig(cwd: string = process.cwd()): { config: Config; root: string } {
|
|
14
|
-
let dir = path.resolve(cwd);
|
|
15
|
-
while (true) {
|
|
16
|
-
const candidate = path.join(dir, DEFAULT_CONFIG_NAME);
|
|
17
|
-
if (fs.existsSync(candidate)) {
|
|
18
|
-
const raw = fs.readFileSync(candidate, "utf8");
|
|
19
|
-
const parsed = JSON.parse(raw);
|
|
20
|
-
validate(parsed, candidate);
|
|
21
|
-
return { config: parsed as Config, root: dir };
|
|
22
|
-
}
|
|
23
|
-
const parent = path.dirname(dir);
|
|
24
|
-
if (parent === dir) break;
|
|
25
|
-
dir = parent;
|
|
26
|
-
}
|
|
27
|
-
throw new Error(`No ${DEFAULT_CONFIG_NAME} found from ${cwd} up to filesystem root.`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function validate(c: unknown, source: string): asserts c is Config {
|
|
31
|
-
if (!c || typeof c !== "object") throw new Error(`${source}: must be a JSON object.`);
|
|
32
|
-
const obj = c as Record<string, unknown>;
|
|
33
|
-
if (typeof obj.apiBase !== "string") throw new Error(`${source}: missing string "apiBase".`);
|
|
34
|
-
if (typeof obj.workspaceId !== "string") throw new Error(`${source}: missing string "workspaceId".`);
|
|
35
|
-
if (obj.roots !== undefined && !Array.isArray(obj.roots)) {
|
|
36
|
-
throw new Error(`${source}: "roots" must be an array of strings if present.`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function getApiKey(): string {
|
|
41
|
-
try {
|
|
42
|
-
const { root } = loadConfig();
|
|
43
|
-
dotenv.config({ path: path.join(root, ".env") });
|
|
44
|
-
} catch {
|
|
45
|
-
// No config yet — let getApiKey still work for direct env exports.
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const key = process.env.DITTO_API_KEY;
|
|
49
|
-
if (!key) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
"DITTO_API_KEY is not set. Add it to .env at the repo root, or `export DITTO_API_KEY=...` in your shell."
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
return key;
|
|
55
|
-
}
|
package/src/discover.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { globSync } from "glob";
|
|
2
|
-
import path from "path";
|
|
3
|
-
|
|
4
|
-
const SPEC_GLOB = "**/*.ditto.md";
|
|
5
|
-
|
|
6
|
-
const IGNORE = ["**/node_modules/**", "**/dist*/**", "**/.git/**", "**/.next/**", "**/coverage/**"];
|
|
7
|
-
|
|
8
|
-
export interface DiscoveredFile {
|
|
9
|
-
abs: string;
|
|
10
|
-
rel: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function discover(repoRoot: string, roots: string[] = ["."]): DiscoveredFile[] {
|
|
14
|
-
const patterns = roots.map((r) => path.posix.join(r, SPEC_GLOB));
|
|
15
|
-
|
|
16
|
-
const results = globSync(patterns, {
|
|
17
|
-
cwd: repoRoot,
|
|
18
|
-
ignore: IGNORE,
|
|
19
|
-
absolute: true,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return results.map((abs) => ({ abs, rel: path.relative(repoRoot, abs) })).sort((a, b) => a.rel.localeCompare(b.rel));
|
|
23
|
-
}
|
package/src/parse.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import yaml from "js-yaml";
|
|
3
|
-
|
|
4
|
-
export interface ParsedSpec {
|
|
5
|
-
kind: "component" | "workspace";
|
|
6
|
-
name?: string;
|
|
7
|
-
spec: Record<string, unknown>;
|
|
8
|
-
filePath: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class ParseError extends Error {
|
|
12
|
-
constructor(public filePath: string, public reason: string) {
|
|
13
|
-
super(`${filePath}: ${reason}`);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function parseSpecFile(filePath: string): ParsedSpec {
|
|
18
|
-
const source = fs.readFileSync(filePath, "utf8");
|
|
19
|
-
const frontmatter = extractFrontmatter(source, filePath);
|
|
20
|
-
const raw = yaml.load(frontmatter);
|
|
21
|
-
|
|
22
|
-
if (!raw || typeof raw !== "object") {
|
|
23
|
-
throw new ParseError(filePath, "frontmatter must be a YAML object");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const spec = raw as Record<string, unknown>;
|
|
27
|
-
|
|
28
|
-
if (spec.workspace === true) {
|
|
29
|
-
return { kind: "workspace", spec, filePath };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (typeof spec.component !== "string" || !spec.component) {
|
|
33
|
-
throw new ParseError(filePath, "missing required 'component' key (string)");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { kind: "component", name: spec.component, spec, filePath };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function extractFrontmatter(source: string, filePath: string): string {
|
|
40
|
-
const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
41
|
-
if (!match) {
|
|
42
|
-
throw new ParseError(filePath, "no YAML frontmatter found (expected --- delimiters)");
|
|
43
|
-
}
|
|
44
|
-
return match[1];
|
|
45
|
-
}
|
package/src/serialize.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import yaml from "js-yaml";
|
|
3
|
-
|
|
4
|
-
const MANAGED_COMMENT = "# Managed by Ditto — do not edit below";
|
|
5
|
-
|
|
6
|
-
export function rewriteManagedKeys(filePath: string, updates: { rules?: unknown[] }): void {
|
|
7
|
-
const source = fs.readFileSync(filePath, "utf8");
|
|
8
|
-
const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
9
|
-
if (!match) throw new Error(`${filePath}: no YAML frontmatter found`);
|
|
10
|
-
|
|
11
|
-
const spec = yaml.load(match[1]) as Record<string, unknown>;
|
|
12
|
-
|
|
13
|
-
if (updates.rules !== undefined) spec.rules = updates.rules;
|
|
14
|
-
|
|
15
|
-
let dumped = yaml
|
|
16
|
-
.dump(spec, {
|
|
17
|
-
lineWidth: -1,
|
|
18
|
-
noRefs: true,
|
|
19
|
-
sortKeys: false,
|
|
20
|
-
quotingType: '"',
|
|
21
|
-
})
|
|
22
|
-
.trimEnd();
|
|
23
|
-
|
|
24
|
-
dumped = insertManagedComment(dumped);
|
|
25
|
-
|
|
26
|
-
const afterFrontmatter = source.slice(match[0].length);
|
|
27
|
-
fs.writeFileSync(filePath, `---\n${dumped}\n---${afterFrontmatter}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function insertManagedComment(dumped: string): string {
|
|
31
|
-
const rulesIdx = dumped.search(/^rules:/m);
|
|
32
|
-
if (rulesIdx === -1) return dumped;
|
|
33
|
-
|
|
34
|
-
const before = dumped.slice(0, rulesIdx);
|
|
35
|
-
if (before.includes(MANAGED_COMMENT)) return dumped;
|
|
36
|
-
|
|
37
|
-
return before + MANAGED_COMMENT + "\n" + dumped.slice(rulesIdx);
|
|
38
|
-
}
|