@latysh-development/monolith 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +132 -2
- package/dist/brief/cli.d.ts +30 -0
- package/dist/brief/cli.d.ts.map +1 -0
- package/dist/brief/cli.js +85 -0
- package/dist/brief/collect.d.ts +17 -0
- package/dist/brief/collect.d.ts.map +1 -0
- package/dist/brief/collect.js +302 -0
- package/dist/brief/config.d.ts +40 -0
- package/dist/brief/config.d.ts.map +1 -0
- package/dist/brief/config.js +85 -0
- package/dist/brief/constants.d.ts +10 -0
- package/dist/brief/constants.d.ts.map +1 -0
- package/dist/brief/constants.js +9 -0
- package/dist/brief/docs.d.ts +53 -0
- package/dist/brief/docs.d.ts.map +1 -0
- package/dist/brief/docs.js +229 -0
- package/dist/brief/freshness.d.ts +104 -0
- package/dist/brief/freshness.d.ts.map +1 -0
- package/dist/brief/freshness.js +263 -0
- package/dist/brief/git-signals.d.ts +17 -0
- package/dist/brief/git-signals.d.ts.map +1 -0
- package/dist/brief/git-signals.js +101 -0
- package/dist/brief/index.d.ts +11 -0
- package/dist/brief/index.d.ts.map +1 -0
- package/dist/brief/index.js +5 -0
- package/dist/brief/reconcile.d.ts +75 -0
- package/dist/brief/reconcile.d.ts.map +1 -0
- package/dist/brief/reconcile.js +259 -0
- package/dist/brief/render.d.ts +60 -0
- package/dist/brief/render.d.ts.map +1 -0
- package/dist/brief/render.js +407 -0
- package/dist/brief/sanitize.d.ts +38 -0
- package/dist/brief/sanitize.d.ts.map +1 -0
- package/dist/brief/sanitize.js +48 -0
- package/dist/brief/selectors.d.ts +36 -0
- package/dist/brief/selectors.d.ts.map +1 -0
- package/dist/brief/selectors.js +75 -0
- package/dist/brief/story.d.ts +18 -0
- package/dist/brief/story.d.ts.map +1 -0
- package/dist/brief/story.js +59 -0
- package/dist/brief/types.d.ts +110 -0
- package/dist/brief/types.d.ts.map +1 -0
- package/dist/brief/types.js +11 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +166 -0
- package/dist/cli/init-args.d.ts +16 -0
- package/dist/cli/init-args.d.ts.map +1 -0
- package/dist/cli/init-args.js +24 -0
- package/dist/cli/ui-args.d.ts +23 -0
- package/dist/cli/ui-args.d.ts.map +1 -0
- package/dist/cli/ui-args.js +66 -0
- package/dist/cli/ui.d.ts +40 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +180 -0
- package/dist/coordinatorium/checkpoint.d.ts +44 -0
- package/dist/coordinatorium/checkpoint.d.ts.map +1 -0
- package/dist/coordinatorium/checkpoint.js +124 -0
- package/dist/coordinatorium/cli.d.ts +39 -0
- package/dist/coordinatorium/cli.d.ts.map +1 -0
- package/dist/coordinatorium/cli.js +203 -0
- package/dist/coordinatorium/errors.d.ts +112 -0
- package/dist/coordinatorium/errors.d.ts.map +1 -0
- package/dist/coordinatorium/errors.js +145 -0
- package/dist/coordinatorium/index.d.ts +11 -0
- package/dist/coordinatorium/index.d.ts.map +1 -0
- package/dist/coordinatorium/index.js +5 -0
- package/dist/coordinatorium/lock.d.ts +187 -0
- package/dist/coordinatorium/lock.d.ts.map +1 -0
- package/dist/coordinatorium/lock.js +659 -0
- package/dist/coordinatorium/mission.d.ts +32 -0
- package/dist/coordinatorium/mission.d.ts.map +1 -0
- package/dist/coordinatorium/mission.js +87 -0
- package/dist/coordinatorium/state-keeper.d.ts +115 -0
- package/dist/coordinatorium/state-keeper.d.ts.map +1 -0
- package/dist/coordinatorium/state-keeper.js +396 -0
- package/dist/coordinatorium/types.d.ts +69 -0
- package/dist/coordinatorium/types.d.ts.map +1 -0
- package/dist/coordinatorium/types.js +27 -0
- package/dist/durable/assess.d.ts +105 -0
- package/dist/durable/assess.d.ts.map +1 -0
- package/dist/durable/assess.js +239 -0
- package/dist/durable/canonical.d.ts +25 -0
- package/dist/durable/canonical.d.ts.map +1 -0
- package/dist/durable/canonical.js +78 -0
- package/dist/durable/cli.d.ts +37 -0
- package/dist/durable/cli.d.ts.map +1 -0
- package/dist/durable/cli.js +141 -0
- package/dist/durable/errors.d.ts +188 -0
- package/dist/durable/errors.d.ts.map +1 -0
- package/dist/durable/errors.js +317 -0
- package/dist/durable/fs-store.d.ts +233 -0
- package/dist/durable/fs-store.d.ts.map +1 -0
- package/dist/durable/fs-store.js +1393 -0
- package/dist/durable/index.d.ts +20 -0
- package/dist/durable/index.d.ts.map +1 -0
- package/dist/durable/index.js +13 -0
- package/dist/durable/log.d.ts +33 -0
- package/dist/durable/log.d.ts.map +1 -0
- package/dist/durable/log.js +157 -0
- package/dist/durable/memory-store.d.ts +51 -0
- package/dist/durable/memory-store.d.ts.map +1 -0
- package/dist/durable/memory-store.js +302 -0
- package/dist/durable/projection.d.ts +35 -0
- package/dist/durable/projection.d.ts.map +1 -0
- package/dist/durable/projection.js +328 -0
- package/dist/durable/prune-check.d.ts +17 -0
- package/dist/durable/prune-check.d.ts.map +1 -0
- package/dist/durable/prune-check.js +45 -0
- package/dist/durable/register.d.ts +100 -0
- package/dist/durable/register.d.ts.map +1 -0
- package/dist/durable/register.js +532 -0
- package/dist/durable/retention.d.ts +49 -0
- package/dist/durable/retention.d.ts.map +1 -0
- package/dist/durable/retention.js +59 -0
- package/dist/durable/runtime.d.ts +497 -0
- package/dist/durable/runtime.d.ts.map +1 -0
- package/dist/durable/runtime.js +1271 -0
- package/dist/durable/seams.d.ts +40 -0
- package/dist/durable/seams.d.ts.map +1 -0
- package/dist/durable/seams.js +21 -0
- package/dist/durable/testing.d.ts +11 -0
- package/dist/durable/testing.d.ts.map +1 -0
- package/dist/durable/testing.js +25 -0
- package/dist/durable/types.d.ts +442 -0
- package/dist/durable/types.d.ts.map +1 -0
- package/dist/durable/types.js +75 -0
- package/dist/graph/builder.d.ts +11 -0
- package/dist/graph/builder.d.ts.map +1 -0
- package/dist/graph/builder.js +153 -0
- package/dist/graph/identity.d.ts +37 -0
- package/dist/graph/identity.d.ts.map +1 -0
- package/dist/graph/identity.js +46 -0
- package/dist/graph/index.d.ts +7 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +3 -0
- package/dist/graph/resolver.d.ts +41 -0
- package/dist/graph/resolver.d.ts.map +1 -0
- package/dist/graph/resolver.js +36 -0
- package/dist/graph/types.d.ts +42 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +1 -0
- package/dist/http/index.d.ts +5 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +2 -0
- package/dist/http/server.d.ts +76 -0
- package/dist/http/server.d.ts.map +1 -0
- package/dist/http/server.js +195 -0
- package/dist/http/ui-assets.d.ts +22 -0
- package/dist/http/ui-assets.d.ts.map +1 -0
- package/dist/http/ui-assets.js +100 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/init/index.d.ts +12 -0
- package/dist/init/index.d.ts.map +1 -0
- package/dist/init/index.js +90 -0
- package/dist/init/prompts.d.ts +3 -0
- package/dist/init/prompts.d.ts.map +1 -0
- package/dist/init/prompts.js +47 -0
- package/dist/init/scaffold.d.ts +25 -0
- package/dist/init/scaffold.d.ts.map +1 -0
- package/dist/init/scaffold.js +204 -0
- package/dist/init/types.d.ts +14 -0
- package/dist/init/types.d.ts.map +1 -0
- package/dist/init/types.js +1 -0
- package/dist/readmodel/dashboard.d.ts +40 -0
- package/dist/readmodel/dashboard.d.ts.map +1 -0
- package/dist/readmodel/dashboard.js +236 -0
- package/dist/readmodel/index.d.ts +3 -0
- package/dist/readmodel/index.d.ts.map +1 -0
- package/dist/readmodel/index.js +1 -0
- package/dist/readmodel/types.d.ts +104 -0
- package/dist/readmodel/types.d.ts.map +1 -0
- package/dist/readmodel/types.js +15 -0
- package/dist/root/index.d.ts +179 -0
- package/dist/root/index.d.ts.map +1 -0
- package/dist/root/index.js +418 -0
- package/dist/root/safe-write.d.ts +268 -0
- package/dist/root/safe-write.d.ts.map +1 -0
- package/dist/root/safe-write.js +966 -0
- package/dist/root/write-artifacts.d.ts +20 -0
- package/dist/root/write-artifacts.d.ts.map +1 -0
- package/dist/root/write-artifacts.js +42 -0
- package/dist/scriptorium/compile.d.ts +4 -0
- package/dist/scriptorium/compile.d.ts.map +1 -0
- package/dist/scriptorium/compile.js +185 -0
- package/dist/scriptorium/frontmatter.d.ts +13 -0
- package/dist/scriptorium/frontmatter.d.ts.map +1 -0
- package/dist/scriptorium/frontmatter.js +90 -0
- package/dist/scriptorium/index.d.ts +3 -0
- package/dist/scriptorium/index.d.ts.map +1 -0
- package/dist/scriptorium/index.js +1 -0
- package/dist/scriptorium/markdown.d.ts +31 -0
- package/dist/scriptorium/markdown.d.ts.map +1 -0
- package/dist/scriptorium/markdown.js +88 -0
- package/dist/scriptorium/types.d.ts +78 -0
- package/dist/scriptorium/types.d.ts.map +1 -0
- package/dist/scriptorium/types.js +1 -0
- package/dist/types/enforcement.d.ts +11 -0
- package/dist/types/enforcement.d.ts.map +1 -0
- package/dist/types/enforcement.js +1 -0
- package/dist/types/integrity.d.ts +33 -0
- package/dist/types/integrity.d.ts.map +1 -0
- package/dist/types/integrity.js +1 -0
- package/dist/types/project-definition.d.ts +30 -0
- package/dist/types/project-definition.d.ts.map +1 -0
- package/dist/types/project-definition.js +1 -0
- package/dist/ui/assets/cosmic-glass-bg-Dwx47zPu.webp +0 -0
- package/dist/ui/assets/index-C4NhMaq7.js +28 -0
- package/dist/ui/assets/index-DMZ7ZvZY.css +1 -0
- package/dist/ui/index.html +13 -0
- package/dist/validate/index.d.ts +6 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +214 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1 -0
- package/package.json +67 -4
- package/templates/.gitkeep +0 -0
- package/templates/default-instance/instruction/.cursorrules +53 -0
- package/templates/default-instance/instruction/.windsurfrules +53 -0
- package/templates/default-instance/instruction/CLAUDE.md +53 -0
- package/templates/default-instance/monolith/context/boot/HANDOFF.md +49 -0
- package/templates/default-instance/monolith/context/boot/WORK_SESSION.md +30 -0
- package/templates/default-instance/monolith/context/execution/current-state.md +30 -0
- package/templates/default-instance/monolith/context/maps/dependency-map.md +38 -0
- package/templates/default-instance/monolith/context/maps/project-map.md +85 -0
- package/templates/default-instance/monolith/context/maps/roadmap-index.md +41 -0
- package/templates/default-instance/monolith/context/maps/routing-map.md +147 -0
- package/templates/default-instance/monolith/context/maps/semantic-map.md +73 -0
- package/templates/default-instance/monolith/context/models/model-registry.md +37 -0
- package/templates/default-instance/monolith/context/models/privacy-classification.md +35 -0
- package/templates/default-instance/monolith/context/templates/plan-template.md +56 -0
- package/templates/default-instance/monolith/governance/README.md +29 -0
- package/templates/default-instance/monolith/governance/RULE_SCHEMA.md +58 -0
- package/templates/default-instance/monolith/learning/INDEX.md +48 -0
- package/templates/default-instance/monolith/learning/injection-map.md +49 -0
- package/templates/default-instance/monolith/learning/taxonomy.md +30 -0
- package/templates/default-instance/monolith/memory/errors/README.md +26 -0
- package/templates/default-instance/monolith/memory/errors/SCHEMA.md +47 -0
- package/templates/default-instance/monolith/memory/errors/TAXONOMY.md +27 -0
- package/templates/default-instance/monolith/memory/long-term/architecture-principles.md +31 -0
- package/templates/default-instance/monolith/memory/long-term/decisions.md +36 -0
- package/templates/default-instance/monolith/memory/long-term/execution-history.md +20 -0
- package/templates/default-instance/monolith/memory/long-term/tech-debt.md +25 -0
- package/templates/default-instance/monolith/memory/working/active.md +41 -0
- package/templates/default-instance/monolith/memory/working/governance-result.md +0 -0
- package/templates/default-instance/monolith/memory/working/integrity-result.md +0 -0
- package/templates/default-instance/monolith/memory/working/story-index.md +28 -0
- package/templates/default-instance/monolith/protocols/boot/boot-sequence.md +59 -0
- package/templates/default-instance/monolith/protocols/cognition/cognitive-modes.md +90 -0
- package/templates/default-instance/monolith/protocols/cognition/dry-run.md +56 -0
- package/templates/default-instance/monolith/protocols/cognition/execution-trace-format.md +52 -0
- package/templates/default-instance/monolith/protocols/cognition/semantic-planning.md +51 -0
- package/templates/default-instance/monolith/protocols/execution/hybrid-execution.md +37 -0
- package/templates/default-instance/monolith/protocols/execution/local-llm-governance.md +42 -0
- package/templates/default-instance/monolith/protocols/governance/command-policy.md +51 -0
- package/templates/default-instance/monolith/protocols/governance/core-invariants.md +40 -0
- package/templates/default-instance/monolith/protocols/governance/memory-governance.md +54 -0
- package/templates/default-instance/monolith/protocols/governance/metadata-schema.md +62 -0
- package/templates/default-instance/monolith/protocols/governance/skills.md +53 -0
- package/templates/default-instance/monolith/protocols/governance/work-session.md +46 -0
- package/templates/default-instance/monolith/skills/INDEX.md +28 -0
- package/templates/default-instance/monolith/skills/current-status/SKILL.md +47 -0
- package/templates/default-instance/monolith/skills/finalize-story/SKILL.md +86 -0
- package/templates/default-instance/monolith/skills/global-status/SKILL.md +56 -0
- package/templates/default-instance/monolith/skills/self-improvement/SKILL.md +77 -0
- package/templates/default-instance/monolith/state.json +11 -0
- package/templates/default-instance/monolith/tests/INDEX.md +57 -0
- package/templates/default-instance/monolith/tests/scenarios/architecture-review.md +32 -0
- package/templates/default-instance/monolith/tests/scenarios/debug-failure.md +32 -0
- package/templates/default-instance/monolith/tests/scenarios/dry-run-activation.md +33 -0
- package/templates/default-instance/monolith/tests/scenarios/execution-trace-verification.md +31 -0
- package/templates/default-instance/monolith/tests/scenarios/finalize-story.md +35 -0
- package/templates/default-instance/monolith/tests/scenarios/implement-feature.md +36 -0
- package/templates/default-instance/monolith/tests/scenarios/local-llm-delegation.md +31 -0
- package/templates/default-instance/monolith/tests/scenarios/planning-request.md +32 -0
- package/templates/default-instance/monolith/tests/scenarios/sensitive-context-task.md +30 -0
- package/templates/default-instance/monolith/tests/scenarios/validator-verification.md +33 -0
- package/templates/default-instance/monolith/tests/validation/expected-behavior.md +70 -0
- package/templates/default-instance/monolith/tests/validators/INDEX.md +17 -0
- package/templates/default-instance/monolith/tests/validators/delegation-semantics.md +22 -0
- package/templates/default-instance/monolith/tests/validators/mode-consistency.md +25 -0
- package/templates/default-instance/monolith/tests/validators/privacy-routing.md +23 -0
- package/templates/default-instance/monolith/tests/validators/protocol-references.md +27 -0
- package/templates/default-instance/monolith/tests/validators/routing-integrity.md +30 -0
- package/templates/default-instance/projectroot/AGENTS.md +107 -0
- package/templates/default-instance/projectroot/monolith.config.json +32 -0
- package/templates/default-instance/projectroot/project.definition.mjs.template +41 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
3
|
+
import { join, dirname, resolve, relative, basename } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { SafeWriteError, createFile, openParent, probeTarget, projectBoundary, resolveMonolithRootValue, } from '../root/index.js';
|
|
6
|
+
const INSTRUCTION_FILE_MAP = {
|
|
7
|
+
claude: 'CLAUDE.md',
|
|
8
|
+
cursor: '.cursorrules',
|
|
9
|
+
windsurf: '.windsurfrules',
|
|
10
|
+
};
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
// Works both from dist/init/ (compiled) and src/init/ (tsx)
|
|
14
|
+
const TEMPLATES_ROOT = resolve(__dirname, '..', '..', 'templates', 'default-instance');
|
|
15
|
+
function substitute(content, vars) {
|
|
16
|
+
return content.replace(/\{\{([A-Z_]+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
|
|
17
|
+
}
|
|
18
|
+
function stripTemplateSuffix(p) {
|
|
19
|
+
return p.endsWith('.template') ? p.slice(0, -'.template'.length) : p;
|
|
20
|
+
}
|
|
21
|
+
/** Documents follow the umask (as before); the runtime state file is owner-only. */
|
|
22
|
+
function writeNew(w, targetPath, content, asState) {
|
|
23
|
+
const parent = openParent(w.boundary, dirname(targetPath), { create: true });
|
|
24
|
+
const outcome = createFile(parent, basename(targetPath), content, {
|
|
25
|
+
...(asState ? { mode: 0o600 } : { mode: 0o666, exactMode: false }),
|
|
26
|
+
durability: 'file',
|
|
27
|
+
});
|
|
28
|
+
w.warnings.push(...outcome.warnings);
|
|
29
|
+
w.created++;
|
|
30
|
+
}
|
|
31
|
+
function copyTemplate(w, templatePath, targetPath, vars, asState = false) {
|
|
32
|
+
writeNew(w, targetPath, substitute(readFileSync(templatePath, 'utf8'), vars), asState);
|
|
33
|
+
w.templates++;
|
|
34
|
+
}
|
|
35
|
+
function walkDir(dir) {
|
|
36
|
+
const results = [];
|
|
37
|
+
if (!existsSync(dir))
|
|
38
|
+
return results;
|
|
39
|
+
for (const entry of readdirSync(dir)) {
|
|
40
|
+
const full = join(dir, entry);
|
|
41
|
+
if (statSync(full).isDirectory()) {
|
|
42
|
+
results.push(...walkDir(full));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
results.push(full);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
function buildVars(config) {
|
|
51
|
+
const r = config.monolithRoot;
|
|
52
|
+
return {
|
|
53
|
+
PROJECT_NAME: config.projectName,
|
|
54
|
+
LANGUAGE: config.language,
|
|
55
|
+
PACKAGE_MANAGER: config.packageManager,
|
|
56
|
+
AI_TOOL: config.aiTool,
|
|
57
|
+
MONOLITH_ROOT: r,
|
|
58
|
+
FINALIZATION_PROTOCOL: config.finalizationProtocol,
|
|
59
|
+
PROTOCOLS_JSON: JSON.stringify(config.protocols, null, 2)
|
|
60
|
+
.split('\n')
|
|
61
|
+
.join('\n '),
|
|
62
|
+
MISTAKE_ROOTS_JSON: JSON.stringify([`${r}/learning/mistakes`, `${r}/memory/errors`], null, 2)
|
|
63
|
+
.split('\n')
|
|
64
|
+
.join('\n '),
|
|
65
|
+
DRIFT_LOG_PATH: `${r}/enforcement/drift-log.json`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Relative to config.monolithRoot (`r`). Shared between createEmptyDirs
|
|
69
|
+
// (what init writes) and planScaffoldConflicts (what init must refuse to
|
|
70
|
+
// overwrite) so the two can never drift apart — see gitkeepTargets().
|
|
71
|
+
const GITKEEP_DIRS = [
|
|
72
|
+
'memory/working/phases',
|
|
73
|
+
'memory/execution/traces',
|
|
74
|
+
'protocols/engineering',
|
|
75
|
+
'learning/mistakes/architecture',
|
|
76
|
+
'learning/mistakes/dependencies',
|
|
77
|
+
'learning/mistakes/nonexistent-api',
|
|
78
|
+
'learning/mistakes/context-sync',
|
|
79
|
+
'learning/mistakes/workflow',
|
|
80
|
+
'learning/mistakes/typescript',
|
|
81
|
+
'learning/mistakes/documentation',
|
|
82
|
+
];
|
|
83
|
+
/** `.gitkeep` paths (relative to targetDir) that init creates for `r`. */
|
|
84
|
+
function gitkeepTargets(r) {
|
|
85
|
+
return GITKEEP_DIRS.map((rel) => `${r}/${rel}/.gitkeep`);
|
|
86
|
+
}
|
|
87
|
+
function createEmptyDirs(w, targetRoot, r) {
|
|
88
|
+
for (const rel of GITKEEP_DIRS) {
|
|
89
|
+
const full = join(targetRoot, r, rel);
|
|
90
|
+
// An existing .gitkeep (possibly user-authored, non-empty) must never be
|
|
91
|
+
// overwritten. planScaffoldConflicts() is expected to have already
|
|
92
|
+
// refused the run in this case; this is the authoritative second guard:
|
|
93
|
+
// creation is exclusive, so an existing entry of any kind is an error.
|
|
94
|
+
writeNew(w, join(full, '.gitkeep'), '', false);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Preflight: compute every tracked template target path scaffoldInstance
|
|
99
|
+
* would write, and return the ones that already exist on disk — or that cannot
|
|
100
|
+
* be written safely (a symlink, including a dangling one, at the target or in
|
|
101
|
+
* a parent component; a FIFO or other special file). Must be called — and
|
|
102
|
+
* found empty — before scaffoldInstance runs, so a conflict is caught before
|
|
103
|
+
* any file is written (no half-scaffolded instance). This is advice for a
|
|
104
|
+
* clean error message; the exclusive creation in scaffoldInstance is the
|
|
105
|
+
* authoritative check.
|
|
106
|
+
*
|
|
107
|
+
* `state.json` is intentionally excluded when it is a regular file:
|
|
108
|
+
* scaffoldInstance never overwrites it (mutable Coordinatorium runtime data,
|
|
109
|
+
* ADR-008), so its presence is not a conflict. A symlink there still is.
|
|
110
|
+
*/
|
|
111
|
+
export function planScaffoldConflicts(targetDir, config) {
|
|
112
|
+
// SEC-001A: the programmatic API enforces the same root policy as the CLI.
|
|
113
|
+
const r = resolveMonolithRootValue(targetDir, config.monolithRoot).monolithRoot;
|
|
114
|
+
const conflicts = [];
|
|
115
|
+
const boundary = projectBoundary(targetDir);
|
|
116
|
+
const check = (targetPath, regularFileIsFine = false) => {
|
|
117
|
+
const probe = probeTarget(boundary, targetPath);
|
|
118
|
+
if (probe.state === 'free')
|
|
119
|
+
return;
|
|
120
|
+
if (probe.state === 'exists' && regularFileIsFine)
|
|
121
|
+
return;
|
|
122
|
+
const rel = relative(targetDir, probe.path);
|
|
123
|
+
if (!conflicts.includes(rel))
|
|
124
|
+
conflicts.push(rel);
|
|
125
|
+
};
|
|
126
|
+
const projectrootTemplates = join(TEMPLATES_ROOT, 'projectroot');
|
|
127
|
+
if (existsSync(projectrootTemplates)) {
|
|
128
|
+
for (const tplFile of walkDir(projectrootTemplates)) {
|
|
129
|
+
const relPath = relative(projectrootTemplates, tplFile);
|
|
130
|
+
check(join(targetDir, stripTemplateSuffix(relPath)));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const monolithTemplates = join(TEMPLATES_ROOT, 'monolith');
|
|
134
|
+
if (existsSync(monolithTemplates)) {
|
|
135
|
+
for (const tplFile of walkDir(monolithTemplates)) {
|
|
136
|
+
const relPath = relative(monolithTemplates, tplFile);
|
|
137
|
+
const strippedRelPath = stripTemplateSuffix(relPath);
|
|
138
|
+
check(join(targetDir, r, strippedRelPath), strippedRelPath === 'state.json');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const instructionFileName = INSTRUCTION_FILE_MAP[config.aiTool];
|
|
142
|
+
if (instructionFileName)
|
|
143
|
+
check(join(targetDir, instructionFileName));
|
|
144
|
+
for (const rel of gitkeepTargets(r))
|
|
145
|
+
check(join(targetDir, rel));
|
|
146
|
+
return conflicts;
|
|
147
|
+
}
|
|
148
|
+
export async function scaffoldInstance(targetDir, config) {
|
|
149
|
+
// SEC-001A: validate before any write; throws RootContainmentError.
|
|
150
|
+
const r = resolveMonolithRootValue(targetDir, config.monolithRoot).monolithRoot;
|
|
151
|
+
const vars = buildVars({ ...config, monolithRoot: r });
|
|
152
|
+
const dirsCreated = [];
|
|
153
|
+
// `targetDir` is the project root the user pointed init at (it must exist,
|
|
154
|
+
// as resolveMonolithRootValue already requires). Everything below it is checked.
|
|
155
|
+
const w = { boundary: projectBoundary(targetDir), warnings: [], created: 0, templates: 0 };
|
|
156
|
+
try {
|
|
157
|
+
// --- Project root files ---
|
|
158
|
+
const projectrootTemplates = join(TEMPLATES_ROOT, 'projectroot');
|
|
159
|
+
if (existsSync(projectrootTemplates)) {
|
|
160
|
+
for (const tplFile of walkDir(projectrootTemplates)) {
|
|
161
|
+
const relPath = relative(projectrootTemplates, tplFile);
|
|
162
|
+
copyTemplate(w, tplFile, join(targetDir, stripTemplateSuffix(relPath)), vars);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// --- Monolith instance files ---
|
|
166
|
+
const monolithTemplates = join(TEMPLATES_ROOT, 'monolith');
|
|
167
|
+
if (existsSync(monolithTemplates)) {
|
|
168
|
+
for (const tplFile of walkDir(monolithTemplates)) {
|
|
169
|
+
const relPath = relative(monolithTemplates, tplFile);
|
|
170
|
+
const strippedRelPath = stripTemplateSuffix(relPath);
|
|
171
|
+
const targetPath = join(targetDir, r, strippedRelPath);
|
|
172
|
+
const isState = strippedRelPath === 'state.json';
|
|
173
|
+
// Coordinatorium state (ADR-008) is mutable runtime data, not a
|
|
174
|
+
// document template — re-running init must never clobber missions
|
|
175
|
+
// an existing instance has already recorded. Only a regular file is
|
|
176
|
+
// left alone; a symlink or special file there is still refused.
|
|
177
|
+
if (isState && probeTarget(w.boundary, targetPath).state === 'exists')
|
|
178
|
+
continue;
|
|
179
|
+
copyTemplate(w, tplFile, targetPath, vars, isState);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// --- AI tool instruction file (CLAUDE.md / .cursorrules / .windsurfrules) ---
|
|
183
|
+
const instructionFileName = INSTRUCTION_FILE_MAP[config.aiTool];
|
|
184
|
+
if (instructionFileName) {
|
|
185
|
+
const tplPath = join(TEMPLATES_ROOT, 'instruction', instructionFileName);
|
|
186
|
+
if (existsSync(tplPath))
|
|
187
|
+
copyTemplate(w, tplPath, join(targetDir, instructionFileName), vars);
|
|
188
|
+
}
|
|
189
|
+
// --- Empty dirs + .gitkeep ---
|
|
190
|
+
createEmptyDirs(w, targetDir, r);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
if (err instanceof SafeWriteError && w.created > 0) {
|
|
194
|
+
throw new SafeWriteError(`${err.message} ${w.created} file(s) had already been created before this failure; ` +
|
|
195
|
+
'init is not transactional and does not roll them back.', { code: err.code, phase: err.phase, publication: err.publication, path: err.path, tmp: err.tmp, cause: err.cause });
|
|
196
|
+
}
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
// Track dirs created
|
|
200
|
+
dirsCreated.push(r, `${r}/memory`, `${r}/context`, `${r}/protocols`, `${r}/skills`, `${r}/governance`, `${r}/learning`, `${r}/tests`, `${r}/adr`);
|
|
201
|
+
console.log(` ✓ ${w.templates} files generated`);
|
|
202
|
+
console.log(` ✓ ${dirsCreated.length + 11} directories created`);
|
|
203
|
+
return { filesCreated: w.templates, dirsCreated, warnings: w.warnings };
|
|
204
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ProjectConfig {
|
|
2
|
+
projectName: string;
|
|
3
|
+
language: 'typescript' | 'javascript' | 'python' | 'other';
|
|
4
|
+
packageManager: 'pnpm' | 'npm' | 'yarn' | 'other';
|
|
5
|
+
aiTool: 'claude' | 'cursor' | 'windsurf' | 'other';
|
|
6
|
+
finalizationProtocol: string;
|
|
7
|
+
protocols: string[];
|
|
8
|
+
monolithRoot: string;
|
|
9
|
+
}
|
|
10
|
+
export interface InitOptions {
|
|
11
|
+
targetDir: string;
|
|
12
|
+
yes?: boolean;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/init/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,YAAY,GAAG,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC3D,cAAc,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;IAClD,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;IACnD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `readDashboardModel(projectRoot)` — builds the DashboardReadModel.
|
|
3
|
+
*
|
|
4
|
+
* Contract:
|
|
5
|
+
* - `projectRoot` is supplied by the embedding process (fixed at startup),
|
|
6
|
+
* never by a request. The set of files read is closed (see SourceId).
|
|
7
|
+
* - Strictly read-only and synchronous: no write, no lock, no `mutateState`,
|
|
8
|
+
* no `validate`, no `import()` of project code, no network. Nothing is
|
|
9
|
+
* cached; every call re-reads.
|
|
10
|
+
* - Never throws. Failures become SourceReport / ReadNote data.
|
|
11
|
+
* - Deterministic: same files ⇒ deep-equal result; no timestamps.
|
|
12
|
+
*
|
|
13
|
+
* Containment (G1): the instance root is resolved with `resolveMonolithRoot`
|
|
14
|
+
* (ADR-009) and every file is read with `readProjectFile`, which realpath-checks
|
|
15
|
+
* each path component against the real project root and then performs the
|
|
16
|
+
* bounded, non-blocking read of SEC-001B.1. `readState` is deliberately NOT
|
|
17
|
+
* used: it does not check containment. Check-then-use races are NOT closed by
|
|
18
|
+
* Core (SEC-001B) and are therefore not closed here.
|
|
19
|
+
*
|
|
20
|
+
* Every source goes through `readProjectFile` (ADR-014): no symlink in any
|
|
21
|
+
* component, one hard link, regular file, absolute boundary, never the cwd.
|
|
22
|
+
* This module adds no filesystem checks of its own; it only interprets Core's
|
|
23
|
+
* typed result (`FILE_CODE_MAP`). Threat model (ADR-013): TM-A supported with
|
|
24
|
+
* limitations, TM-B partially supported, TM-C (concurrent mutation) NOT supported.
|
|
25
|
+
*/
|
|
26
|
+
import { type ProjectFileErrorCode } from '../root/index.js';
|
|
27
|
+
import type { DashboardReadModel, SourceErrorCode, SourceStatus } from './types.js';
|
|
28
|
+
export declare const ROADMAP_REL = "context/maps/roadmap-index.md";
|
|
29
|
+
/** Protective ceiling, like the other SEC-001B.1 limits — not a content policy. */
|
|
30
|
+
export declare const MAX_ROADMAP_BYTES: number;
|
|
31
|
+
/**
|
|
32
|
+
* Exhaustive over Core's `ProjectFileErrorCode`: a new Core code is a compile
|
|
33
|
+
* error here until it is mapped. The status of a security violation is NOT
|
|
34
|
+
* decided here — `sourceFailure` takes it from Core's `isReadSecurityViolation`,
|
|
35
|
+
* so the UI keeps no second classification of filesystem security. The
|
|
36
|
+
* 'forbidden' entries only document that and are checked against Core in tests.
|
|
37
|
+
*/
|
|
38
|
+
export declare const FILE_CODE_MAP: Readonly<Record<ProjectFileErrorCode, readonly [SourceStatus, SourceErrorCode]>>;
|
|
39
|
+
export declare function readDashboardModel(projectRoot: string): DashboardReadModel;
|
|
40
|
+
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/readmodel/dashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,EAQL,KAAK,oBAAoB,EAE1B,MAAM,kBAAkB,CAAC;AAS1B,OAAO,KAAK,EAGV,kBAAkB,EAKlB,eAAe,EAGf,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,WAAW,kCAAkC,CAAC;AAC3D,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,QAAa,CAAC;AAS5C;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,oBAAoB,EAAE,SAAS,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC,CAa1G,CAAC;AA+LF,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,CAQ1E"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `readDashboardModel(projectRoot)` — builds the DashboardReadModel.
|
|
3
|
+
*
|
|
4
|
+
* Contract:
|
|
5
|
+
* - `projectRoot` is supplied by the embedding process (fixed at startup),
|
|
6
|
+
* never by a request. The set of files read is closed (see SourceId).
|
|
7
|
+
* - Strictly read-only and synchronous: no write, no lock, no `mutateState`,
|
|
8
|
+
* no `validate`, no `import()` of project code, no network. Nothing is
|
|
9
|
+
* cached; every call re-reads.
|
|
10
|
+
* - Never throws. Failures become SourceReport / ReadNote data.
|
|
11
|
+
* - Deterministic: same files ⇒ deep-equal result; no timestamps.
|
|
12
|
+
*
|
|
13
|
+
* Containment (G1): the instance root is resolved with `resolveMonolithRoot`
|
|
14
|
+
* (ADR-009) and every file is read with `readProjectFile`, which realpath-checks
|
|
15
|
+
* each path component against the real project root and then performs the
|
|
16
|
+
* bounded, non-blocking read of SEC-001B.1. `readState` is deliberately NOT
|
|
17
|
+
* used: it does not check containment. Check-then-use races are NOT closed by
|
|
18
|
+
* Core (SEC-001B) and are therefore not closed here.
|
|
19
|
+
*
|
|
20
|
+
* Every source goes through `readProjectFile` (ADR-014): no symlink in any
|
|
21
|
+
* component, one hard link, regular file, absolute boundary, never the cwd.
|
|
22
|
+
* This module adds no filesystem checks of its own; it only interprets Core's
|
|
23
|
+
* typed result (`FILE_CODE_MAP`). Threat model (ADR-013): TM-A supported with
|
|
24
|
+
* limitations, TM-B partially supported, TM-C (concurrent mutation) NOT supported.
|
|
25
|
+
*/
|
|
26
|
+
import { MAX_STATE_BYTES, validateState } from '../coordinatorium/index.js';
|
|
27
|
+
import { CONFIG_FILE_NAME, MAX_PROJECT_FILE_BYTES, isReadSecurityViolation, RootContainmentError, readProjectFile, resolveMonolithRoot, } from '../root/index.js';
|
|
28
|
+
import { MAX_ACTIVE_CONTEXT_BYTES } from '../brief/collect.js';
|
|
29
|
+
import { ACTIVE_MD_REL } from '../brief/constants.js';
|
|
30
|
+
import { extractCurrentStory } from '../brief/story.js';
|
|
31
|
+
import { hasCheckpointOrderMismatch, selectActiveMissions, selectLatestCheckpoint, } from '../brief/selectors.js';
|
|
32
|
+
export const ROADMAP_REL = 'context/maps/roadmap-index.md';
|
|
33
|
+
/** Protective ceiling, like the other SEC-001B.1 limits — not a content policy. */
|
|
34
|
+
export const MAX_ROADMAP_BYTES = 256 * 1024;
|
|
35
|
+
const CHANGED_RETRIES = 3;
|
|
36
|
+
/**
|
|
37
|
+
* Exhaustive over Core's `ProjectFileErrorCode`: a new Core code is a compile
|
|
38
|
+
* error here until it is mapped. The status of a security violation is NOT
|
|
39
|
+
* decided here — `sourceFailure` takes it from Core's `isReadSecurityViolation`,
|
|
40
|
+
* so the UI keeps no second classification of filesystem security. The
|
|
41
|
+
* 'forbidden' entries only document that and are checked against Core in tests.
|
|
42
|
+
*/
|
|
43
|
+
export const FILE_CODE_MAP = {
|
|
44
|
+
FILE_MISSING: ['absent', 'SOURCE_ABSENT'],
|
|
45
|
+
FILE_OUTSIDE_PROJECT: ['forbidden', 'SOURCE_OUTSIDE_PROJECT'],
|
|
46
|
+
FILE_UNRESOLVABLE: ['unreadable', 'SOURCE_UNRESOLVABLE'],
|
|
47
|
+
FILE_NOT_REGULAR: ['forbidden', 'SOURCE_NOT_REGULAR'],
|
|
48
|
+
FILE_EMPTY: ['corrupt', 'SOURCE_EMPTY'],
|
|
49
|
+
FILE_TOO_LARGE: ['unreadable', 'SOURCE_TOO_LARGE'],
|
|
50
|
+
FILE_CHANGED: ['unreadable', 'SOURCE_CHANGED'],
|
|
51
|
+
FILE_UNREADABLE: ['unreadable', 'SOURCE_UNREADABLE'],
|
|
52
|
+
FILE_SYMLINK: ['forbidden', 'SOURCE_SYMLINK'],
|
|
53
|
+
FILE_HARDLINKED: ['forbidden', 'SOURCE_HARDLINKED'],
|
|
54
|
+
READ_BOUNDARY_INVALID: ['forbidden', 'SOURCE_BOUNDARY_INVALID'],
|
|
55
|
+
READ_UNSUPPORTED_PLATFORM: ['forbidden', 'SOURCE_UNSUPPORTED_PLATFORM'],
|
|
56
|
+
};
|
|
57
|
+
/** Never maps a security violation to anything but 'forbidden'; no default for unknown codes. */
|
|
58
|
+
function sourceFailure(code) {
|
|
59
|
+
const [status, sourceCode] = FILE_CODE_MAP[code];
|
|
60
|
+
return [isReadSecurityViolation(code) ? 'forbidden' : status, sourceCode];
|
|
61
|
+
}
|
|
62
|
+
function report(id, path, status, code) {
|
|
63
|
+
return { id, path, status, code };
|
|
64
|
+
}
|
|
65
|
+
function withStatus(r, status, code) {
|
|
66
|
+
return { ...r, status, code };
|
|
67
|
+
}
|
|
68
|
+
/** One contained, bounded read of a fixed project-relative path. */
|
|
69
|
+
function load(projectRoot, id, relPath, maxBytes) {
|
|
70
|
+
try {
|
|
71
|
+
let read = readProjectFile(projectRoot, relPath, { maxBytes, allowEmpty: true });
|
|
72
|
+
// A rename-based writer replacing the file under us is normal, not a fault.
|
|
73
|
+
for (let i = 1; i < CHANGED_RETRIES && !read.ok && read.code === 'FILE_CHANGED'; i++) {
|
|
74
|
+
read = readProjectFile(projectRoot, relPath, { maxBytes, allowEmpty: true });
|
|
75
|
+
}
|
|
76
|
+
if (read.ok)
|
|
77
|
+
return { report: report(id, relPath, 'available', null), content: read.content };
|
|
78
|
+
const [status, code] = sourceFailure(read.code);
|
|
79
|
+
return { report: report(id, relPath, status, code), content: null };
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { report: report(id, relPath, 'unreadable', 'INTERNAL'), content: null };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function isPlainObject(v) {
|
|
86
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
87
|
+
}
|
|
88
|
+
function loadConfig(projectRoot) {
|
|
89
|
+
const loaded = load(projectRoot, 'config', CONFIG_FILE_NAME, MAX_PROJECT_FILE_BYTES);
|
|
90
|
+
if (loaded.report.status === 'absent')
|
|
91
|
+
return { report: loaded.report, read: { status: 'absent' }, name: null };
|
|
92
|
+
if (loaded.content === null) {
|
|
93
|
+
return { report: loaded.report, read: null, name: null };
|
|
94
|
+
}
|
|
95
|
+
let parsed;
|
|
96
|
+
try {
|
|
97
|
+
parsed = JSON.parse(loaded.content);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return { report: withStatus(loaded.report, 'corrupt', 'CONFIG_INVALID'), read: null, name: null };
|
|
101
|
+
}
|
|
102
|
+
if (!isPlainObject(parsed)) {
|
|
103
|
+
return { report: withStatus(loaded.report, 'corrupt', 'CONFIG_INVALID'), read: null, name: null };
|
|
104
|
+
}
|
|
105
|
+
const project = parsed.project;
|
|
106
|
+
const rawName = isPlainObject(project) ? project.name : undefined;
|
|
107
|
+
const name = typeof rawName === 'string' && rawName.length > 0 ? rawName : null;
|
|
108
|
+
return { report: loaded.report, read: { status: 'ok', cfg: parsed }, name };
|
|
109
|
+
}
|
|
110
|
+
function loadState(projectRoot, root) {
|
|
111
|
+
const loaded = load(projectRoot, 'state', `${root.monolithRoot}/state.json`, MAX_STATE_BYTES);
|
|
112
|
+
if (loaded.content === null)
|
|
113
|
+
return { report: loaded.report, state: null, schemaVersionOnDisk: null };
|
|
114
|
+
const corrupt = (code) => ({
|
|
115
|
+
report: withStatus(loaded.report, 'corrupt', code),
|
|
116
|
+
state: null,
|
|
117
|
+
schemaVersionOnDisk: null,
|
|
118
|
+
});
|
|
119
|
+
if (loaded.content.trim() === '')
|
|
120
|
+
return corrupt('SOURCE_EMPTY');
|
|
121
|
+
let parsed;
|
|
122
|
+
try {
|
|
123
|
+
parsed = JSON.parse(loaded.content);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return corrupt('PARSE_ERROR');
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const state = validateState(parsed);
|
|
130
|
+
const onDisk = isPlainObject(parsed) && typeof parsed.schemaVersion === 'number' ? parsed.schemaVersion : null;
|
|
131
|
+
return { report: loaded.report, state, schemaVersionOnDisk: onDisk };
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return corrupt('SCHEMA_INVALID');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function section(data, sources) {
|
|
138
|
+
return { data, sources };
|
|
139
|
+
}
|
|
140
|
+
/** Root could not be trusted: nothing below it is read (fail closed, never a default fallback). */
|
|
141
|
+
function unreadableModel(config, rootCode = 'ROOT_INVALID') {
|
|
142
|
+
const refused = (id) => report(id, null, 'forbidden', rootCode);
|
|
143
|
+
const state = refused('state');
|
|
144
|
+
const story = refused('activeStory');
|
|
145
|
+
const roadmap = refused('roadmap');
|
|
146
|
+
return {
|
|
147
|
+
projectOverview: section(null, [config, state, story]),
|
|
148
|
+
currentMissions: section(null, [state]),
|
|
149
|
+
checkpoints: section(null, [state]),
|
|
150
|
+
roadmap: section(null, [roadmap]),
|
|
151
|
+
diagnostics: { validation: 'NOT_PERFORMED', sources: [config, state, story, roadmap], notes: [] },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function buildModel(projectRoot) {
|
|
155
|
+
const config = loadConfig(projectRoot);
|
|
156
|
+
if (config.read === null)
|
|
157
|
+
return unreadableModel(config.report);
|
|
158
|
+
let root;
|
|
159
|
+
try {
|
|
160
|
+
root = resolveMonolithRoot(projectRoot, config.read);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
if (err instanceof RootContainmentError)
|
|
164
|
+
return unreadableModel(config.report);
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
const notes = [];
|
|
168
|
+
const stateResult = loadState(projectRoot, root);
|
|
169
|
+
const storyLoaded = load(projectRoot, 'activeStory', `${root.monolithRoot}/${ACTIVE_MD_REL}`, MAX_ACTIVE_CONTEXT_BYTES);
|
|
170
|
+
const roadmapLoaded = load(projectRoot, 'roadmap', `${root.monolithRoot}/${ROADMAP_REL}`, MAX_ROADMAP_BYTES);
|
|
171
|
+
// An absent state.json means "no missions ever recorded", exactly like `brief`.
|
|
172
|
+
const stateUsable = stateResult.report.status === 'available' || stateResult.report.status === 'absent';
|
|
173
|
+
const state = stateResult.state;
|
|
174
|
+
const missions = state ? Object.values(state.tactical.missions) : [];
|
|
175
|
+
const active = state ? selectActiveMissions(state) : [];
|
|
176
|
+
if (stateResult.schemaVersionOnDisk === 1)
|
|
177
|
+
notes.push({ code: 'STATE_LEGACY_SCHEMA_MIGRATED', severity: 'info' });
|
|
178
|
+
if (active.length > 1)
|
|
179
|
+
notes.push({ code: 'MULTIPLE_ACTIVE_MISSIONS', severity: 'info' });
|
|
180
|
+
let currentStory = null;
|
|
181
|
+
if (storyLoaded.content !== null) {
|
|
182
|
+
const extraction = extractCurrentStory(storyLoaded.content);
|
|
183
|
+
if (extraction.hasCurrentStorySection && extraction.excerpt !== null && extraction.lineRange !== null) {
|
|
184
|
+
currentStory = { excerpt: extraction.excerpt, lineRange: extraction.lineRange };
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
notes.push({ code: 'ACTIVE_MD_NO_CURRENT_STORY', severity: 'info' });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const overview = {
|
|
191
|
+
name: config.name,
|
|
192
|
+
monolithRoot: root.monolithRoot,
|
|
193
|
+
monolithRootSource: root.source,
|
|
194
|
+
stateSchemaVersionOnDisk: stateResult.schemaVersionOnDisk,
|
|
195
|
+
stateRevision: state && stateResult.report.status === 'available' ? state.revision : null,
|
|
196
|
+
activeMissionCount: stateUsable ? active.length : null,
|
|
197
|
+
finishedMissionCount: stateUsable ? missions.length - active.length : null,
|
|
198
|
+
currentStory,
|
|
199
|
+
};
|
|
200
|
+
const currentMissions = stateUsable ? { active } : null;
|
|
201
|
+
let checkpoints = null;
|
|
202
|
+
if (state !== null || stateResult.report.status === 'absent') {
|
|
203
|
+
const all = state ? Object.values(state.tactical.checkpoints) : [];
|
|
204
|
+
checkpoints = {
|
|
205
|
+
byActiveMission: active.map((m) => {
|
|
206
|
+
const latest = state ? selectLatestCheckpoint(state, m.id) : null;
|
|
207
|
+
if (state && latest && hasCheckpointOrderMismatch(state, m.id, latest)) {
|
|
208
|
+
notes.push({ code: 'CHECKPOINT_ORDER_MISMATCH', severity: 'info' });
|
|
209
|
+
}
|
|
210
|
+
return { missionId: m.id, count: all.filter((c) => c.missionId === m.id).length, latest };
|
|
211
|
+
}),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const roadmap = roadmapLoaded.content === null ? null : { text: roadmapLoaded.content };
|
|
215
|
+
return {
|
|
216
|
+
projectOverview: section(overview, [config.report, stateResult.report, storyLoaded.report]),
|
|
217
|
+
currentMissions: section(currentMissions, [stateResult.report]),
|
|
218
|
+
checkpoints: section(checkpoints, [stateResult.report]),
|
|
219
|
+
roadmap: section(roadmap, [roadmapLoaded.report]),
|
|
220
|
+
diagnostics: {
|
|
221
|
+
validation: 'NOT_PERFORMED',
|
|
222
|
+
sources: [config.report, stateResult.report, storyLoaded.report, roadmapLoaded.report],
|
|
223
|
+
notes,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
export function readDashboardModel(projectRoot) {
|
|
228
|
+
try {
|
|
229
|
+
return buildModel(projectRoot);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Unexpected fault: report it as data, without any message or path.
|
|
233
|
+
const internal = report('config', null, 'unreadable', 'INTERNAL');
|
|
234
|
+
return unreadableModel(internal, 'INTERNAL');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { readDashboardModel, ROADMAP_REL, MAX_ROADMAP_BYTES } from './dashboard.js';
|
|
2
|
+
export type { CheckpointsData, CurrentMissions, DashboardReadModel, MissionCheckpoints, NoteCode, ProjectOverview, ReadDiagnostics, ReadNote, RoadmapData, Section, SourceErrorCode, SourceId, SourceReport, SourceStatus, } from './types.js';
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/readmodel/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpF,YAAY,EACV,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,QAAQ,EACR,eAAe,EACf,eAAe,EACf,QAAQ,EACR,WAAW,EACX,OAAO,EACP,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { readDashboardModel, ROADMAP_REL, MAX_ROADMAP_BYTES } from './dashboard.js';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed Read Model for Mission Control (UI-001B.1, ADR-010).
|
|
3
|
+
*
|
|
4
|
+
* A pure, read-only projection of data MONOLITH Core already owns. It adds no
|
|
5
|
+
* persistent schema and no source of truth: every field is copied from, or
|
|
6
|
+
* counted over, an existing artifact (`monolith.config.json`, `state.json`,
|
|
7
|
+
* `active.md`, `roadmap-index.md`).
|
|
8
|
+
*
|
|
9
|
+
* Errors are DATA, not text: only fixed enum codes and project-relative paths
|
|
10
|
+
* cross this boundary. No absolute path, no exception message and no file
|
|
11
|
+
* excerpt other than the explicit content fields below ever appears in a
|
|
12
|
+
* diagnostic, so the DTO is safe to serialize over HTTP later (G2 still gates
|
|
13
|
+
* that).
|
|
14
|
+
*/
|
|
15
|
+
import type { Checkpoint, Mission } from '../coordinatorium/types.js';
|
|
16
|
+
/** The fixed, closed set of files this model reads. */
|
|
17
|
+
export type SourceId = 'config' | 'state' | 'activeStory' | 'roadmap';
|
|
18
|
+
/**
|
|
19
|
+
* - available: read and (where applicable) parsed successfully
|
|
20
|
+
* - absent: the file does not exist (a legitimate state for a fresh project)
|
|
21
|
+
* - corrupt: the file was read but its content is unusable
|
|
22
|
+
* - forbidden: reading was refused by a security boundary
|
|
23
|
+
* - unreadable: the file exists inside the boundary but could not be read
|
|
24
|
+
*/
|
|
25
|
+
export type SourceStatus = 'available' | 'absent' | 'corrupt' | 'forbidden' | 'unreadable';
|
|
26
|
+
export type SourceErrorCode = 'SOURCE_ABSENT' | 'SOURCE_OUTSIDE_PROJECT' | 'SOURCE_UNRESOLVABLE' | 'SOURCE_NOT_REGULAR' | 'SOURCE_EMPTY' | 'SOURCE_TOO_LARGE' | 'SOURCE_CHANGED' | 'SOURCE_UNREADABLE' | 'SOURCE_SYMLINK' | 'SOURCE_HARDLINKED' | 'SOURCE_BOUNDARY_INVALID' | 'SOURCE_UNSUPPORTED_PLATFORM' | 'PARSE_ERROR' | 'SCHEMA_INVALID' | 'CONFIG_INVALID' | 'ROOT_INVALID' | 'INTERNAL';
|
|
27
|
+
export interface SourceReport {
|
|
28
|
+
id: SourceId;
|
|
29
|
+
/** Project-relative, `/`-separated; null when the instance root could not be trusted. */
|
|
30
|
+
path: string | null;
|
|
31
|
+
status: SourceStatus;
|
|
32
|
+
/** null iff status === 'available'. */
|
|
33
|
+
code: SourceErrorCode | null;
|
|
34
|
+
}
|
|
35
|
+
/** `data` is null only when the sections' sources could not supply it. */
|
|
36
|
+
export interface Section<T> {
|
|
37
|
+
data: T | null;
|
|
38
|
+
/** The sources this section depends on, in fixed order. */
|
|
39
|
+
sources: SourceReport[];
|
|
40
|
+
}
|
|
41
|
+
export interface ProjectOverview {
|
|
42
|
+
/** `project.name` from `monolith.config.json`; null when absent or not a non-empty string. */
|
|
43
|
+
name: string | null;
|
|
44
|
+
/** Validated relative instance root (ADR-009), `/`-separated. */
|
|
45
|
+
monolithRoot: string;
|
|
46
|
+
monolithRootSource: 'config' | 'default';
|
|
47
|
+
/** `schemaVersion` as found on disk (1 is migrated in memory); null unless state is available. */
|
|
48
|
+
stateSchemaVersionOnDisk: number | null;
|
|
49
|
+
stateRevision: number | null;
|
|
50
|
+
/** Counts over `tactical.missions`; null unless state is available or absent (absent ⇒ 0). */
|
|
51
|
+
activeMissionCount: number | null;
|
|
52
|
+
finishedMissionCount: number | null;
|
|
53
|
+
/** Verbatim `## Current Story` section of `active.md`; null when the section cannot be shown. */
|
|
54
|
+
currentStory: {
|
|
55
|
+
excerpt: string;
|
|
56
|
+
lineRange: string;
|
|
57
|
+
} | null;
|
|
58
|
+
}
|
|
59
|
+
export interface CurrentMissions {
|
|
60
|
+
/** Every `in_progress` mission, ordered by (startedAt, id). More than one is reported, never collapsed. */
|
|
61
|
+
active: Mission[];
|
|
62
|
+
}
|
|
63
|
+
export interface MissionCheckpoints {
|
|
64
|
+
missionId: string;
|
|
65
|
+
/** Number of checkpoints recorded for the mission. */
|
|
66
|
+
count: number;
|
|
67
|
+
/**
|
|
68
|
+
* Highest-ordinal checkpoint (ID order is authoritative, as in `brief`).
|
|
69
|
+
* Its `remainingSteps` are a HISTORICAL record at that checkpoint, not the
|
|
70
|
+
* project's current plan.
|
|
71
|
+
*/
|
|
72
|
+
latest: Checkpoint | null;
|
|
73
|
+
}
|
|
74
|
+
export interface CheckpointsData {
|
|
75
|
+
/** One entry per active mission, same order as `CurrentMissions.active`. */
|
|
76
|
+
byActiveMission: MissionCheckpoints[];
|
|
77
|
+
}
|
|
78
|
+
export interface RoadmapData {
|
|
79
|
+
/** Raw, unparsed text of `roadmap-index.md`. Untrusted: must be shown escaped, never rendered or executed. */
|
|
80
|
+
text: string;
|
|
81
|
+
}
|
|
82
|
+
export type NoteCode = 'STATE_LEGACY_SCHEMA_MIGRATED' | 'MULTIPLE_ACTIVE_MISSIONS' | 'CHECKPOINT_ORDER_MISMATCH' | 'ACTIVE_MD_NO_CURRENT_STORY';
|
|
83
|
+
export interface ReadNote {
|
|
84
|
+
code: NoteCode;
|
|
85
|
+
severity: 'info' | 'warn';
|
|
86
|
+
}
|
|
87
|
+
export interface ReadDiagnostics {
|
|
88
|
+
/**
|
|
89
|
+
* This reports availability of the fixed sources only. `monolith validate`
|
|
90
|
+
* is never run, so a clean report is NOT a statement that the project is valid.
|
|
91
|
+
*/
|
|
92
|
+
validation: 'NOT_PERFORMED';
|
|
93
|
+
/** Every source, in fixed order: config, state, activeStory, roadmap. */
|
|
94
|
+
sources: SourceReport[];
|
|
95
|
+
notes: ReadNote[];
|
|
96
|
+
}
|
|
97
|
+
export interface DashboardReadModel {
|
|
98
|
+
projectOverview: Section<ProjectOverview>;
|
|
99
|
+
currentMissions: Section<CurrentMissions>;
|
|
100
|
+
checkpoints: Section<CheckpointsData>;
|
|
101
|
+
roadmap: Section<RoadmapData>;
|
|
102
|
+
diagnostics: ReadDiagnostics;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/readmodel/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAEtE,uDAAuD;AACvD,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,YAAY,CAAC;AAE3F,MAAM,MAAM,eAAe,GACvB,eAAe,GACf,wBAAwB,GACxB,qBAAqB,GACrB,oBAAoB,GACpB,cAAc,GACd,kBAAkB,GAClB,gBAAgB,GAChB,mBAAmB,GACnB,gBAAgB,GAChB,mBAAmB,GACnB,yBAAyB,GACzB,6BAA6B,GAC7B,aAAa,GACb,gBAAgB,GAChB,gBAAgB,GAChB,cAAc,GACd,UAAU,CAAC;AAEf,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,QAAQ,CAAC;IACb,yFAAyF;IACzF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,uCAAuC;IACvC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;CAC9B;AAED,0EAA0E;AAC1E,MAAM,WAAW,OAAO,CAAC,CAAC;IACxB,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,2DAA2D;IAC3D,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,8FAA8F;IAC9F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,QAAQ,GAAG,SAAS,CAAC;IACzC,kGAAkG;IAClG,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,8FAA8F;IAC9F,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,iGAAiG;IACjG,YAAY,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC7D;AAED,MAAM,WAAW,eAAe;IAC9B,2GAA2G;IAC3G,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,eAAe,EAAE,kBAAkB,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,8GAA8G;IAC9G,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,QAAQ,GAChB,8BAA8B,GAC9B,0BAA0B,GAC1B,2BAA2B,GAC3B,4BAA4B,CAAC;AAEjC,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,UAAU,EAAE,eAAe,CAAC;IAC5B,yEAAyE;IACzE,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,eAAe,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,eAAe,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,WAAW,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B,WAAW,EAAE,eAAe,CAAC;CAC9B"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed Read Model for Mission Control (UI-001B.1, ADR-010).
|
|
3
|
+
*
|
|
4
|
+
* A pure, read-only projection of data MONOLITH Core already owns. It adds no
|
|
5
|
+
* persistent schema and no source of truth: every field is copied from, or
|
|
6
|
+
* counted over, an existing artifact (`monolith.config.json`, `state.json`,
|
|
7
|
+
* `active.md`, `roadmap-index.md`).
|
|
8
|
+
*
|
|
9
|
+
* Errors are DATA, not text: only fixed enum codes and project-relative paths
|
|
10
|
+
* cross this boundary. No absolute path, no exception message and no file
|
|
11
|
+
* excerpt other than the explicit content fields below ever appears in a
|
|
12
|
+
* diagnostic, so the DTO is safe to serialize over HTTP later (G2 still gates
|
|
13
|
+
* that).
|
|
14
|
+
*/
|
|
15
|
+
export {};
|