eidosmd 0.1.0 → 0.3.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 +50 -29
- package/browser/dist/assets/index-Cc3cNWHY.css +1 -0
- package/browser/dist/assets/index-DQgCQRa5.js +46 -0
- package/browser/dist/favicon.svg +5 -0
- package/browser/dist/index.html +15 -0
- package/browser/dist/mark.svg +4 -0
- package/dist/src/cli.js +7 -0
- package/dist/src/commands/agents.js +1 -1
- package/dist/src/commands/canvas.js +77 -0
- package/dist/src/commands/check.js +10 -4
- package/dist/src/commands/configure.js +201 -0
- package/dist/src/commands/framework.js +37 -7
- package/dist/src/commands/index.js +4 -4
- package/dist/src/commands/init.js +5 -0
- package/dist/src/commands/instructions.js +1 -1
- package/dist/src/commands/list.js +8 -8
- package/dist/src/commands/migrate.js +32 -0
- package/dist/src/commands/new.js +3 -3
- package/dist/src/commands/property.js +125 -0
- package/dist/src/commands/seeds.js +5 -5
- package/dist/src/commands/setup.js +131 -0
- package/dist/src/commands/version.js +44 -0
- package/dist/src/commands/whoami.js +4 -4
- package/dist/src/context.js +5 -5
- package/dist/src/core/blueprint.js +16 -11
- package/dist/src/core/canvas-schema.js +148 -0
- package/dist/src/core/canvas.js +732 -0
- package/dist/src/core/check.js +221 -70
- package/dist/src/core/convert.js +7 -6
- package/dist/src/core/edits.js +1381 -0
- package/dist/src/core/framework-markdown.js +119 -34
- package/dist/src/core/framework-model.js +62 -10
- package/dist/src/core/framework-structured.js +222 -47
- package/dist/src/core/framework.js +13 -13
- package/dist/src/core/frontmatter.js +61 -1
- package/dist/src/core/git.js +84 -0
- package/dist/src/core/index-leaf.js +2 -2
- package/dist/src/core/links.js +87 -0
- package/dist/src/core/markdown.js +16 -9
- package/dist/src/core/me.js +16 -8
- package/dist/src/core/migrate.js +319 -0
- package/dist/src/core/naming.js +1 -1
- package/dist/src/core/regions.js +117 -0
- package/dist/src/core/root.js +2 -2
- package/dist/src/core/scaffold.js +33 -27
- package/dist/src/core/seed.js +187 -71
- package/dist/src/core/server.js +1410 -53
- package/dist/src/core/settings.js +232 -0
- package/dist/src/core/store.js +315 -0
- package/dist/src/core/template.js +32 -0
- package/dist/src/core/versions.js +84 -0
- package/dist/src/output.js +4 -1
- package/dist/src/program.js +421 -41
- package/instructions/authoring.md +15 -12
- package/instructions/configuring.md +81 -34
- package/instructions/init-required.md +4 -4
- package/instructions/overview.md +21 -9
- package/instructions/validating.md +9 -6
- package/package.json +21 -12
- package/standard/EIDOS.md +142 -259
- package/standard/seeds/README.md +12 -16
- package/standard/seeds/book/Framework.yaml +61 -0
- package/standard/seeds/book/README.md +10 -5
- package/standard/seeds/book/_gitignore +9 -3
- package/standard/seeds/book/me.md +1 -1
- package/standard/seeds/book/roles/README.md +3 -3
- package/standard/seeds/book/roles/framework-owner.md +2 -2
- package/standard/seeds/book/{shapes → templates}/chapter.full.md +0 -8
- package/standard/seeds/book/{shapes → templates}/chapter.sketch.md +0 -7
- package/standard/seeds/book/{shapes → templates}/frame.market.md +0 -6
- package/standard/seeds/book/templates/frame.premise.md +17 -0
- package/standard/seeds/book/{shapes → templates}/frame.reader.md +0 -6
- package/standard/seeds/book/{shapes → templates}/frame.voice.md +0 -7
- package/standard/seeds/research/Framework.yaml +61 -0
- package/standard/seeds/research/README.md +10 -5
- package/standard/seeds/research/_gitignore +9 -3
- package/standard/seeds/research/me.md +1 -1
- package/standard/seeds/research/roles/README.md +3 -3
- package/standard/seeds/research/roles/framework-owner.md +2 -2
- package/standard/seeds/research/{shapes → templates}/frame.ethics.md +0 -6
- package/standard/seeds/research/{shapes → templates}/frame.method.md +0 -7
- package/standard/seeds/research/{shapes → templates}/frame.prior-work.md +0 -6
- package/standard/seeds/research/{shapes → templates}/frame.question.md +0 -7
- package/standard/seeds/research/{shapes → templates}/investigation.full.md +0 -8
- package/standard/seeds/research/{shapes → templates}/investigation.note.md +0 -7
- package/standard/seeds/software/Framework.yaml +62 -0
- package/standard/seeds/software/README.md +7 -6
- package/standard/seeds/software/_gitignore +9 -3
- package/standard/seeds/software/me.md +1 -1
- package/standard/seeds/software/roles/README.md +3 -3
- package/standard/seeds/software/roles/framework-owner.md +2 -2
- package/standard/seeds/software/roles/project-manager.md +2 -2
- package/standard/seeds/software/roles/stakeholder.md +1 -1
- package/standard/seeds/software/{shapes → templates}/frame.architecture.md +0 -7
- package/standard/seeds/software/{shapes → templates}/frame.audience.md +1 -8
- package/standard/seeds/software/{shapes → templates}/frame.criteria.md +0 -8
- package/standard/seeds/software/{shapes → templates}/frame.market.md +0 -8
- package/standard/seeds/software/{shapes → templates}/spec.full.md +0 -8
- package/standard/seeds/software/{shapes → templates}/spec.micro.md +0 -9
- package/browser/index.html +0 -268
- package/dist/src/commands/convert.js +0 -30
- package/dist/src/core/shape.js +0 -26
- package/standard/seeds/book/Framework.md +0 -87
- package/standard/seeds/book/shapes/frame.premise.md +0 -24
- package/standard/seeds/research/Framework.md +0 -88
- package/standard/seeds/software/Framework.md +0 -88
- /package/standard/seeds/software/{shapes → templates}/.gitkeep +0 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Eidos">
|
|
2
|
+
<rect width="32" height="32" rx="7" fill="#000000"/>
|
|
3
|
+
<rect x="5.1" y="5.1" width="21.8" height="21.8" rx="5" fill="none" stroke="#e08a63" stroke-width="1.8" opacity="0.5"/>
|
|
4
|
+
<path d="M16 9.4 21.9 16 16 22.6 10.1 16 16 9.4Z" fill="#e08a63"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="dark light" />
|
|
7
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
8
|
+
<title>Eidos</title>
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DQgCQRa5.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Cc3cNWHY.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="app"></div>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" role="img" aria-label="Eidos">
|
|
2
|
+
<rect x="2.75" y="2.75" width="26.5" height="26.5" rx="6.25" stroke="#e08a63" stroke-width="1.9" opacity="0.42"/>
|
|
3
|
+
<path d="M16 8.6 22.9 16 16 23.4 9.1 16 16 8.6Z" fill="#e08a63"/>
|
|
4
|
+
</svg>
|
package/dist/src/cli.js
CHANGED
|
@@ -2,4 +2,11 @@
|
|
|
2
2
|
// The executable entry point: one root of Eidos blueprints, scaffolded,
|
|
3
3
|
// validated, and indexed from the shell.
|
|
4
4
|
import { buildProgram } from './program.js';
|
|
5
|
+
// A reader that closes the pipe early (`eidos framework | head`) is not an
|
|
6
|
+
// error worth a stack trace: stop writing and leave quietly.
|
|
7
|
+
process.stdout.on('error', (error) => {
|
|
8
|
+
if (error.code === 'EPIPE')
|
|
9
|
+
process.exit(0);
|
|
10
|
+
throw error;
|
|
11
|
+
});
|
|
5
12
|
buildProgram().parse(process.argv);
|
|
@@ -10,7 +10,7 @@ export function nudge() {
|
|
|
10
10
|
NUDGE_START,
|
|
11
11
|
'## Eidos',
|
|
12
12
|
'',
|
|
13
|
-
'This repository keeps its blueprints in an Eidos root. Before creating, editing, reviewing, or discussing a blueprint or the framework, run `eidos instructions overview` and follow it; it names the guide to read for each kind of work. Use the `eidos` CLI for structure (`eidos new`, `eidos check`, `eidos index`); never hand-assemble frontmatter or edit
|
|
13
|
+
'This repository keeps its blueprints in an Eidos root. Before creating, editing, reviewing, or discussing a blueprint or the framework, run `eidos instructions overview` and follow it; it names the guide to read for each kind of work. Use the `eidos` CLI for structure (`eidos new`, `eidos check`, `eidos index`); never hand-assemble frontmatter or edit the generated index in the framework document.',
|
|
14
14
|
NUDGE_END,
|
|
15
15
|
].join('\n');
|
|
16
16
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// `eidos canvas`: the canvas maps a root keeps under .eidos/plugins/eidosmd/maps/, listed or
|
|
2
|
+
// created from the shell; the browser is where they are drawn.
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { stringify } from 'yaml';
|
|
6
|
+
import { createCanvas, findPage, listCanvases, loadCanvas, CanvasError } from '../core/canvas.js';
|
|
7
|
+
import { CANVAS_SCHEMA } from '../core/canvas-schema.js';
|
|
8
|
+
import { CliError, EXIT_OK, print, printJson } from '../output.js';
|
|
9
|
+
export function runCanvasList(context, options) {
|
|
10
|
+
const canvases = listCanvases(context.framework);
|
|
11
|
+
if (options.json) {
|
|
12
|
+
printJson(canvases.map((canvas) => ({ id: canvas.id, title: canvas.title, path: path.relative(context.root, canvas.file).split(path.sep).join('/'), pages: canvas.pages.map((page) => ({ id: page.id, title: page.title, nodes: page.nodes.length, edges: page.edges.length })), problems: canvas.problems })));
|
|
13
|
+
return EXIT_OK;
|
|
14
|
+
}
|
|
15
|
+
if (canvases.length === 0) {
|
|
16
|
+
print('no canvases under .eidos/plugins/eidosmd/maps/; create one: eidos canvas new "<title>", or open eidos browser');
|
|
17
|
+
return EXIT_OK;
|
|
18
|
+
}
|
|
19
|
+
for (const canvas of canvases) {
|
|
20
|
+
print(`${canvas.id} — ${canvas.title}`);
|
|
21
|
+
for (const page of canvas.pages) {
|
|
22
|
+
print(` ${page.id}: ${page.title} (${page.nodes.length} node${page.nodes.length === 1 ? '' : 's'}, ${page.edges.length} edge${page.edges.length === 1 ? '' : 's'})`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return EXIT_OK;
|
|
26
|
+
}
|
|
27
|
+
export function runCanvasNew(context, title, options) {
|
|
28
|
+
let canvas;
|
|
29
|
+
try {
|
|
30
|
+
canvas = createCanvas(context.framework, title);
|
|
31
|
+
}
|
|
32
|
+
catch (cause) {
|
|
33
|
+
if (cause instanceof CanvasError) {
|
|
34
|
+
throw new CliError(cause.message);
|
|
35
|
+
}
|
|
36
|
+
throw cause;
|
|
37
|
+
}
|
|
38
|
+
const rel = path.relative(context.root, canvas.file).split(path.sep).join('/');
|
|
39
|
+
if (options.json) {
|
|
40
|
+
printJson({ id: canvas.id, title: canvas.title, path: rel });
|
|
41
|
+
return EXIT_OK;
|
|
42
|
+
}
|
|
43
|
+
print(`✓ ${rel} (canvas ${canvas.id}, one page: overview)`, ' next: eidos browser, and draw');
|
|
44
|
+
return EXIT_OK;
|
|
45
|
+
}
|
|
46
|
+
// The file as it is, or one page of it, for a person or an agent about to edit it.
|
|
47
|
+
export function runCanvasShow(context, id, options) {
|
|
48
|
+
let canvas;
|
|
49
|
+
try {
|
|
50
|
+
canvas = loadCanvas(context.framework, id);
|
|
51
|
+
if (options.page !== undefined) {
|
|
52
|
+
const page = findPage(canvas, options.page);
|
|
53
|
+
if (options.json)
|
|
54
|
+
printJson(page);
|
|
55
|
+
else
|
|
56
|
+
print(stringify(page).trimEnd());
|
|
57
|
+
return EXIT_OK;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (cause) {
|
|
61
|
+
if (cause instanceof CanvasError) {
|
|
62
|
+
throw new CliError(cause.message);
|
|
63
|
+
}
|
|
64
|
+
throw cause;
|
|
65
|
+
}
|
|
66
|
+
if (options.json) {
|
|
67
|
+
printJson({ id: canvas.id, title: canvas.title, path: path.relative(context.root, canvas.file).split(path.sep).join('/'), pages: canvas.pages, problems: canvas.problems });
|
|
68
|
+
return EXIT_OK;
|
|
69
|
+
}
|
|
70
|
+
print(readFileSync(canvas.file, 'utf8').trimEnd());
|
|
71
|
+
return EXIT_OK;
|
|
72
|
+
}
|
|
73
|
+
// The JSON Schema of the file, for whoever edits one without the browser.
|
|
74
|
+
export function runCanvasSchema() {
|
|
75
|
+
printJson(CANVAS_SCHEMA);
|
|
76
|
+
return EXIT_OK;
|
|
77
|
+
}
|
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
// whether anything was an error.
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { checkRoot } from '../core/check.js';
|
|
6
|
+
import { readSettings } from '../core/settings.js';
|
|
6
7
|
import { standardVersion } from '../paths.js';
|
|
7
8
|
import { CliError, EXIT_FINDINGS, EXIT_OK, print, printJson } from '../output.js';
|
|
9
|
+
// Whether a warning fails this run: the flag when given, else the root's setting.
|
|
10
|
+
export function strictFor(root, flag) {
|
|
11
|
+
return flag ?? readSettings(root).shared.check.strict;
|
|
12
|
+
}
|
|
8
13
|
function mark(finding) {
|
|
9
14
|
return finding.level === 'error' ? '✗' : '⚠';
|
|
10
15
|
}
|
|
@@ -16,17 +21,18 @@ export function runCheck(context, paths, options) {
|
|
|
16
21
|
const resolved = path.resolve(context.cwd, given);
|
|
17
22
|
const match = context.blueprints.find((blueprint) => path.resolve(blueprint.path) === resolved);
|
|
18
23
|
if (!match) {
|
|
19
|
-
throw new CliError(`${given} is not a blueprint in ${context.root} (not in a declared collection
|
|
24
|
+
throw new CliError(`${given} is not a blueprint in ${context.root} (not in a declared collection)`);
|
|
20
25
|
}
|
|
21
26
|
only.add(match.path);
|
|
22
27
|
}
|
|
23
28
|
}
|
|
24
29
|
const report = checkRoot(context.framework, context.blueprints, { only, standardVersion: standardVersion() });
|
|
25
|
-
const
|
|
30
|
+
const strict = strictFor(context.root, options.strict);
|
|
31
|
+
const failed = report.errors > 0 || (strict && report.warnings > 0);
|
|
26
32
|
if (options.json) {
|
|
27
33
|
printJson({
|
|
28
34
|
ok: !failed,
|
|
29
|
-
strict
|
|
35
|
+
strict,
|
|
30
36
|
root: report.root,
|
|
31
37
|
eidos_version: report.eidosVersion,
|
|
32
38
|
naming: report.naming,
|
|
@@ -55,7 +61,7 @@ export function runCheck(context, paths, options) {
|
|
|
55
61
|
}
|
|
56
62
|
else {
|
|
57
63
|
const parts = [`${report.errors} error${report.errors === 1 ? '' : 's'}`, `${report.warnings} warning${report.warnings === 1 ? '' : 's'}`];
|
|
58
|
-
print(`${failed ? '✗' : '⚠'} ${parts.join(', ')}${
|
|
64
|
+
print(`${failed ? '✗' : '⚠'} ${parts.join(', ')}${strict && report.errors === 0 && report.warnings > 0 ? (options.strict === undefined ? ' (strict: this root fails on warnings)' : ' (--strict)') : ''}`);
|
|
59
65
|
}
|
|
60
66
|
return failed ? EXIT_FINDINGS : EXIT_OK;
|
|
61
67
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// `eidos configure:<noun> <operation>`: the framework edited from the shell.
|
|
2
|
+
// Every mutating command builds a plan (src/core/edits.ts), prints it whole,
|
|
3
|
+
// and asks before writing; --dry-run prints and stops, --yes proceeds
|
|
4
|
+
// without the question, --force proceeds through what needs forcing. With
|
|
5
|
+
// no terminal and no switch the command refuses and names the switch, so an
|
|
6
|
+
// agent learns it from the error instead of hanging on a prompt.
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { applyPlan, editContext, EditError, planJson } from '../core/edits.js';
|
|
9
|
+
import { listRoles } from '../core/me.js';
|
|
10
|
+
import { readSettings } from '../core/settings.js';
|
|
11
|
+
import { standardVersion } from '../paths.js';
|
|
12
|
+
import { askYesNo, interactive } from './setup.js';
|
|
13
|
+
import { CliError, EXIT_FINDINGS, EXIT_OK, EXIT_USAGE, print, printJson } from '../output.js';
|
|
14
|
+
const describe = (step) => {
|
|
15
|
+
switch (step.kind) {
|
|
16
|
+
case 'document':
|
|
17
|
+
return `~ ${step.path} ${step.what}`;
|
|
18
|
+
case 'key':
|
|
19
|
+
if (step.op === 'rename')
|
|
20
|
+
return `~ ${step.path} ${step.key} → ${step.to ?? ''}`;
|
|
21
|
+
if (step.op === 'delete')
|
|
22
|
+
return `~ ${step.path} drop ${step.key}`;
|
|
23
|
+
return `~ ${step.path} ${step.key}: ${JSON.stringify(step.before ?? null)} → ${JSON.stringify(step.after ?? '')}`;
|
|
24
|
+
case 'links':
|
|
25
|
+
return `~ ${step.path} ${step.count} link${step.count === 1 ? '' : 's'} rewritten`;
|
|
26
|
+
case 'write':
|
|
27
|
+
return `+ ${step.path} ${step.what}`;
|
|
28
|
+
case 'move':
|
|
29
|
+
return `> ${step.from} → ${step.to}`;
|
|
30
|
+
case 'remove':
|
|
31
|
+
return `- ${step.path} ${step.what}`;
|
|
32
|
+
case 'settings':
|
|
33
|
+
return `~ ${step.path} ${step.what}`;
|
|
34
|
+
default:
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function printPlan(plan) {
|
|
39
|
+
print(`plan: ${plan.command}`);
|
|
40
|
+
if (plan.steps.length === 0)
|
|
41
|
+
print(' nothing to change');
|
|
42
|
+
for (const step of plan.steps)
|
|
43
|
+
print(` ${describe(step)}`);
|
|
44
|
+
if (plan.lost.length > 0) {
|
|
45
|
+
print('', 'values that will be gone:');
|
|
46
|
+
for (const lost of plan.lost)
|
|
47
|
+
print(` ${lost.path} ${lost.key}: ${JSON.stringify(lost.value)}`);
|
|
48
|
+
}
|
|
49
|
+
if (plan.notes.length > 0) {
|
|
50
|
+
print('', 'note:');
|
|
51
|
+
for (const note of plan.notes)
|
|
52
|
+
print(` ${note}`);
|
|
53
|
+
}
|
|
54
|
+
if (plan.conflicts.length > 0) {
|
|
55
|
+
print('', 'conflicts:');
|
|
56
|
+
for (const conflict of plan.conflicts)
|
|
57
|
+
print(` ✗ ${conflict.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Build, show, confirm, apply, then index and check: the one path every
|
|
61
|
+
// mutating configure command takes.
|
|
62
|
+
export async function runPlan(context, build, options) {
|
|
63
|
+
let plan;
|
|
64
|
+
try {
|
|
65
|
+
plan = build(editContext(context.root));
|
|
66
|
+
}
|
|
67
|
+
catch (cause) {
|
|
68
|
+
if (cause instanceof EditError)
|
|
69
|
+
throw new CliError(cause.message);
|
|
70
|
+
throw cause;
|
|
71
|
+
}
|
|
72
|
+
if (!options.json)
|
|
73
|
+
printPlan(plan);
|
|
74
|
+
const blocking = plan.conflicts.filter((conflict) => !conflict.force || !options.force);
|
|
75
|
+
if (blocking.length > 0) {
|
|
76
|
+
if (options.json)
|
|
77
|
+
printJson({ ok: false, applied: false, ...planJson(plan) });
|
|
78
|
+
const forcible = blocking.every((conflict) => conflict.force);
|
|
79
|
+
throw new CliError(forcible ? 'the plan needs --force to proceed (see conflicts)' : `the plan cannot proceed: ${blocking.map((conflict) => conflict.message).join('; ')}`);
|
|
80
|
+
}
|
|
81
|
+
if (options.dryRun) {
|
|
82
|
+
if (options.json)
|
|
83
|
+
printJson({ ok: true, applied: false, dry_run: true, ...planJson(plan) });
|
|
84
|
+
else
|
|
85
|
+
print('', 'dry run: nothing written');
|
|
86
|
+
return EXIT_OK;
|
|
87
|
+
}
|
|
88
|
+
if (plan.steps.length === 0) {
|
|
89
|
+
if (options.json)
|
|
90
|
+
printJson({ ok: true, applied: false, ...planJson(plan) });
|
|
91
|
+
return EXIT_OK;
|
|
92
|
+
}
|
|
93
|
+
if (!options.yes && !options.force) {
|
|
94
|
+
if (!interactive()) {
|
|
95
|
+
if (options.json)
|
|
96
|
+
printJson({ ok: false, applied: false, ...planJson(plan) });
|
|
97
|
+
throw new CliError(plan.destructive ? 'not a terminal: values or files would be gone; run with --force to proceed, or --dry-run to see the plan' : 'not a terminal: run with --yes to proceed, or --dry-run to see the plan', EXIT_USAGE);
|
|
98
|
+
}
|
|
99
|
+
const go = await askYesNo(plan.destructive ? 'Proceed? Values or files listed above will be gone' : 'Proceed?', !plan.destructive);
|
|
100
|
+
if (!go) {
|
|
101
|
+
print('nothing written');
|
|
102
|
+
return EXIT_OK;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (plan.destructive && !options.force && !interactive()) {
|
|
106
|
+
if (options.json)
|
|
107
|
+
printJson({ ok: false, applied: false, ...planJson(plan) });
|
|
108
|
+
throw new CliError('not a terminal: values or files would be gone; --yes is not enough, run with --force', EXIT_USAGE);
|
|
109
|
+
}
|
|
110
|
+
const applied = applyPlan(plan, standardVersion());
|
|
111
|
+
const strict = readSettings(context.root).shared.check.strict;
|
|
112
|
+
const failed = applied.errors > 0 || (strict && applied.warnings > 0);
|
|
113
|
+
if (options.json) {
|
|
114
|
+
printJson({ ok: !failed, applied: true, ...planJson(plan), new_findings: applied.newFindings, errors: applied.errors, warnings: applied.warnings, strict });
|
|
115
|
+
return failed ? EXIT_FINDINGS : EXIT_OK;
|
|
116
|
+
}
|
|
117
|
+
print('', `✓ ${plan.steps.length} step${plan.steps.length === 1 ? '' : 's'} applied; index rewritten`);
|
|
118
|
+
if (applied.newFindings.length > 0) {
|
|
119
|
+
print(' check now also reports:');
|
|
120
|
+
for (const finding of applied.newFindings)
|
|
121
|
+
print(` ${finding.level === 'error' ? '✗' : '⚠'} ${finding.path ?? '(root)'} ${finding.code} ${finding.message}`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
print(' check: nothing new');
|
|
125
|
+
}
|
|
126
|
+
return failed ? EXIT_FINDINGS : EXIT_OK;
|
|
127
|
+
}
|
|
128
|
+
// ---- show -------------------------------------------------------------------
|
|
129
|
+
export function showProperty(context, name, json) {
|
|
130
|
+
const all = [...context.framework.schema.core, ...context.framework.schema.custom, ...Object.values(context.framework.schema.tools).flat()];
|
|
131
|
+
const chosen = name === undefined ? all : all.filter((property) => property.name === name);
|
|
132
|
+
if (name !== undefined && chosen.length === 0)
|
|
133
|
+
throw new CliError(`no property '${name}' (eidos framework lists them)`);
|
|
134
|
+
const view = chosen.map((property) => ({ name: property.name, type: property.type, applies_to: property.appliesTo, required: property.required, ...(property.options ? { options: property.options } : {}), meaning: property.meaning, owner: property.owner, ...(property.tools ? { tools: property.tools } : {}) }));
|
|
135
|
+
if (json) {
|
|
136
|
+
printJson(name === undefined ? view : view[0]);
|
|
137
|
+
return EXIT_OK;
|
|
138
|
+
}
|
|
139
|
+
for (const property of view) {
|
|
140
|
+
print(`${property.name} (${property.type}, ${property.applies_to === 'all' ? 'all' : property.applies_to.join(', ')}${property.required ? ', required' : ''}; ${property.owner === 'eidos' ? "the standard's" : property.owner === 'custom' ? 'yours' : `${property.owner}'s`})`);
|
|
141
|
+
if (property.meaning)
|
|
142
|
+
print(` ${property.meaning}`);
|
|
143
|
+
if (property.options)
|
|
144
|
+
print(` options: ${property.options.join(', ')}`);
|
|
145
|
+
}
|
|
146
|
+
return EXIT_OK;
|
|
147
|
+
}
|
|
148
|
+
export function showCollection(context, name, json) {
|
|
149
|
+
const chosen = name === undefined ? context.framework.collections : context.framework.collections.filter((collection) => collection.name.toLowerCase() === name.toLowerCase());
|
|
150
|
+
if (name !== undefined && chosen.length === 0)
|
|
151
|
+
throw new CliError(`no collection '${name}'`);
|
|
152
|
+
const view = chosen.map((collection) => ({ name: collection.name, description: collection.description, unit: collection.variants.find((variant) => variant.isDefault)?.unit ?? null, variants: collection.variants.map((variant) => ({ name: variant.name, template: variant.template, default: variant.isDefault, description: variant.description })), grouping: collection.grouping }));
|
|
153
|
+
if (json) {
|
|
154
|
+
printJson(name === undefined ? view : view[0]);
|
|
155
|
+
return EXIT_OK;
|
|
156
|
+
}
|
|
157
|
+
for (const collection of view) {
|
|
158
|
+
print(`${collection.name}${collection.unit ? ` (unit ${collection.unit})` : ''}`);
|
|
159
|
+
if (collection.description)
|
|
160
|
+
print(` ${collection.description}`);
|
|
161
|
+
print(` variants: ${collection.variants.map((variant) => `${variant.name}${variant.default ? ' (default)' : ''}`).join(', ')}`);
|
|
162
|
+
if (collection.grouping)
|
|
163
|
+
print(` ${collection.grouping.label}${collection.grouping.property ? ` (${collection.grouping.property})` : ''}: ${collection.grouping.groups.map((group) => group.name).join(', ') || 'none yet'}`);
|
|
164
|
+
}
|
|
165
|
+
return EXIT_OK;
|
|
166
|
+
}
|
|
167
|
+
// Every folder at the root as the framework declares it, with its type; a
|
|
168
|
+
// collection's variants and grouping are `configure:collection show`'s.
|
|
169
|
+
export function showFolder(context, name, json) {
|
|
170
|
+
const chosen = name === undefined ? context.framework.folders : context.framework.folders.filter((folder) => folder.name.toLowerCase() === name.toLowerCase());
|
|
171
|
+
if (name !== undefined && chosen.length === 0)
|
|
172
|
+
throw new CliError(`no folder '${name}' (eidos framework lists them)`);
|
|
173
|
+
const view = chosen.map((folder) => ({ name: folder.name, type: folder.type, description: folder.description }));
|
|
174
|
+
if (json) {
|
|
175
|
+
printJson(name === undefined ? view : view[0]);
|
|
176
|
+
return EXIT_OK;
|
|
177
|
+
}
|
|
178
|
+
for (const folder of view) {
|
|
179
|
+
print(`${folder.name} (${folder.type})`);
|
|
180
|
+
if (folder.description)
|
|
181
|
+
print(` ${folder.description}`);
|
|
182
|
+
}
|
|
183
|
+
return EXIT_OK;
|
|
184
|
+
}
|
|
185
|
+
export function showRole(context, name, json) {
|
|
186
|
+
const roles = listRoles(context.root);
|
|
187
|
+
const chosen = name === undefined ? roles : roles.filter((role) => role.name === name);
|
|
188
|
+
if (name !== undefined && chosen.length === 0)
|
|
189
|
+
throw new CliError(`no role '${name}' (eidos roles lists them)`);
|
|
190
|
+
if (json) {
|
|
191
|
+
printJson(name === undefined ? chosen.map((role) => ({ name: role.name, title: role.title, summary: role.summary })) : { name: chosen[0]?.name, title: chosen[0]?.title, summary: chosen[0]?.summary, text: readFileSync(chosen[0]?.file ?? '', 'utf8') });
|
|
192
|
+
return EXIT_OK;
|
|
193
|
+
}
|
|
194
|
+
if (name === undefined) {
|
|
195
|
+
for (const role of chosen)
|
|
196
|
+
print(`${role.name} ${role.title}${role.summary ? `: ${role.summary}` : ''}`);
|
|
197
|
+
return EXIT_OK;
|
|
198
|
+
}
|
|
199
|
+
process.stdout.write(readFileSync(chosen[0]?.file ?? '', 'utf8'));
|
|
200
|
+
return EXIT_OK;
|
|
201
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// `eidos framework`: what the root's framework document declares, as a human
|
|
2
2
|
// summary, as JSON an agent can read instead of parsing the file, or as the
|
|
3
|
-
//
|
|
3
|
+
// document itself, normalized.
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { isCollection, unitOf } from '../core/framework-model.js';
|
|
5
6
|
import { frameworkToDocument, serializeDocument } from '../core/framework-structured.js';
|
|
6
7
|
import { CliError, EXIT_OK, print, printJson } from '../output.js';
|
|
7
8
|
export function frameworkJson(framework) {
|
|
@@ -16,7 +17,7 @@ export function runFramework(context, options) {
|
|
|
16
17
|
const { framework } = context;
|
|
17
18
|
if (options.as !== undefined) {
|
|
18
19
|
if (options.as !== 'yaml') {
|
|
19
|
-
throw new CliError('--as takes yaml
|
|
20
|
+
throw new CliError('--as takes yaml');
|
|
20
21
|
}
|
|
21
22
|
process.stdout.write(serializeDocument(frameworkToDocument(framework)));
|
|
22
23
|
return EXIT_OK;
|
|
@@ -44,6 +45,10 @@ export function runFramework(context, options) {
|
|
|
44
45
|
if (collection.framing) {
|
|
45
46
|
tags.push('framing');
|
|
46
47
|
}
|
|
48
|
+
const unit = unitOf(collection);
|
|
49
|
+
if (unit) {
|
|
50
|
+
tags.push(`unit: ${unit}`);
|
|
51
|
+
}
|
|
47
52
|
if (collection.grouping) {
|
|
48
53
|
tags.push(`grouped by ${collection.grouping.label}${collection.grouping.property ? ` (\`${collection.grouping.property}\`)` : ''}`);
|
|
49
54
|
}
|
|
@@ -54,14 +59,21 @@ export function runFramework(context, options) {
|
|
|
54
59
|
if (collection.description) {
|
|
55
60
|
print(` ${collection.description}`);
|
|
56
61
|
}
|
|
57
|
-
const
|
|
58
|
-
print(`
|
|
62
|
+
const variants = collection.variants.map((variant) => `${variant.name}${variant.isDefault ? ' (default)' : ''}`);
|
|
63
|
+
print(` variants: ${variants.length > 0 ? variants.join(', ') : 'none declared'}`);
|
|
59
64
|
if (collection.grouping && collection.grouping.groups.length > 0) {
|
|
60
65
|
print(` groups: ${collection.grouping.groups.map((group) => group.name).join(', ')}`);
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
const plain = framework.folders.filter((folder) => !isCollection(folder));
|
|
69
|
+
if (plain.length > 0) {
|
|
70
|
+
print('', 'Folders');
|
|
71
|
+
for (const folder of plain) {
|
|
72
|
+
print(` ${folder.name} (${folder.type})${folder.description ? ` — ${folder.description}` : ''}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
print('', 'Properties');
|
|
76
|
+
print(` core: ${framework.schema.core.map((property) => `${property.name}${property.required ? ' (required)' : ''}`).join(', ')}`);
|
|
65
77
|
if (framework.schema.custom.length === 0) {
|
|
66
78
|
print(' custom: none');
|
|
67
79
|
}
|
|
@@ -69,7 +81,25 @@ export function runFramework(context, options) {
|
|
|
69
81
|
print(' custom:');
|
|
70
82
|
for (const property of framework.schema.custom) {
|
|
71
83
|
const scope = property.appliesTo === 'all' ? 'all' : property.appliesTo.join(', ');
|
|
72
|
-
|
|
84
|
+
const tools = property.tools ? ` [${Object.keys(property.tools).join(', ')}]` : '';
|
|
85
|
+
print(` ${property.name} (${property.type}, ${scope}${property.required ? ', required' : ''}) — ${property.meaning}${tools}`);
|
|
86
|
+
if (property.options)
|
|
87
|
+
print(` options: ${property.options.join(', ')}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const [tool, block] of Object.entries(framework.schema.tools)) {
|
|
91
|
+
print(` ${tool} (the tool's own block):`);
|
|
92
|
+
for (const property of block) {
|
|
93
|
+
const scope = property.appliesTo === 'all' ? 'all' : property.appliesTo.join(', ');
|
|
94
|
+
print(` ${property.name} (${property.type}, ${scope}${property.required ? ', required' : ''}) — ${property.meaning}`);
|
|
95
|
+
if (property.options)
|
|
96
|
+
print(` options: ${property.options.join(', ')}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (framework.vocabulary.length > 0) {
|
|
100
|
+
print('', 'Vocabulary');
|
|
101
|
+
for (const term of framework.vocabulary) {
|
|
102
|
+
print(` ${term.term}: ${term.means}${term.not.length > 0 ? ` (not: ${term.not.join('; ')})` : ''}`);
|
|
73
103
|
}
|
|
74
104
|
}
|
|
75
105
|
return EXIT_OK;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// `eidos index`: regenerate each collection's index
|
|
1
|
+
// `eidos index`: regenerate each collection's index in the framework document, or verify them with --check.
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { buildIndexes } from '../core/index-leaf.js';
|
|
4
4
|
import { findCollection } from '../core/framework.js';
|
|
@@ -9,13 +9,13 @@ export function runIndex(context, options) {
|
|
|
9
9
|
collections = options.collection.map((name) => {
|
|
10
10
|
const found = findCollection(context.framework, name);
|
|
11
11
|
if (!found) {
|
|
12
|
-
throw new CliError(`collection '${name}' is not declared in
|
|
12
|
+
throw new CliError(`collection '${name}' is not declared in the framework document`);
|
|
13
13
|
}
|
|
14
14
|
return found;
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
if (context.framework.collections.length === 0) {
|
|
18
|
-
throw new CliError('no collections declared in
|
|
18
|
+
throw new CliError('no collections declared in the framework document');
|
|
19
19
|
}
|
|
20
20
|
const results = buildIndexes(context.framework, context.blueprints, { collections, check: options.check });
|
|
21
21
|
const stale = results.some((result) => result.status === 'stale');
|
|
@@ -39,7 +39,7 @@ export function runIndex(context, options) {
|
|
|
39
39
|
const count = `${result.count} blueprint${result.count === 1 ? '' : 's'}`;
|
|
40
40
|
switch (result.status) {
|
|
41
41
|
case 'folder-missing':
|
|
42
|
-
printError(` ✗ ${result.collection.name}: folder not found (${path.
|
|
42
|
+
printError(` ✗ ${result.collection.name}: folder not found (${path.join(context.root, result.collection.name)})`);
|
|
43
43
|
break;
|
|
44
44
|
case 'stale':
|
|
45
45
|
print(` ✗ stale: ${relFile}`);
|
|
@@ -23,6 +23,7 @@ export function runInit(rootArg, options, cwd = process.cwd()) {
|
|
|
23
23
|
naming,
|
|
24
24
|
groups: options.group,
|
|
25
25
|
product: options.product ?? null,
|
|
26
|
+
framing: options.framing,
|
|
26
27
|
date: options.date,
|
|
27
28
|
dryRun: options.dryRun,
|
|
28
29
|
});
|
|
@@ -41,6 +42,7 @@ export function runInit(rootArg, options, cwd = process.cwd()) {
|
|
|
41
42
|
naming: result.naming,
|
|
42
43
|
framework_file: result.frameworkFile,
|
|
43
44
|
collections: result.collections,
|
|
45
|
+
folders: result.folders,
|
|
44
46
|
grouped_collection: result.groupedCollection,
|
|
45
47
|
groups: result.groups,
|
|
46
48
|
scaffolded: result.scaffolded,
|
|
@@ -58,6 +60,9 @@ export function runInit(rootArg, options, cwd = process.cwd()) {
|
|
|
58
60
|
collections += `; groups under ${result.groupedCollection}: ${result.groups.join(', ')}`;
|
|
59
61
|
}
|
|
60
62
|
print(collections);
|
|
63
|
+
if (result.folders.length > 0) {
|
|
64
|
+
print(` folders: ${result.folders.join(', ')}`);
|
|
65
|
+
}
|
|
61
66
|
if (result.scaffolded.length > 0) {
|
|
62
67
|
print(` blank frames: ${result.scaffolded.join(', ')}`);
|
|
63
68
|
}
|
|
@@ -8,7 +8,7 @@ export const GUIDES = [
|
|
|
8
8
|
{ name: 'overview', summary: 'Required first read before answering any request in a root' },
|
|
9
9
|
{ name: 'authoring', summary: 'How to place, scaffold, fill, and check a blueprint with the owner' },
|
|
10
10
|
{ name: 'validating', summary: 'How to run a check, read its findings, and report a review' },
|
|
11
|
-
{ name: 'configuring', summary: 'How to add a collection, a
|
|
11
|
+
{ name: 'configuring', summary: 'How to add a collection, a variant, or a property, and keep the Framework current' },
|
|
12
12
|
{ name: 'init-required', summary: 'How to create a root when there is none here' },
|
|
13
13
|
];
|
|
14
14
|
export function guideText(name) {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// `eidos list`: the blueprints in the root, filterable by collection, group,
|
|
2
|
-
//
|
|
3
|
-
import { blueprintId, blueprintTitle, propertyString,
|
|
2
|
+
// variant, or any property value.
|
|
3
|
+
import { blueprintId, blueprintTitle, propertyString, resolveVariant } from '../core/blueprint.js';
|
|
4
4
|
import { findCollection } from '../core/framework.js';
|
|
5
5
|
import { CliError, EXIT_OK, print, printJson, table } from '../output.js';
|
|
6
6
|
export function blueprintJson(blueprint) {
|
|
7
|
-
const {
|
|
7
|
+
const { variant, declared } = resolveVariant(blueprint);
|
|
8
8
|
return {
|
|
9
9
|
id: blueprintId(blueprint),
|
|
10
10
|
title: blueprintTitle(blueprint),
|
|
11
11
|
summary: propertyString(blueprint, 'summary'),
|
|
12
12
|
collection: blueprint.collection.name,
|
|
13
13
|
group: blueprint.group,
|
|
14
|
-
|
|
14
|
+
variant: variant?.name ?? declared,
|
|
15
15
|
path: blueprint.rel,
|
|
16
16
|
properties: blueprint.properties ?? {},
|
|
17
17
|
};
|
|
@@ -46,9 +46,9 @@ export function runList(context, collectionArg, options) {
|
|
|
46
46
|
const wanted = options.group.toLowerCase();
|
|
47
47
|
selected = selected.filter((blueprint) => blueprint.group?.toLowerCase() === wanted);
|
|
48
48
|
}
|
|
49
|
-
if (options.
|
|
50
|
-
const wanted = options.
|
|
51
|
-
selected = selected.filter((blueprint) =>
|
|
49
|
+
if (options.variant !== undefined) {
|
|
50
|
+
const wanted = options.variant.toLowerCase();
|
|
51
|
+
selected = selected.filter((blueprint) => resolveVariant(blueprint).variant?.name.toLowerCase() === wanted);
|
|
52
52
|
}
|
|
53
53
|
for (const clause of options.where) {
|
|
54
54
|
selected = selected.filter((blueprint) => matchesWhere(blueprint, clause));
|
|
@@ -65,7 +65,7 @@ export function runList(context, collectionArg, options) {
|
|
|
65
65
|
blueprintId(blueprint) ?? '(no id)',
|
|
66
66
|
blueprintTitle(blueprint),
|
|
67
67
|
blueprint.group === null ? blueprint.collection.name : `${blueprint.collection.name}/${blueprint.group}`,
|
|
68
|
-
|
|
68
|
+
resolveVariant(blueprint).variant?.name ?? '?',
|
|
69
69
|
]);
|
|
70
70
|
print(...table([['ID', 'TITLE', 'COLLECTION', 'FLAVOR'], ...rows]));
|
|
71
71
|
print('', `${selected.length} blueprint${selected.length === 1 ? '' : 's'}`);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// `eidos migrate`: move a root to the standard this CLI carries, mechanically,
|
|
2
|
+
// and say what moved.
|
|
3
|
+
import { findMigratableRoot, migrateRoot, MigrationError } from '../core/migrate.js';
|
|
4
|
+
import { CliError, EXIT_OK, print, printJson } from '../output.js';
|
|
5
|
+
export function runMigrate(options, cwd = process.cwd()) {
|
|
6
|
+
let report;
|
|
7
|
+
try {
|
|
8
|
+
report = migrateRoot(findMigratableRoot(cwd, options.root), { dryRun: options.dryRun });
|
|
9
|
+
}
|
|
10
|
+
catch (cause) {
|
|
11
|
+
if (cause instanceof MigrationError)
|
|
12
|
+
throw new CliError(cause.message);
|
|
13
|
+
throw cause;
|
|
14
|
+
}
|
|
15
|
+
if (options.json) {
|
|
16
|
+
printJson(report);
|
|
17
|
+
return EXIT_OK;
|
|
18
|
+
}
|
|
19
|
+
if (report.actions.length === 0) {
|
|
20
|
+
print(`${report.root} already targets Eidos ${report.to}; nothing to move`);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
print(`${options.dryRun ? 'would move' : 'moved'} ${report.root} from Eidos ${report.from ?? '?'} to ${report.to}:`);
|
|
24
|
+
for (const action of report.actions)
|
|
25
|
+
print(` ${options.dryRun ? '·' : '✓'} ${action}`);
|
|
26
|
+
}
|
|
27
|
+
for (const note of report.notes)
|
|
28
|
+
print(` ! ${note}`);
|
|
29
|
+
if (!options.dryRun && report.actions.length > 0)
|
|
30
|
+
print('', 'next: eidos check');
|
|
31
|
+
return EXIT_OK;
|
|
32
|
+
}
|
package/dist/src/commands/new.js
CHANGED
|
@@ -20,7 +20,7 @@ export function runNew(context, collection, titleWords, options) {
|
|
|
20
20
|
result = scaffoldBlueprint(context.framework, context.blueprints, {
|
|
21
21
|
collection,
|
|
22
22
|
title: titleWords.join(' '),
|
|
23
|
-
|
|
23
|
+
variant: options.variant ?? null,
|
|
24
24
|
group: options.group ?? null,
|
|
25
25
|
id: options.id ?? null,
|
|
26
26
|
summary: options.summary ?? null,
|
|
@@ -45,7 +45,7 @@ export function runNew(context, collection, titleWords, options) {
|
|
|
45
45
|
id: result.id,
|
|
46
46
|
collection: result.collection.name,
|
|
47
47
|
group: result.group,
|
|
48
|
-
|
|
48
|
+
variant: result.variant.name,
|
|
49
49
|
dry_run: options.dryRun,
|
|
50
50
|
notes: result.notes,
|
|
51
51
|
content: options.dryRun ? result.content : undefined,
|
|
@@ -56,7 +56,7 @@ export function runNew(context, collection, titleWords, options) {
|
|
|
56
56
|
print(`would write ${result.rel}:`, '', result.content);
|
|
57
57
|
}
|
|
58
58
|
else {
|
|
59
|
-
print(`✓ ${result.rel} (${result.collection.name}, ${result.
|
|
59
|
+
print(`✓ ${result.rel} (${result.collection.name}, ${result.variant.unit}.${result.variant.name}, id ${result.id})`);
|
|
60
60
|
}
|
|
61
61
|
for (const note of result.notes) {
|
|
62
62
|
print(` ! ${note}`);
|