eidosmd 0.1.0 → 0.2.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-C2NMN_D4.css +1 -0
- package/browser/dist/assets/index-C65k1ihb.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 +1 -1
- package/dist/src/commands/framework.js +24 -5
- package/dist/src/commands/index.js +4 -4
- package/dist/src/commands/init.js +1 -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/seeds.js +5 -5
- package/dist/src/commands/setup.js +119 -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 +722 -0
- package/dist/src/core/check.js +127 -51
- package/dist/src/core/convert.js +7 -6
- package/dist/src/core/framework-markdown.js +113 -34
- package/dist/src/core/framework-model.js +27 -10
- package/dist/src/core/framework-structured.js +132 -33
- package/dist/src/core/framework.js +13 -13
- package/dist/src/core/git.js +59 -0
- package/dist/src/core/index-leaf.js +2 -2
- package/dist/src/core/me.js +16 -8
- package/dist/src/core/migrate.js +243 -0
- package/dist/src/core/naming.js +1 -1
- package/dist/src/core/root.js +2 -2
- package/dist/src/core/scaffold.js +19 -19
- package/dist/src/core/seed.js +151 -60
- package/dist/src/core/server.js +1236 -52
- package/dist/src/core/settings.js +181 -0
- package/dist/src/core/store.js +259 -0
- package/dist/src/core/template.js +32 -0
- package/dist/src/core/versions.js +79 -0
- package/dist/src/output.js +4 -1
- package/dist/src/program.js +134 -39
- package/instructions/authoring.md +13 -12
- package/instructions/configuring.md +65 -34
- package/instructions/init-required.md +4 -4
- package/instructions/overview.md +17 -7
- package/instructions/validating.md +5 -4
- package/package.json +21 -12
- package/standard/EIDOS.md +129 -188
- package/standard/seeds/README.md +6 -6
- package/standard/seeds/book/Framework.yaml +81 -0
- package/standard/seeds/book/README.md +9 -5
- package/standard/seeds/book/_gitignore +3 -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 +81 -0
- package/standard/seeds/research/README.md +9 -5
- package/standard/seeds/research/_gitignore +3 -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 +82 -0
- package/standard/seeds/software/README.md +5 -5
- package/standard/seeds/software/_gitignore +3 -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
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
// The framework model: everything a framework document declares, whichever
|
|
2
|
-
// of
|
|
3
|
-
// convention, the Top-Level docs, the Collections (each with
|
|
4
|
-
//
|
|
2
|
+
// of `.eidos/Framework.md` or `.yaml` it is. The version and naming
|
|
3
|
+
// convention, the Top-Level docs, the Collections (each with variants and
|
|
4
|
+
// grouping), the Properties table (`schema` here, `properties` on disk), the
|
|
5
|
+
// Vocabulary, and the root's own Versions.
|
|
5
6
|
import { kebab } from './naming.js';
|
|
6
|
-
export const FRAMEWORK_DIR = '
|
|
7
|
+
export const FRAMEWORK_DIR = '.eidos';
|
|
8
|
+
// This tool's name, as the standard has a tool sign what it keeps in a
|
|
9
|
+
// framework: its folder under `.eidos/plugins/<name>/`, and its fields on a
|
|
10
|
+
// Properties row (a key on the YAML entry, a column in the markdown table).
|
|
11
|
+
export const TOOL_KEY = 'eidosmd';
|
|
12
|
+
export const PLUGINS_DIR = 'plugins';
|
|
7
13
|
export function isFrameworkFormat(value) {
|
|
8
14
|
return value === 'md' || value === 'yaml';
|
|
9
15
|
}
|
|
@@ -13,20 +19,31 @@ export const FRAMEWORK_FILES = [
|
|
|
13
19
|
{ name: 'Framework.yml', format: 'yaml' },
|
|
14
20
|
];
|
|
15
21
|
export const PROPERTY_TYPES = ['Text', 'List', 'Number', 'Checkbox', 'Date', 'Date & time'];
|
|
22
|
+
// The unit a collection speaks in, the word for one of its blueprints: what
|
|
23
|
+
// its template files are named for (`<unit>.<variant>.md`), read off the
|
|
24
|
+
// default variant. A collection whose unit is `frame` is the framing one, this
|
|
25
|
+
// CLI's recommendation for every product; the standard names no collection.
|
|
26
|
+
export function unitOf(collection) {
|
|
27
|
+
const variant = collection.variants.find((entry) => entry.isDefault) ?? collection.variants[0];
|
|
28
|
+
return variant && variant.unit !== '' ? variant.unit : null;
|
|
29
|
+
}
|
|
16
30
|
export class FrameworkError extends Error {
|
|
17
31
|
constructor(message) {
|
|
18
32
|
super(message);
|
|
19
33
|
this.name = 'FrameworkError';
|
|
20
34
|
}
|
|
21
35
|
}
|
|
22
|
-
// The standard's own
|
|
36
|
+
// The standard's own four (Eidos 5.0.0), used when a Framework.md carries no core block.
|
|
23
37
|
export const STANDARD_CORE = [
|
|
24
|
-
{ name: 'id', type: 'Text', appliesTo: 'all', meaning: 'Stable, unique,
|
|
25
|
-
{ name: 'title', type: 'Text', appliesTo: 'all', meaning: 'Human-readable name.', core: true },
|
|
26
|
-
{ name: 'summary', type: 'Text', appliesTo: 'all', meaning: 'One plain line: what this blueprint is. The source for the collection index.', core: true },
|
|
27
|
-
{ name: '
|
|
28
|
-
{ name: 'connects_to', type: 'List', appliesTo: 'all', meaning: 'Blueprints this one connects to on the canvas, each a link.', core: true },
|
|
38
|
+
{ name: 'id', type: 'Text', appliesTo: 'all', meaning: 'Stable, unique identity, in any form: a slug, a number, a GUID. Assigned once, never changed.', core: true, owner: 'eidos' },
|
|
39
|
+
{ name: 'title', type: 'Text', appliesTo: 'all', meaning: 'Human-readable name.', core: true, owner: 'eidos' },
|
|
40
|
+
{ name: 'summary', type: 'Text', appliesTo: 'all', meaning: 'One plain line: what this blueprint is. The source for the collection index.', core: true, owner: 'eidos' },
|
|
41
|
+
{ name: 'variant', type: 'Text', appliesTo: 'all', meaning: 'Which variant this blueprint follows. Absent = the collection default.', core: true, owner: 'eidos' },
|
|
29
42
|
];
|
|
43
|
+
// Every property of the Properties table, whichever block owns it, core first.
|
|
44
|
+
export function allProperties(schema) {
|
|
45
|
+
return [...schema.core, ...schema.custom, ...Object.values(schema.tools).flat()];
|
|
46
|
+
}
|
|
30
47
|
// `Domains` -> `domain`: the custom property that carries a collection's group,
|
|
31
48
|
// matched by the grouping label's singular against the properties scoped to it.
|
|
32
49
|
export function groupingProperty(collection, custom) {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
// The YAML framework document,
|
|
1
|
+
// The YAML framework document, `.eidos/Framework.yaml`: the same model as the
|
|
2
2
|
// markdown form, as fields in the frontmatter's snake_case, for a root that
|
|
3
3
|
// scripts and agents read.
|
|
4
|
-
// Paths are relative to
|
|
4
|
+
// Paths are relative to `.eidos/`, as the markdown links are. A structured
|
|
5
5
|
// document also carries the generated index under `index`, one list per
|
|
6
6
|
// collection, which `eidos index` rewrites in place without disturbing
|
|
7
7
|
// anything a person wrote around it.
|
|
8
8
|
import { Document, isMap, isPair, isScalar, parse as parseYaml, parseDocument } from 'yaml';
|
|
9
|
-
import { groupingProperty, STANDARD_CORE, } from './framework-model.js';
|
|
9
|
+
import { groupingProperty, STANDARD_CORE, TOOL_KEY, unitOf } from './framework-model.js';
|
|
10
10
|
import { DEFAULT_NAMING, isNaming } from './naming.js';
|
|
11
|
+
const STANDARD_PROPERTY_KEYS = new Set(['name', 'type', 'applies_to', 'meaning']);
|
|
11
12
|
function isPlain(value) {
|
|
12
13
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
14
|
}
|
|
@@ -51,23 +52,43 @@ function readCanvas(value, where, problems) {
|
|
|
51
52
|
problems.push(`${where}: canvas must be file, card, or { mode: card, section: <Section> }`);
|
|
52
53
|
return null;
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// `template` (`shape` before 5.0.0) is the file's path relative to `.eidos/`.
|
|
56
|
+
function readVariant(value, where, problems) {
|
|
57
|
+
const given = isPlain(value) ? text(value['template']) || text(value['shape']) : '';
|
|
58
|
+
if (!isPlain(value) || text(value['name']) === '' || given === '') {
|
|
59
|
+
problems.push(`${where}: every variant needs a name and a template`);
|
|
57
60
|
return null;
|
|
58
61
|
}
|
|
59
|
-
const
|
|
60
|
-
const file =
|
|
62
|
+
const template = given.replace(/\\/g, '/');
|
|
63
|
+
const file = template.split('/').pop() ?? template;
|
|
61
64
|
const stem = file.replace(/\.md$/i, '');
|
|
62
65
|
const dot = stem.indexOf('.');
|
|
63
66
|
return {
|
|
64
67
|
name: text(value['name']),
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
unit: dot === -1 ? stem : stem.slice(0, dot),
|
|
69
|
+
template,
|
|
67
70
|
description: text(value['description']),
|
|
68
71
|
isDefault: value['default'] === true,
|
|
69
72
|
};
|
|
70
73
|
}
|
|
74
|
+
// `vocabulary`: one entry per term; `not` is a list (a single string reads as one entry).
|
|
75
|
+
function readVocabulary(value, problems) {
|
|
76
|
+
const out = [];
|
|
77
|
+
list(value).forEach((entry, index) => {
|
|
78
|
+
if (!isPlain(entry) || text(entry['term']) === '') {
|
|
79
|
+
problems.push(`vocabulary[${index}]: a term needs a term`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const nots = Array.isArray(entry['not']) ? entry['not'] : entry['not'] === undefined || entry['not'] === null ? [] : [entry['not']];
|
|
83
|
+
out.push({
|
|
84
|
+
term: text(entry['term']),
|
|
85
|
+
means: text(entry['means']),
|
|
86
|
+
not: nots.map((item) => text(item)).filter((item) => item !== ''),
|
|
87
|
+
see: text(entry['see']) === '' ? null : text(entry['see']),
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
71
92
|
function readCollection(value, index, problems) {
|
|
72
93
|
const where = `collections[${index}]`;
|
|
73
94
|
if (!isPlain(value) || text(value['name']) === '') {
|
|
@@ -75,11 +96,11 @@ function readCollection(value, index, problems) {
|
|
|
75
96
|
return null;
|
|
76
97
|
}
|
|
77
98
|
const name = text(value['name']);
|
|
78
|
-
const
|
|
79
|
-
.map((
|
|
80
|
-
.filter((
|
|
81
|
-
if (
|
|
82
|
-
const first =
|
|
99
|
+
const variants = list(value['variants'] ?? value['flavors'])
|
|
100
|
+
.map((variant, position) => readVariant(variant, `${where} (${name}) variants[${position}]`, problems))
|
|
101
|
+
.filter((variant) => variant !== null);
|
|
102
|
+
if (variants.length > 0 && !variants.some((variant) => variant.isDefault)) {
|
|
103
|
+
const first = variants[0];
|
|
83
104
|
if (first) {
|
|
84
105
|
first.isDefault = true;
|
|
85
106
|
}
|
|
@@ -87,12 +108,13 @@ function readCollection(value, index, problems) {
|
|
|
87
108
|
const collection = {
|
|
88
109
|
name,
|
|
89
110
|
description: text(value['description']),
|
|
90
|
-
framing:
|
|
111
|
+
framing: false,
|
|
91
112
|
leaf: null,
|
|
92
|
-
|
|
113
|
+
variants,
|
|
93
114
|
canvas: readCanvas(value['canvas'], `${where} (${name})`, problems),
|
|
94
115
|
grouping: null,
|
|
95
116
|
};
|
|
117
|
+
collection.framing = unitOf(collection) === 'frame';
|
|
96
118
|
const grouping = value['grouping'];
|
|
97
119
|
if (grouping !== undefined && grouping !== null) {
|
|
98
120
|
if (!isPlain(grouping) || text(grouping['label']) === '') {
|
|
@@ -111,7 +133,7 @@ function readCollection(value, index, problems) {
|
|
|
111
133
|
}
|
|
112
134
|
return collection;
|
|
113
135
|
}
|
|
114
|
-
function readProperty(value, where, core, problems) {
|
|
136
|
+
function readProperty(value, where, core, problems, owner = core ? 'eidos' : 'custom') {
|
|
115
137
|
if (!isPlain(value) || text(value['name']) === '') {
|
|
116
138
|
problems.push(`${where}: a property needs a name`);
|
|
117
139
|
return null;
|
|
@@ -132,7 +154,54 @@ function readProperty(value, where, core, problems) {
|
|
|
132
154
|
problems.push(`${where}: applies_to must be all or a list of collections`);
|
|
133
155
|
}
|
|
134
156
|
}
|
|
135
|
-
|
|
157
|
+
const property = { name: text(value['name']), type: text(value['type']) || 'Text', appliesTo: applies, meaning: text(value['meaning']), core, owner };
|
|
158
|
+
const tools = {};
|
|
159
|
+
for (const [key, field] of Object.entries(value)) {
|
|
160
|
+
if (!STANDARD_PROPERTY_KEYS.has(key) && key !== 'canvas' && field !== undefined && field !== null) {
|
|
161
|
+
tools[key] = field;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (Object.keys(tools).length > 0) {
|
|
165
|
+
property.tools = tools;
|
|
166
|
+
}
|
|
167
|
+
// This tool's hint sits under its own key; a bare `canvas` (how the first
|
|
168
|
+
// canvases were written) still reads.
|
|
169
|
+
const own = isPlain(tools[TOOL_KEY]) ? tools[TOOL_KEY]['canvas'] : undefined;
|
|
170
|
+
const hint = readCanvasHint(own) ?? readCanvasHint(value['canvas']);
|
|
171
|
+
if (hint) {
|
|
172
|
+
property.canvas = hint;
|
|
173
|
+
}
|
|
174
|
+
return property;
|
|
175
|
+
}
|
|
176
|
+
function readStringMap(value) {
|
|
177
|
+
if (!isPlain(value)) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
const out = {};
|
|
181
|
+
for (const [key, item] of Object.entries(value)) {
|
|
182
|
+
if (typeof item === 'string' || typeof item === 'number') {
|
|
183
|
+
out[key] = String(item);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
187
|
+
}
|
|
188
|
+
export function readCanvasHint(value) {
|
|
189
|
+
if (!isPlain(value)) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const hint = {};
|
|
193
|
+
if (value['show'] === true) {
|
|
194
|
+
hint.show = true;
|
|
195
|
+
}
|
|
196
|
+
const shape = readStringMap(value['shape']);
|
|
197
|
+
if (shape) {
|
|
198
|
+
hint.shape = shape;
|
|
199
|
+
}
|
|
200
|
+
const color = readStringMap(value['color']);
|
|
201
|
+
if (color) {
|
|
202
|
+
hint.color = color;
|
|
203
|
+
}
|
|
204
|
+
return Object.keys(hint).length > 0 ? hint : null;
|
|
136
205
|
}
|
|
137
206
|
export function readEmbeddedIndex(value) {
|
|
138
207
|
if (!isPlain(value)) {
|
|
@@ -182,15 +251,23 @@ export function parseFrameworkStructured(source, root, file) {
|
|
|
182
251
|
.map((collection, index) => readCollection(collection, index, problems))
|
|
183
252
|
.filter((collection) => collection !== null);
|
|
184
253
|
if (!Array.isArray(document['collections']) && !error && isPlain(value)) {
|
|
185
|
-
problems.push('collections must be a list
|
|
254
|
+
problems.push('collections must be a list');
|
|
186
255
|
}
|
|
187
|
-
const schema = isPlain(document['schema']) ? document['schema'] : {};
|
|
256
|
+
const schema = isPlain(document['properties']) ? document['properties'] : isPlain(document['schema']) ? document['schema'] : {};
|
|
188
257
|
const core = list(schema['core'])
|
|
189
258
|
.map((property, index) => readProperty(property, `schema.core[${index}]`, true, problems))
|
|
190
259
|
.filter((property) => property !== null);
|
|
191
260
|
const custom = list(schema['custom'])
|
|
192
261
|
.map((property, index) => readProperty(property, `schema.custom[${index}]`, false, problems))
|
|
193
262
|
.filter((property) => property !== null);
|
|
263
|
+
const tools = {};
|
|
264
|
+
if (isPlain(schema['tools'])) {
|
|
265
|
+
for (const [tool, block] of Object.entries(schema['tools'])) {
|
|
266
|
+
tools[tool] = list(block)
|
|
267
|
+
.map((property, index) => readProperty(property, `schema.tools.${tool}[${index}]`, false, problems, tool))
|
|
268
|
+
.filter((property) => property !== null);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
194
271
|
for (const collection of collections) {
|
|
195
272
|
if (collection.grouping && collection.grouping.property === null) {
|
|
196
273
|
collection.grouping.property = groupingProperty(collection, custom);
|
|
@@ -209,7 +286,8 @@ export function parseFrameworkStructured(source, root, file) {
|
|
|
209
286
|
namingError,
|
|
210
287
|
topLevel,
|
|
211
288
|
collections,
|
|
212
|
-
schema: { core: core.length > 0 ? core : STANDARD_CORE, custom },
|
|
289
|
+
schema: { core: core.length > 0 ? core : STANDARD_CORE, custom, tools },
|
|
290
|
+
vocabulary: readVocabulary(document['vocabulary'], problems),
|
|
213
291
|
problems,
|
|
214
292
|
};
|
|
215
293
|
}
|
|
@@ -249,12 +327,12 @@ export function frameworkToDocument(framework) {
|
|
|
249
327
|
entry.canvas = { mode: 'card', section: collection.canvas.section };
|
|
250
328
|
}
|
|
251
329
|
}
|
|
252
|
-
entry.
|
|
253
|
-
const item = { name:
|
|
254
|
-
if (
|
|
255
|
-
item.description =
|
|
330
|
+
entry.variants = collection.variants.map((variant) => {
|
|
331
|
+
const item = { name: variant.name, template: variant.template };
|
|
332
|
+
if (variant.description) {
|
|
333
|
+
item.description = variant.description;
|
|
256
334
|
}
|
|
257
|
-
if (
|
|
335
|
+
if (variant.isDefault) {
|
|
258
336
|
item.default = true;
|
|
259
337
|
}
|
|
260
338
|
return item;
|
|
@@ -276,23 +354,44 @@ export function frameworkToDocument(framework) {
|
|
|
276
354
|
}
|
|
277
355
|
return entry;
|
|
278
356
|
});
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
357
|
+
const entry = ({ name, type, appliesTo: applies, meaning, canvas, tools }) => {
|
|
358
|
+
const fields = { ...(tools ?? {}) };
|
|
359
|
+
if (canvas) {
|
|
360
|
+
const own = isPlain(fields[TOOL_KEY]) ? fields[TOOL_KEY] : {};
|
|
361
|
+
fields[TOOL_KEY] = { ...own, canvas };
|
|
362
|
+
}
|
|
363
|
+
return { name, type, applies_to: applies, meaning, ...fields };
|
|
282
364
|
};
|
|
365
|
+
document.properties = {
|
|
366
|
+
core: framework.schema.core.map(({ name, type, meaning, tools }) => ({ name, type, meaning, ...(tools ?? {}) })),
|
|
367
|
+
custom: framework.schema.custom.map(entry),
|
|
368
|
+
};
|
|
369
|
+
if (Object.keys(framework.schema.tools).length > 0) {
|
|
370
|
+
document.properties.tools = Object.fromEntries(Object.entries(framework.schema.tools).map(([tool, block]) => [tool, block.map(entry)]));
|
|
371
|
+
}
|
|
372
|
+
if (framework.vocabulary.length > 0) {
|
|
373
|
+
document.vocabulary = framework.vocabulary.map((term) => {
|
|
374
|
+
const entry = { term: term.term, means: term.means, not: term.not };
|
|
375
|
+
if (term.see)
|
|
376
|
+
entry.see = term.see;
|
|
377
|
+
return entry;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
283
380
|
return document;
|
|
284
381
|
}
|
|
285
382
|
const YAML_GUIDANCE = {
|
|
286
383
|
eidos_version: ' The Eidos version this framework targets; migrate reads and bumps it.',
|
|
287
384
|
naming: ' How files, folders, and links are named: kebab-case | TitleCase | Title Case. Settled once.',
|
|
288
385
|
top_level: ' The top-level documents, README first: one entry per one-of-a-kind doc (a Roadmap, a Vision).',
|
|
289
|
-
collections: " The collections
|
|
290
|
-
|
|
386
|
+
collections: " The collections. Each declares its variants (one default; each a template file named <unit>.<variant>.md under templates/, the unit being the word for one of its blueprints) and its grouping.\n Add a group under `grouping.groups` with a one-line description; add a variant with a template file in templates/.",
|
|
387
|
+
properties: " The Properties table, one block per owner. `core` is the standard's and moves with eidos_version; `custom` is yours: name, type (Text | List | Number | Checkbox | Date | Date & time), applies_to (all or a list), meaning; `tools.<tool>` is a block a tool declared and alone writes.\n A key past those four on a row is a tool's, named for the tool; `eidosmd.canvas` is how this CLI styles a canvas node by the property's value.",
|
|
388
|
+
vocabulary: " The root's own terms, one entry each: term, means (one line), not (the near-misses, each with why it is a different thing), and see (the blueprint that defines it in full) when one does. Absent = none declared.",
|
|
389
|
+
versions: " Snapshots of the root, taken on purpose, newest first: version (the root's own number), commit (the sha that is the snapshot), tag (blueprints/<version>, when made). Absent = none recorded, the normal state.",
|
|
291
390
|
index: ' Generated by `eidos index`; never hand-edited. One list per collection, in the order the markdown index would use.',
|
|
292
391
|
};
|
|
293
392
|
// A YAML document a person will edit gets the guidance the markdown seed
|
|
294
393
|
// keeps as prose, as comments above each top-level key.
|
|
295
|
-
function annotateYaml(document) {
|
|
394
|
+
export function annotateYaml(document) {
|
|
296
395
|
if (!isMap(document.contents)) {
|
|
297
396
|
return;
|
|
298
397
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Finding and loading the root's framework document
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Finding and loading the root's framework document, `.eidos/Framework.yaml`,
|
|
2
|
+
// parsed into the one model every command reads. A 4.x root's Framework.md
|
|
3
|
+
// is found and parsed too, only so `migrate` can move it.
|
|
4
4
|
import { existsSync, readFileSync } from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { parseFrameworkMarkdown } from './framework-markdown.js';
|
|
@@ -8,7 +8,7 @@ import { parseFrameworkStructured } from './framework-structured.js';
|
|
|
8
8
|
import { appliesTo, FRAMEWORK_DIR, FRAMEWORK_FILES, FrameworkError } from './framework-model.js';
|
|
9
9
|
import { kebab } from './naming.js';
|
|
10
10
|
export * from './framework-model.js';
|
|
11
|
-
// The one framework document in a folder's
|
|
11
|
+
// The one framework document in a folder's `.eidos/`, or null; two is an error.
|
|
12
12
|
export function findFrameworkFile(dir) {
|
|
13
13
|
const present = FRAMEWORK_FILES.map((candidate) => ({ file: path.join(dir, FRAMEWORK_DIR, candidate.name), format: candidate.format })).filter((candidate) => existsSync(candidate.file));
|
|
14
14
|
if (present.length > 1) {
|
|
@@ -28,7 +28,7 @@ export function parseFramework(text, format, root, file) {
|
|
|
28
28
|
export function loadFramework(root) {
|
|
29
29
|
const found = findFrameworkFile(root);
|
|
30
30
|
if (!found) {
|
|
31
|
-
throw new FrameworkError(`${path.join(root, FRAMEWORK_DIR)} holds no framework document (Framework.
|
|
31
|
+
throw new FrameworkError(`${path.join(root, FRAMEWORK_DIR)} holds no framework document (Framework.yaml)`);
|
|
32
32
|
}
|
|
33
33
|
return parseFramework(readFileSync(found.file, 'utf8'), found.format, root, found.file);
|
|
34
34
|
}
|
|
@@ -38,19 +38,19 @@ export function findCollection(framework, name) {
|
|
|
38
38
|
framework.collections.find((collection) => kebab(collection.name) === kebab(name)) ??
|
|
39
39
|
null);
|
|
40
40
|
}
|
|
41
|
-
export function
|
|
41
|
+
export function findVariant(collection, name) {
|
|
42
42
|
const wanted = name.trim().toLowerCase();
|
|
43
|
-
return (collection.
|
|
44
|
-
collection.
|
|
43
|
+
return (collection.variants.find((variant) => variant.name.toLowerCase() === wanted) ??
|
|
44
|
+
collection.variants.find((variant) => kebab(variant.name) === kebab(name)) ??
|
|
45
45
|
null);
|
|
46
46
|
}
|
|
47
|
-
export function
|
|
48
|
-
return collection.
|
|
47
|
+
export function defaultVariant(collection) {
|
|
48
|
+
return collection.variants.find((variant) => variant.isDefault) ?? collection.variants[0] ?? null;
|
|
49
49
|
}
|
|
50
50
|
// The properties a blueprint in this collection carries, core first.
|
|
51
51
|
export function propertiesFor(framework, collectionName) {
|
|
52
|
-
return [...framework.schema.core, ...framework.schema.custom.filter((property) => appliesTo(property, collectionName))];
|
|
52
|
+
return [...framework.schema.core, ...[...framework.schema.custom, ...Object.values(framework.schema.tools).flat()].filter((property) => appliesTo(property, collectionName))];
|
|
53
53
|
}
|
|
54
|
-
export function
|
|
55
|
-
return path.join(framework.root, FRAMEWORK_DIR,
|
|
54
|
+
export function templatePath(framework, variant) {
|
|
55
|
+
return path.join(framework.root, FRAMEWORK_DIR, variant.template);
|
|
56
56
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// The little git the CLI needs: the commit a root sits at, whether a commit
|
|
2
|
+
// exists, and a tag on one. Every call is read-only except `gitTag`, and
|
|
3
|
+
// every one answers null or false rather than throwing where git is absent
|
|
4
|
+
// or the root is not in a repository.
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
function git(root, args) {
|
|
8
|
+
try {
|
|
9
|
+
return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || null;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function gitHead(root) {
|
|
16
|
+
return git(root, ['rev-parse', 'HEAD']);
|
|
17
|
+
}
|
|
18
|
+
// The full sha of a commit named any way git accepts (a short sha, HEAD, a
|
|
19
|
+
// tag), or null when nothing by that name is a commit here.
|
|
20
|
+
export function gitResolve(root, ref) {
|
|
21
|
+
return git(root, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
|
|
22
|
+
}
|
|
23
|
+
export function gitTagExists(root, tag) {
|
|
24
|
+
return git(root, ['rev-parse', '--verify', '--quiet', `refs/tags/${tag}`]) !== null;
|
|
25
|
+
}
|
|
26
|
+
export function gitTag(root, tag, sha) {
|
|
27
|
+
return git(root, ['tag', tag, sha]) !== null || gitTagExists(root, tag);
|
|
28
|
+
}
|
|
29
|
+
// Who git says is committing here: the repository's configured user, falling
|
|
30
|
+
// back to the global one the way git itself does. Null outside a repository
|
|
31
|
+
// or where no name is set.
|
|
32
|
+
export function gitUser(root) {
|
|
33
|
+
if (gitHead(root) === null && git(root, ['rev-parse', '--git-dir']) === null)
|
|
34
|
+
return null;
|
|
35
|
+
const name = git(root, ['config', 'user.name']);
|
|
36
|
+
if (name === null)
|
|
37
|
+
return null;
|
|
38
|
+
return { name, email: git(root, ['config', 'user.email']) };
|
|
39
|
+
}
|
|
40
|
+
// The commits that touched one file, newest first, following renames.
|
|
41
|
+
export function gitLog(root, file, limit = 50) {
|
|
42
|
+
const out = git(root, ['log', `--max-count=${limit}`, '--follow', '--format=%H%x1f%h%x1f%an%x1f%ae%x1f%aI%x1f%s', '--', file]);
|
|
43
|
+
if (out === null)
|
|
44
|
+
return [];
|
|
45
|
+
return out
|
|
46
|
+
.split('\n')
|
|
47
|
+
.map((line) => line.split('\x1f'))
|
|
48
|
+
.filter((parts) => parts.length === 6)
|
|
49
|
+
.map(([sha, short, author, email, date, subject]) => ({ sha: sha ?? '', short: short ?? '', author: author ?? '', email: email ?? '', date: date ?? '', subject: subject ?? '' }));
|
|
50
|
+
}
|
|
51
|
+
// The file as one commit held it, or null when that commit has no such file.
|
|
52
|
+
export function gitShowFile(root, sha, file) {
|
|
53
|
+
try {
|
|
54
|
+
return execFileSync('git', ['show', `${sha}:${file.split(path.sep).join('/')}`], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// `summary`, kept inside the YAML framework document under `index`, one list
|
|
3
3
|
// per collection, regenerated wholesale. `eidos index` rewrites that key and
|
|
4
4
|
// nothing else in the document, so the comments and layout a person chose
|
|
5
|
-
// survive. (A markdown root
|
|
6
|
-
//
|
|
5
|
+
// survive. (A 4.x markdown root kept a per-collection `index.md` instead;
|
|
6
|
+
// `migrate` converts such a root.)
|
|
7
7
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { blueprintId, blueprintTitle, propertyString } from './blueprint.js';
|
package/dist/src/core/me.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// The actor:
|
|
2
|
-
//
|
|
1
|
+
// The actor: `.eidos/me.md`, personal and gitignored, naming a role from
|
|
2
|
+
// `.eidos/roles/` and calibrating it on three axes.
|
|
3
3
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { headings, section } from './markdown.js';
|
|
6
6
|
import { kebab } from './naming.js';
|
|
7
7
|
export function rolesDir(root) {
|
|
8
|
-
return path.join(root, '
|
|
8
|
+
return path.join(root, '.eidos', 'roles');
|
|
9
9
|
}
|
|
10
10
|
export function meFile(root) {
|
|
11
|
-
return path.join(root, '
|
|
11
|
+
return path.join(root, '.eidos', 'me.md');
|
|
12
12
|
}
|
|
13
13
|
export function listRoles(root) {
|
|
14
14
|
const dir = rolesDir(root);
|
|
@@ -27,6 +27,11 @@ export function listRoles(root) {
|
|
|
27
27
|
return { name: entry.replace(/\.md$/i, ''), file, title, summary };
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
|
+
// A role's file name is its kebab-case name, one level under roles/.
|
|
31
|
+
export const isRoleName = (name) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name);
|
|
32
|
+
export function roleFile(root, name) {
|
|
33
|
+
return path.join(rolesDir(root), `${name}.md`);
|
|
34
|
+
}
|
|
30
35
|
export function findRole(root, name) {
|
|
31
36
|
const wanted = kebab(name);
|
|
32
37
|
return listRoles(root).find((role) => role.name === wanted || kebab(role.title) === wanted) ?? null;
|
|
@@ -65,13 +70,16 @@ export function parseActor(text) {
|
|
|
65
70
|
actor.capacity = axis('Technical capacity');
|
|
66
71
|
return actor;
|
|
67
72
|
}
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
// `note` is anything the person adds under their role, in their own words.
|
|
74
|
+
export function renderActor(role, custom, axes, note = null) {
|
|
75
|
+
const lines = ['# Me', '', 'Personal and per-actor, gitignored, never shared. The agent reads it before acting.', ''];
|
|
70
76
|
if (role) {
|
|
71
|
-
lines.push(`## You are: ${role.title}`, '', `Role: [
|
|
77
|
+
lines.push(`## You are: ${role.title}`, '', `Role: [.eidos/roles/${role.name}.md](roles/${role.name}.md).`, '');
|
|
78
|
+
if (note)
|
|
79
|
+
lines.push(note, '');
|
|
72
80
|
}
|
|
73
81
|
else if (custom) {
|
|
74
|
-
lines.push(`## You are: ${custom}`, '', 'A custom role: describe how you want to be helped here.', '');
|
|
82
|
+
lines.push(`## You are: ${custom}`, '', note ?? 'A custom role: describe how you want to be helped here.', '');
|
|
75
83
|
}
|
|
76
84
|
else {
|
|
77
85
|
lines.push('## You are:', '', '<!-- Name your role, or pick one with `eidos whoami --role <role>`. -->', '');
|