dreamteamer 0.20.0 → 0.22.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/bin/dt-hook.sh +54 -0
- package/collections/collections.collection.yaml +9 -0
- package/collections/command-bindings.collection.yaml +1 -1
- package/collections/proofs.collection.yaml +96 -0
- package/collections/repos.collection.yaml +4 -3
- package/package.json +2 -2
- package/skills/using-dreamteamer/SKILL.md +16 -7
- package/skills/using-dreamteamer/references/before-you-build.md +16 -2
- package/skills/using-dreamteamer/references/collections.md +50 -9
- package/skills/using-dreamteamer/references/commands.md +16 -3
- package/skills/using-dreamteamer/references/data-modeling.md +2 -2
- package/skills/using-dreamteamer/references/exporting.md +53 -0
- package/skills/using-dreamteamer/references/proofs.md +435 -0
- package/skills/using-dreamteamer/references/skills.md +8 -0
- package/skills/using-dreamteamer/references/worktrees.md +235 -0
- package/src/checkout.js +666 -0
- package/src/cli.js +301 -40
- package/src/collections-cli.js +237 -63
- package/src/commit.js +1 -1
- package/src/compile.js +190 -15
- package/src/export-notebooklm.js +502 -0
- package/src/filter.js +4 -1
- package/src/harnesses.js +25 -10
- package/src/init.js +2 -0
- package/src/land.js +736 -0
- package/src/presentation.js +28 -1
- package/src/prove.js +1922 -0
- package/src/runtime.js +26 -0
- package/src/schema-ops.js +48 -25
package/src/compile.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// dreamteamer compile — materialize (modules × workspace sources) into .dreamteamer,
|
|
2
2
|
// the single runtime read surface: copies + provenance manifest, then harness adapters.
|
|
3
3
|
// explicit only; nothing rebuilds implicitly.
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
4
5
|
import { createHash } from 'node:crypto';
|
|
5
6
|
import fs from 'node:fs';
|
|
6
7
|
import path from 'node:path';
|
|
@@ -18,7 +19,13 @@ import {
|
|
|
18
19
|
import { runHarnessAdapters } from './harnesses.js';
|
|
19
20
|
import { satisfies } from './semver.js';
|
|
20
21
|
import { parseEnvValues } from './env-vars.js';
|
|
21
|
-
import { DERIVED_KINDS, readManifest, runtimeDir } from './runtime.js';
|
|
22
|
+
import { DERIVED_KINDS, readManifest, runtimeDir, engineId, engineVersion } from './runtime.js';
|
|
23
|
+
// ⚠ RE-EXPORTED, NOT RE-IMPLEMENTED. `engineVersion` moved to the boundary layer so `prove` can
|
|
24
|
+
// stamp a ledger row without importing the compiler (that edge was a real, if latent, cycle).
|
|
25
|
+
// Every existing caller spells it `from './compile.js'`, and a second reader of the engine's own
|
|
26
|
+
// package.json is exactly the drift this file's comments keep naming.
|
|
27
|
+
export { engineId, engineVersion };
|
|
28
|
+
import { artifactRefs, proofPathFor, validateProofShape, stepWarnings } from './prove.js';
|
|
22
29
|
|
|
23
30
|
// re-exported, not moved: `readManifest` is in the VS Code extension's hand-maintained engine
|
|
24
31
|
// contract as `compileMod.readManifest` (engine.ts), and a removed export is the same cross-repo
|
|
@@ -382,8 +389,23 @@ function stampMirror(byName, ctx, ownerName, field, prop, holder, mirrorName, ta
|
|
|
382
389
|
t.schema.properties = { ...t.schema.properties, [mirrorName]: generated };
|
|
383
390
|
}
|
|
384
391
|
|
|
385
|
-
export const KINDS = ['collections', 'skills', 'agents', 'commands', 'command-bindings', 'ui-views', 'collection-templates'];
|
|
392
|
+
export const KINDS = ['collections', 'skills', 'agents', 'commands', 'command-bindings', 'ui-views', 'collection-templates', 'proofs'];
|
|
386
393
|
const FOLDER_KINDS = new Set(['skills']); // folder-shape entities: copy the whole record folder
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* ⚠ `proofs/fixtures/` IS RECORDS, NOT PROOFS. It holds the store a `writes` proof runs against —
|
|
397
|
+
* laid onto a throwaway worktree by `dt prove`, mirroring the workspace root — so it is not a
|
|
398
|
+
* compiled source at all.
|
|
399
|
+
*
|
|
400
|
+
* ONE predicate, because BOTH enumerations of a kind directory have to agree about it and they are
|
|
401
|
+
* 900 lines apart. The stager alone made compile read `data/notes/x.note.md` as a proof and refuse
|
|
402
|
+
* the whole module; the staleness scan alone then reported every fixture file "(new, uncompiled)"
|
|
403
|
+
* on every single command, for ever, with no compile able to clear it.
|
|
404
|
+
*
|
|
405
|
+
* @param {string} kind the source kind being enumerated
|
|
406
|
+
* @param {string} relFromKind the entry's path RELATIVE to the kind directory, '/'-separated
|
|
407
|
+
*/
|
|
408
|
+
const isProofFixture = (kind, relFromKind) => kind === 'proofs' && (relFromKind === 'fixtures' || relFromKind.startsWith('fixtures/'));
|
|
387
409
|
// DERIVED_KINDS (projected, not staged) lives in runtime.js — the boundary both halves read. Not in
|
|
388
410
|
// KINDS on purpose: a module folder named `modules/` would be nonsense, and `isSystem` below keys
|
|
389
411
|
// off KINDS to decide `storage.base`, so a `modules` collection landing on `base: workspace` would
|
|
@@ -477,6 +499,11 @@ function strayKindDirs(source, wsRoot, declaredIgnore) {
|
|
|
477
499
|
|
|
478
500
|
const sha256 = (buf) => 'sha256:' + createHash('sha256').update(buf).digest('hex');
|
|
479
501
|
|
|
502
|
+
/** Is this workspace-relative source path inside an installed package? A nudge to write a file
|
|
503
|
+
* there would name a path the next `npm install` erases. (schema-ops has the same one-liner for the
|
|
504
|
+
* same reason; importing it back here would close a cycle for one regex.) */
|
|
505
|
+
const inNodeModules = (p) => /(^|[\\/])node_modules([\\/]|$)/.test(String(p));
|
|
506
|
+
|
|
480
507
|
// channel -> the directory the operator knows it by (used in shadow warnings, and it IS the
|
|
481
508
|
// `location` field's vocabulary — see collections/modules.collection.yaml. Keeping the export name
|
|
482
509
|
// on purpose: renaming a symbol under src/ is a cross-repo change, and this one buys nothing.)
|
|
@@ -636,6 +663,7 @@ export function compile({ root, pkg }) {
|
|
|
636
663
|
const moduleDeps = new Map(); // module name -> [module names] — HARD, must be acyclic
|
|
637
664
|
const modulePeers = new Map(); // module name -> [collection names] — SOFT, cannot cycle
|
|
638
665
|
const moduleNamespaces = new Map(); // module name -> [namespaces it DECLARES] (§8, option A)
|
|
666
|
+
const moduleLocalAssets = []; // {rel, owner, base} — validated with the workspace's own, below
|
|
639
667
|
for (const source of sources) {
|
|
640
668
|
let mpkg;
|
|
641
669
|
try { mpkg = JSON.parse(fs.readFileSync(path.join(source.root, 'package.json'), 'utf8')); } catch { continue; }
|
|
@@ -675,6 +703,13 @@ export function compile({ root, pkg }) {
|
|
|
675
703
|
if (!declaredEnv.has(k)) declaredEnv.set(k, []);
|
|
676
704
|
declaredEnv.get(k).push(source.name);
|
|
677
705
|
}
|
|
706
|
+
// Gathered here because mpkg is already parsed; refused below, next to the workspace's own
|
|
707
|
+
// declaration. The classic layout pushes the ROOT itself as an inline source, whose
|
|
708
|
+
// package.json IS `config` — reading it here too would report every workspace-level
|
|
709
|
+
// declaration twice, under the wrong owner.
|
|
710
|
+
if (source.root !== root) {
|
|
711
|
+
for (const rel of mpkg.dreamteamer?.['local-assets'] ?? []) moduleLocalAssets.push({ rel, owner: source.name, base: source.root });
|
|
712
|
+
}
|
|
678
713
|
}
|
|
679
714
|
// `dreamteamer.vars` is the WORKSPACE's own declaration (root package.json, not a module's): the
|
|
680
715
|
// keys a `${env:NAME}` template is allowed to name. Same missing-key question as
|
|
@@ -714,6 +749,33 @@ export function compile({ root, pkg }) {
|
|
|
714
749
|
}
|
|
715
750
|
}
|
|
716
751
|
|
|
752
|
+
// ---- local-assets and postinstall: what `dt install` will do to a checkout -------
|
|
753
|
+
// `local-assets` are the gitignored heavy folders a checkout SHARES by symlink instead of
|
|
754
|
+
// duplicating — a browser profile dir, a model cache. Declared, never discovered. Every
|
|
755
|
+
// refusal below is something the installer would otherwise hit at RUN time, on a machine nobody
|
|
756
|
+
// is watching; the compiler is where a declaration is cheap to fix. (`src/checkout.js` refuses
|
|
757
|
+
// the escaping rel a second time when the plan is built — a runtime that writes a symlink
|
|
758
|
+
// outside the root must not wait for the compiler to be run.)
|
|
759
|
+
const ENGINE_OWNED = new Set(['.env', 'node_modules', '.dreamteamer', '.git']);
|
|
760
|
+
const gitQ = (args) => { try { execFileSync('git', args, { cwd: root, stdio: 'ignore' }); return true; } catch { return false; } };
|
|
761
|
+
for (const a of [...(config['local-assets'] ?? []).map((rel) => ({ rel, owner: 'workspace', base: root })), ...moduleLocalAssets]) {
|
|
762
|
+
const abs = path.resolve(a.base, a.rel), rel = path.relative(root, abs);
|
|
763
|
+
if (a.owner !== 'workspace' && path.relative(a.base, abs).startsWith('..')) fail(`local-assets: "${a.rel}" (module ${a.owner}) escapes its module with .. — declare it at the workspace level instead`);
|
|
764
|
+
// The workspace-level twin, and it earns its own line: a rel that climbs out of the ROOT
|
|
765
|
+
// otherwise reached the gitignore check below, which cannot see a path outside the repo and
|
|
766
|
+
// so told the operator to ignore something git will never match. `checkout.js` refuses the
|
|
767
|
+
// same shape when the plan is built.
|
|
768
|
+
if (rel.startsWith('..')) fail(`local-assets: "${a.rel}" escapes the workspace root with .. — a local asset must be a path INSIDE the workspace`);
|
|
769
|
+
if (ENGINE_OWNED.has(rel.split(path.sep)[0])) fail(`local-assets: "${rel}" is engine-owned — install links .env and installs node_modules itself; never declare them`);
|
|
770
|
+
if (gitQ(['ls-files', '--error-unmatch', rel])) fail(`local-assets: "${rel}" is tracked — a tracked path needs no link and a link would shadow it`);
|
|
771
|
+
// ⚠ WITHOUT a trailing slash, and the message has to say so: a dir-only pattern (`.profiles/`)
|
|
772
|
+
// matches neither a symlink nor a path that does not exist yet (measured, git 2.50) — and a
|
|
773
|
+
// shared asset is exactly those two shapes, a symlink in every non-primary checkout and
|
|
774
|
+
// absent before the first install.
|
|
775
|
+
if (!gitQ(['check-ignore', '-q', rel])) fail(`local-assets: "${rel}" is not gitignored — add it to .gitignore WITHOUT a trailing slash (a worktree holds it as a symlink, and a dir-only pattern ignores neither a symlink nor an absent path); a shared asset must never be committed`);
|
|
776
|
+
}
|
|
777
|
+
if (config.postinstall != null && typeof config.postinstall !== 'string') fail('dreamteamer.postinstall must be a single shell string');
|
|
778
|
+
|
|
717
779
|
// ---- the module dependency graph -------------------------------------------------
|
|
718
780
|
// `dependencies` names MODULES and must be acyclic. `peerDependencies` names COLLECTIONS and
|
|
719
781
|
// therefore cannot cycle at all — which is the whole reason it exists: two modules that each
|
|
@@ -808,6 +870,7 @@ export function compile({ root, pkg }) {
|
|
|
808
870
|
: fs.readdirSync(srcDir).sort();
|
|
809
871
|
for (const name of names) {
|
|
810
872
|
if (name.startsWith('.')) continue;
|
|
873
|
+
if (isProofFixture(kind, name)) continue;
|
|
811
874
|
const entityId = name.replace(/\.[^.]+\.(yaml|md|json)$/, '');
|
|
812
875
|
if (disabled.has(`${source.name}/${entityId}`)) { disabledHits.add(`${source.name}/${entityId}`); continue; }
|
|
813
876
|
const srcPath = path.join(srcDir, name);
|
|
@@ -899,7 +962,7 @@ export function compile({ root, pkg }) {
|
|
|
899
962
|
// NOTHING. Warn; do not fail, since a module that is temporarily source-free is the
|
|
900
963
|
// operator's business, not the compiler's. Runs AFTER UI staging so a UI-only module counts.
|
|
901
964
|
// ⚠ A module with a scaffolded-but-EMPTY kind folder is a module being AUTHORED, not a mistake.
|
|
902
|
-
// `add modules` creates exactly that shape —
|
|
965
|
+
// `add modules` creates exactly that shape — eight empty kind folders — and a verb whose own
|
|
903
966
|
// output triggers a warning reads as a broken install. The warning's remaining job is the case it
|
|
904
967
|
// was written for: a module that ships nothing the engine recognises AT ALL, which is what
|
|
905
968
|
// decision 156 cost two days. `kindDir` returns the flat path when neither layout exists, so this
|
|
@@ -1313,7 +1376,26 @@ export function compile({ root, pkg }) {
|
|
|
1313
1376
|
// generated mirror (which never carries x-unique) cannot trip it.
|
|
1314
1377
|
const h = (prop.items && typeof prop.items === 'object') ? prop.items : prop;
|
|
1315
1378
|
if (h['x-unique'] === true && h['x-inverse'] === undefined && h['x-inverse-of'] === undefined) {
|
|
1316
|
-
console.warn(`⚠ collection ${name}: x-unique on "${fieldName}" is inert — it is a RELATION keyword, enforced only while the store maintains a mirror, and this field declares no x-inverse. Nothing constrains the value. Declare the relation (dreamteamer
|
|
1379
|
+
console.warn(`⚠ collection ${name}: x-unique on "${fieldName}" is inert — it is a RELATION keyword, enforced only while the store maintains a mirror, and this field declares no x-inverse. Nothing constrains the value. Declare the relation (dreamteamer set-field ${name} --name ${fieldName} --inverse) or drop x-unique.`);
|
|
1380
|
+
}
|
|
1381
|
+
// `x-choices` decorates ENUM VALUES (presentation.js#choiceRow, 0.21.0), and both ways of
|
|
1382
|
+
// getting it wrong are SILENT: a key that is not a value decorates nothing, and the keyword
|
|
1383
|
+
// on a non-enum field is read by no one at all. Either way the author sees no error and no
|
|
1384
|
+
// decoration — on a surface they are probably not looking at while editing the descriptor.
|
|
1385
|
+
// ⚠ WARNINGS, not failures, for the same reason as x-unique directly above: neither breaks
|
|
1386
|
+
// anything today, and a descriptor mid-edit must stay compilable.
|
|
1387
|
+
const choices = h['x-choices'];
|
|
1388
|
+
if (choices && typeof choices === 'object' && !Array.isArray(choices)) {
|
|
1389
|
+
const values = Array.isArray(h.enum) ? h.enum.map(String) : null;
|
|
1390
|
+
if (!values) {
|
|
1391
|
+
console.warn(`⚠ collection ${name}: x-choices on "${fieldName}" is inert — it decorates the values of an enum, and this field declares no enum. Nothing reads it.`);
|
|
1392
|
+
} else {
|
|
1393
|
+
// Named per offending key rather than "some keys are wrong", and the legal values are
|
|
1394
|
+
// quoted so the fix needs no second lookup — the same shape as every other warning here.
|
|
1395
|
+
for (const k of Object.keys(choices)) {
|
|
1396
|
+
if (!values.includes(k)) console.warn(`⚠ collection ${name}: x-choices on "${fieldName}" has an entry for "${k}", which is not one of its enum values (${values.join(', ')}) — it decorates nothing. Fix the spelling, or drop the entry.`);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1317
1399
|
}
|
|
1318
1400
|
}
|
|
1319
1401
|
const rt = path.join('collections', `${name}.collection.yaml`);
|
|
@@ -1514,6 +1596,46 @@ export function compile({ root, pkg }) {
|
|
|
1514
1596
|
}
|
|
1515
1597
|
}
|
|
1516
1598
|
|
|
1599
|
+
// ---- proof validation --------------------------------------------------------------
|
|
1600
|
+
// A proof declares behaviour the ENGINE judges, so every key in one is a value the engine
|
|
1601
|
+
// interprets — which by the rule stated above the ui-view block makes all of them compile's to
|
|
1602
|
+
// validate. The two silent failures this refuses, both measured in the spike:
|
|
1603
|
+
// `about: skills/greter` — names no artifact, so the proof proves nothing and says so nowhere.
|
|
1604
|
+
// `where: { statuz: … }` — an unknown key fails CLOSED in `matchesFilter`, so a `count`
|
|
1605
|
+
// expectation over it passes or fails FOREVER for a reason no output names.
|
|
1606
|
+
// The judgement itself lives in `prove.js` (unit-tested against a hand-built descriptor set);
|
|
1607
|
+
// this block is the wiring, and it is deliberately paid for only when a proof exists.
|
|
1608
|
+
//
|
|
1609
|
+
// ⚠ `artifactRefs` is computed unconditionally, because the coverage line below prints its
|
|
1610
|
+
// denominators on EVERY compile — a workspace with no proofs still gets told what it has.
|
|
1611
|
+
const proofArtifacts = artifactRefs(entries);
|
|
1612
|
+
const proofEntries = [...entries].filter(([rt]) => rt.startsWith('proofs/'));
|
|
1613
|
+
/** every artifact ref named by at least one proof — the coverage numerators, and the nudge's silencer */
|
|
1614
|
+
const provenRefs = new Set();
|
|
1615
|
+
if (proofEntries.length) {
|
|
1616
|
+
// the MERGED descriptors, read back out of the entries this compile is about to write —
|
|
1617
|
+
// the same source of truth the ui-view block reads its own field list from, so a proof is
|
|
1618
|
+
// judged against the schema the workspace will actually run on (`extends` applied).
|
|
1619
|
+
const merged = new Map();
|
|
1620
|
+
for (const [rt, e] of entries) {
|
|
1621
|
+
if (!rt.startsWith('collections/')) continue;
|
|
1622
|
+
const doc = load(e.bytes.toString('utf8'));
|
|
1623
|
+
if (doc?.name) merged.set(doc.name, doc);
|
|
1624
|
+
}
|
|
1625
|
+
const proofCtx = { descriptors: merged, declaredVars, moduleEnv: new Set(declaredEnv.keys()), artifacts: proofArtifacts.all };
|
|
1626
|
+
for (const [rt, e] of proofEntries) {
|
|
1627
|
+
const proof = loadSource(e.bytes.toString('utf8'), e.sources[0].path);
|
|
1628
|
+
const errs = validateProofShape(proof, proofCtx);
|
|
1629
|
+
if (errs.length) fail(errs.map((m) => `${rt}: ${m}`).join('\n '));
|
|
1630
|
+
// ⚠ WARNINGS, not errors (R17). A step is a shell string, so a brace nobody substitutes is
|
|
1631
|
+
// as likely to be `awk '{print}'` as a typo'd `{recrod}` — and `substitute` refusing both
|
|
1632
|
+
// at run time was measured to kill four correct steps. Naming the token here is the whole
|
|
1633
|
+
// net that remains: it costs a line, and the proof still compiles and still runs.
|
|
1634
|
+
for (const w of stepWarnings(proof)) console.warn(`⚠ ${rt}: ${w}`);
|
|
1635
|
+
for (const ref of proof?.about ?? []) provenRefs.add(String(ref));
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1517
1639
|
// ---- materialize .dreamteamer ------------------------------------------------
|
|
1518
1640
|
// mkdir the runtime ROOT unconditionally: with zero entries nothing below created it, so the
|
|
1519
1641
|
// manifest write at the end failed ENOENT — `init` followed by `compile` in a fresh workspace
|
|
@@ -1596,6 +1718,69 @@ export function compile({ root, pkg }) {
|
|
|
1596
1718
|
: `${sources.length - 1} module(s) + workspace`;
|
|
1597
1719
|
console.log(`✔ compiled ${summary || 'nothing'} from ${sourceLabel} → .dreamteamer`);
|
|
1598
1720
|
for (const line of harnessSummary) console.log(`✔ harness ${line}`);
|
|
1721
|
+
|
|
1722
|
+
// ---- proof coverage, on EVERY compile ----------------------------------------
|
|
1723
|
+
// One line, unconditional, even at zero: coverage that is only reported when someone asks is
|
|
1724
|
+
// coverage nobody knows the number of. The denominators are what this compile actually produced
|
|
1725
|
+
// (`artifactRefs`, the one enumeration `prove --missing` also reads), the numerators what a
|
|
1726
|
+
// proof names.
|
|
1727
|
+
const covered = (list) => `${list.filter((a) => provenRefs.has(a)).length}/${list.length}`;
|
|
1728
|
+
console.log(`proofs: ${proofEntries.length} declared · commands ${covered(proofArtifacts.commands)} · skills ${covered(proofArtifacts.skills)} · scripts ${covered(proofArtifacts.scripts)} · bindings ${covered(proofArtifacts.bindings)}`);
|
|
1729
|
+
|
|
1730
|
+
// ---- the nudge: ONCE, per NEW command or script with no proof -----------------
|
|
1731
|
+
// module id → the module's workspace-relative ROOT, exactly the value the module projection
|
|
1732
|
+
// stores in `path`. Sorted longest-first so a nested module wins over its parent, and the root
|
|
1733
|
+
// layout's `.` (the empty prefix) matches last.
|
|
1734
|
+
const moduleRoots = sources.map((s) => ({ id: moduleId(s.name), root: rel(s.root) || '.' }))
|
|
1735
|
+
.sort((a, b) => b.root.length - a.root.length);
|
|
1736
|
+
const ownerRoot = (srcPath) => moduleRoots.find(({ root: r }) => r === '.' || srcPath === r || srcPath.startsWith(`${r}/`))?.root ?? '.';
|
|
1737
|
+
// ⚠ NEW, not merely uncovered. A workspace adopting proofs has forty-odd artifacts and none of
|
|
1738
|
+
// them proven; forty-four warnings on day one is the noise that teaches an operator to skip
|
|
1739
|
+
// every line this compile prints. So the nudge fires only for an artifact absent from the
|
|
1740
|
+
// PREVIOUS manifest, and a first-ever compile (no previous manifest at all) nudges nothing.
|
|
1741
|
+
//
|
|
1742
|
+
// Commands and scripts only — they are the artifacts that RUN, and a skill gets its nudge at the
|
|
1743
|
+
// moment `dt add skills` writes it. An artifact shipped from node_modules is skipped: a proof
|
|
1744
|
+
// written there is erased by the next `npm install`.
|
|
1745
|
+
//
|
|
1746
|
+
// ⚠ A KNOWN GAP, stated rather than hidden: a script added to an EXISTING module does not nudge.
|
|
1747
|
+
// The manifest records a module record's SOURCE hash (its package.json), and adding a file under
|
|
1748
|
+
// `bin/` changes neither the entry key nor that hash, so there is nothing to compare. The
|
|
1749
|
+
// coverage line still counts it.
|
|
1750
|
+
if (prevManifest?.entries) {
|
|
1751
|
+
for (const ref of [...proofArtifacts.commands, ...proofArtifacts.scripts]) {
|
|
1752
|
+
if (provenRefs.has(ref)) continue;
|
|
1753
|
+
const known = artifactSource(entries, ref);
|
|
1754
|
+
if (!known || known.new === false || inNodeModules(known.source)) continue;
|
|
1755
|
+
console.log(`no proof yet for ${ref} — ${proofPathFor(ref, known.moduleRoot)} (see using-dreamteamer › proofs)`);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/** Where an artifact's source lives, and whether the PREVIOUS manifest already knew it.
|
|
1760
|
+
*
|
|
1761
|
+
* ⚠ THE MODULE ROOT COMES OFF THE SOURCE, NEVER OFF THE FILE PATH. Slicing it out of the
|
|
1762
|
+
* artifact's source path (`lastIndexOf('/commands/')`, `replace(/\/package\.json$/)`) is
|
|
1763
|
+
* correct only in the `workspace-module` layout. In the ROOT layout (no `workspace-module`, see
|
|
1764
|
+
* :619) a command's source is `commands/hello.command.md` — no module segment at all — so the
|
|
1765
|
+
* slice returned -1 and the nudge named `commands/hello.command.m/proofs/…`, and a module
|
|
1766
|
+
* script named `package.json/proofs/…`. `rel(source.root)` is the same value the module record's
|
|
1767
|
+
* `path` field carries (see the projection near :1459), which is the value that is always right. */
|
|
1768
|
+
function artifactSource(all, ref) {
|
|
1769
|
+
if (ref.startsWith('commands/')) {
|
|
1770
|
+
const key = `${ref}.command.md`;
|
|
1771
|
+
const source = all.get(key)?.sources?.[0]?.path ?? '';
|
|
1772
|
+
if (!source) return null;
|
|
1773
|
+
return { source, moduleRoot: ownerRoot(source), new: !(key in prevManifest.entries) };
|
|
1774
|
+
}
|
|
1775
|
+
// a module script: `<module-id>/bin/<file>` — the id is in the ref, so the root is a lookup
|
|
1776
|
+
// rather than a guess. Its "entry" is the module record compile projected, so a whole NEW
|
|
1777
|
+
// module's scripts nudge and an existing module's do not (above).
|
|
1778
|
+
const mod = ref.slice(0, ref.indexOf('/'));
|
|
1779
|
+
const key = `modules/${mod}.module.yaml`;
|
|
1780
|
+
const entry = all.get(key);
|
|
1781
|
+
if (!entry) return null;
|
|
1782
|
+
return { source: entry.sources?.[0]?.path ?? '', moduleRoot: moduleRoots.find((m) => m.id === mod)?.root ?? '.', new: !(key in prevManifest.entries) };
|
|
1783
|
+
}
|
|
1599
1784
|
return 0;
|
|
1600
1785
|
}
|
|
1601
1786
|
|
|
@@ -1606,17 +1791,6 @@ function prevManifestNamespaces(root) {
|
|
|
1606
1791
|
return normalizeNamespaces(readManifest(root)?.namespaces);
|
|
1607
1792
|
}
|
|
1608
1793
|
|
|
1609
|
-
function engineId() {
|
|
1610
|
-
try {
|
|
1611
|
-
const p = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
1612
|
-
return `${p.name}@${p.version}`;
|
|
1613
|
-
} catch { return 'dreamteamer@unknown'; }
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
// bare version of the RUNNING engine (dev clone or installed copy — whichever loaded)
|
|
1617
|
-
export function engineVersion() {
|
|
1618
|
-
return engineId().split('@').pop();
|
|
1619
|
-
}
|
|
1620
1794
|
|
|
1621
1795
|
// staleness: does any manifest entry's SOURCE differ from what was compiled, or is a
|
|
1622
1796
|
// source file missing/new? used by `status` and warned about at every tool entry.
|
|
@@ -1646,6 +1820,7 @@ export function staleness(root) {
|
|
|
1646
1820
|
const dir = kindDir(r, kind);
|
|
1647
1821
|
if (!fs.existsSync(dir)) continue;
|
|
1648
1822
|
for (const f of walk(dir)) {
|
|
1823
|
+
if (isProofFixture(kind, path.relative(dir, f).split(path.sep).join('/'))) continue;
|
|
1649
1824
|
const relPath = path.relative(root, f);
|
|
1650
1825
|
if (!known.has(relPath)) stale.push(`${relPath} (new, uncompiled)`);
|
|
1651
1826
|
}
|