@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dmytro Latysh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,133 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/branding/monolith-banner.png" alt="MONOLITH — Cognitive Infrastructure for AI" width="100%">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">MONOLITH</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Cognitive Infrastructure for AI</strong><br>
|
|
9
|
+
Observable · Structured · Governed
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
|
|
14
|
+
<a href="./package.json"><img src="https://img.shields.io/badge/node-%3E%3D20-339933.svg" alt="Node.js >= 20"></a>
|
|
15
|
+
<img src="https://img.shields.io/badge/status-alpha-orange.svg" alt="Status: alpha">
|
|
16
|
+
<a href="https://hub.xerj.org/contributors/Demion-L"><img src="https://hub.xerj.org/contributors/Demion-L/badge.svg" alt="XERJ Corpus Hub contributor"></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
MONOLITH is an open-source toolkit for observable project state, structured
|
|
22
|
+
knowledge, and explicit execution governance. It gives a
|
|
23
|
+
human and an AI assistant a shared, inspectable record of a project —
|
|
24
|
+
structured knowledge, explicit read and write boundaries, and traceable
|
|
25
|
+
decisions — instead of relying on an assistant's unaided memory.
|
|
26
|
+
|
|
27
|
+
> **[MANIFESTO.md](./MANIFESTO.md)** — why this project exists and the engineering principles that govern it.
|
|
28
|
+
> **[AGENTS.md](./AGENTS.md)** — current scope, source structure, and the authoritative entry point for AI assistants working in this repository.
|
|
29
|
+
|
|
30
|
+
## Why MONOLITH?
|
|
31
|
+
|
|
32
|
+
AI assistants lose context between sessions, and what a project "knows" ends up
|
|
33
|
+
scattered across chats, notes and habits. MONOLITH keeps that knowledge in the
|
|
34
|
+
project itself, in a form both a person and an assistant can read, check and
|
|
35
|
+
trust — using mechanisms that are deterministic and testable.
|
|
36
|
+
|
|
37
|
+
## What MONOLITH Does
|
|
38
|
+
|
|
39
|
+
- **Observable** — `monolith brief` summarizes project state in the terminal, including whether it looks stale; Mission Control shows it in a local, read-only web interface.
|
|
40
|
+
- **Structured** — structure is extracted from a project's own markdown (frontmatter, headings, explicit links) into an explicit relationship graph. No embeddings, no LLM, no fuzzy matching.
|
|
41
|
+
- **Governed** — protocols, skills and cognitive modes describe how an assistant reads and updates project memory; missions and checkpoints record progress. This is a recorded discipline, not a runtime gate.
|
|
42
|
+
- **Reproducible** — same input, same output. State lives in a schema-versioned file written with a lock and atomic replace; Durable State adds a hash-chained event log.
|
|
43
|
+
- **Bounded** — managed files are read and written through strict primitives that refuse symlinks; `monolith validate` never executes project code; Mission Control has no write endpoint.
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
Requires Node.js >= 20. MONOLITH is an alpha package that is **not published
|
|
48
|
+
to npm**. Build it from a clone:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/Demion-L/the_monolith.git
|
|
52
|
+
cd the_monolith
|
|
53
|
+
pnpm install
|
|
54
|
+
pnpm build
|
|
55
|
+
npm link # exposes the `monolith` command (or run: node dist/cli/index.js)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Once the first alpha is published, `npm install -g @latysh-development/monolith@alpha`
|
|
59
|
+
becomes the primary install path ([release notes](./docs/release/alpha-installation.md)).
|
|
60
|
+
|
|
61
|
+
Then, in a project directory:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
monolith init # scaffold a MONOLITH instance (refuses to overwrite existing files)
|
|
65
|
+
monolith validate # static integrity check of the instance
|
|
66
|
+
monolith brief # bounded, read-only summary of project state
|
|
67
|
+
monolith ui # Mission Control at http://127.0.0.1:4173 ; Ctrl+C to stop
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`monolith ui` must be run inside a directory that contains a MONOLITH
|
|
71
|
+
instance, and it does not open a browser — open the printed address yourself.
|
|
72
|
+
All commands: [CLI reference](./docs/reference/cli.md).
|
|
73
|
+
|
|
74
|
+
## Architecture
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
Markdown artifact → Scriptorium (extraction) → ERG (relationship graph) → governed project memory
|
|
78
|
+
|
|
79
|
+
MONOLITH Kernel (src/) ──► Project Instance (.monolith/, monolith.config.json)
|
|
80
|
+
│ safe, bounded reads
|
|
81
|
+
▼
|
|
82
|
+
Read model ──► loopback HTTP API ──► Mission Control UI
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
> **Kernel knows HOW. Instance knows WHAT.** The Kernel holds generic
|
|
86
|
+
> mechanics and has no hardcoded knowledge of any project; an Instance — the
|
|
87
|
+
> `.monolith/` directory of a project using MONOLITH, this repository
|
|
88
|
+
> included — supplies the project's own knowledge. Details:
|
|
89
|
+
> [`docs/architecture/`](./docs/architecture/README.md).
|
|
90
|
+
|
|
91
|
+
## Ecosystem
|
|
92
|
+
|
|
93
|
+
- **[Latysh Development](https://github.com/Demion-L)** — developer of MONOLITH.
|
|
94
|
+
- **[Webway](https://github.com/Demion-L/webway)** — a separate, independently developed project on semantic and associative knowledge linking. It was extracted from MONOLITH and has no runtime coupling to it.
|
|
95
|
+
- **SphynX** — a related application. MONOLITH does not depend on it, and this repository does not document it as a current consumer of MONOLITH.
|
|
96
|
+
|
|
97
|
+
## Community & Recognition
|
|
98
|
+
|
|
99
|
+
MONOLITH is listed as a contributor project on
|
|
100
|
+
[XERJ Corpus Hub](https://hub.xerj.org/contributors/Demion-L). At the time of
|
|
101
|
+
writing, the contributor page exists, a MONOLITH commit is selected, the corpus
|
|
102
|
+
status is `candidate` and the manifest is `draft`. This is a contribution
|
|
103
|
+
listing — not an official partnership, endorsement or certification.
|
|
104
|
+
|
|
105
|
+
## Documentation
|
|
106
|
+
|
|
107
|
+
- [CLI reference](./docs/reference/cli.md) — every command, `init`, `validate`, `brief`, `mission`, `durable`, `ui`
|
|
108
|
+
- [Mission Control](./docs/reference/mission-control.md) — sections, LIVE/DEMO, skins, limitations
|
|
109
|
+
- [Security & Limitations](./docs/reference/security-and-limitations.md) — threat model, platform and filesystem caveats
|
|
110
|
+
- [Programmatic API](./docs/reference/write-scope-api.md) · [Development](./docs/reference/development.md)
|
|
111
|
+
- [Architecture](./docs/architecture/README.md) · [ADRs](./docs/adr/README.md) · [Knowledge-base index](./docs/README.md)
|
|
112
|
+
- [`AGENTS.md`](./AGENTS.md) — scope and entry point for AI assistants · [`MANIFESTO.md`](./MANIFESTO.md) — principles
|
|
113
|
+
|
|
114
|
+
## Project Status
|
|
115
|
+
|
|
116
|
+
**Alpha** (`0.1.0-alpha.0`). The API and CLI are experimental and may change
|
|
117
|
+
without notice. Not published to npm. Durable State and the Coordinatorium
|
|
118
|
+
(`monolith durable`, `monolith mission`) are experimental and are not part of
|
|
119
|
+
the Quick Start.
|
|
120
|
+
|
|
121
|
+
**Security:** Mission Control is read-only, listens on `127.0.0.1` only and has
|
|
122
|
+
no authentication. Concurrent adversarial mutation of a project is unsupported;
|
|
123
|
+
Windows is unverified. Durable State is merged but its security acceptance is
|
|
124
|
+
still open. See [Security & Limitations](./docs/reference/security-and-limitations.md).
|
|
125
|
+
|
|
126
|
+
**Not yet implemented:** a write-enabled UI, authentication for Mission
|
|
127
|
+
Control, `monolith context <topic>`, `monolith status`, `monolith dry-run`, the
|
|
128
|
+
enforcement gate engine and integrity validators (type declarations only), and
|
|
129
|
+
protocol drift detection. `AGENTS.md` is the authoritative status record.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
[MIT](./LICENSE) © 2026 Dmytro Latysh
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `monolith brief` CLI command handler.
|
|
3
|
+
*
|
|
4
|
+
* stdout carries only the brief's own content; CLI diagnostics and the
|
|
5
|
+
* INCOMPLETE warning go to stderr (section 12). Exit codes (DEC-CONTEXT-002):
|
|
6
|
+
* 0 — brief formed (COMPLETE or INCOMPLETE)
|
|
7
|
+
* 1 — CLI usage error (thrown as a plain Error, caught by src/cli/index.ts's main())
|
|
8
|
+
* 2 — --strict and the result is INCOMPLETE
|
|
9
|
+
*
|
|
10
|
+
* `--reconcile` (SELF-AWARENESS-002) prints the State Reconciliation Report
|
|
11
|
+
* instead of the brief: what a Freshness verdict rests on and what a person
|
|
12
|
+
* should check. Same read-only contract, same exit codes (a report is formed
|
|
13
|
+
* -> 0, whatever the Freshness; usage error -> 1). It does not combine with
|
|
14
|
+
* `--strict`, whose meaning (Completeness) belongs to the brief.
|
|
15
|
+
*/
|
|
16
|
+
export interface BriefArgsOk {
|
|
17
|
+
ok: true;
|
|
18
|
+
maxChars: number;
|
|
19
|
+
strict: boolean;
|
|
20
|
+
/** Present (true) only when `--reconcile` was given, so existing result shapes are unchanged. */
|
|
21
|
+
reconcile?: true;
|
|
22
|
+
}
|
|
23
|
+
export interface BriefArgsErr {
|
|
24
|
+
ok: false;
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
export type BriefArgsResult = BriefArgsOk | BriefArgsErr;
|
|
28
|
+
export declare function parseBriefArgs(args: string[]): BriefArgsResult;
|
|
29
|
+
export declare function runBrief(args: string[], targetDir?: string): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/brief/cli.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;GAcG;AAMH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,iGAAiG;IACjG,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,YAAY,CAAC;AAIzD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CA6C9D;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BvF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/**
|
|
3
|
+
* `monolith brief` CLI command handler.
|
|
4
|
+
*
|
|
5
|
+
* stdout carries only the brief's own content; CLI diagnostics and the
|
|
6
|
+
* INCOMPLETE warning go to stderr (section 12). Exit codes (DEC-CONTEXT-002):
|
|
7
|
+
* 0 — brief formed (COMPLETE or INCOMPLETE)
|
|
8
|
+
* 1 — CLI usage error (thrown as a plain Error, caught by src/cli/index.ts's main())
|
|
9
|
+
* 2 — --strict and the result is INCOMPLETE
|
|
10
|
+
*
|
|
11
|
+
* `--reconcile` (SELF-AWARENESS-002) prints the State Reconciliation Report
|
|
12
|
+
* instead of the brief: what a Freshness verdict rests on and what a person
|
|
13
|
+
* should check. Same read-only contract, same exit codes (a report is formed
|
|
14
|
+
* -> 0, whatever the Freshness; usage error -> 1). It does not combine with
|
|
15
|
+
* `--strict`, whose meaning (Completeness) belongs to the brief.
|
|
16
|
+
*/
|
|
17
|
+
import { collect } from './collect.js';
|
|
18
|
+
import { render, DEFAULT_MAX_CHARS } from './render.js';
|
|
19
|
+
import { reconcile, renderReconciliation } from './reconcile.js';
|
|
20
|
+
const USAGE = 'Usage: monolith brief [--max-chars N] [--strict]\n monolith brief --reconcile [--max-chars N]';
|
|
21
|
+
export function parseBriefArgs(args) {
|
|
22
|
+
let maxChars = DEFAULT_MAX_CHARS;
|
|
23
|
+
let strict = false;
|
|
24
|
+
let reconcileMode = false;
|
|
25
|
+
const unknown = [];
|
|
26
|
+
let i = 0;
|
|
27
|
+
while (i < args.length) {
|
|
28
|
+
const arg = args[i];
|
|
29
|
+
if (arg === '--strict') {
|
|
30
|
+
strict = true;
|
|
31
|
+
i++;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (arg === '--reconcile') {
|
|
35
|
+
reconcileMode = true;
|
|
36
|
+
i++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg === '--max-chars') {
|
|
40
|
+
const value = args[i + 1];
|
|
41
|
+
if (value === undefined) {
|
|
42
|
+
return { ok: false, message: `--max-chars requires a value.\n\n${USAGE}` };
|
|
43
|
+
}
|
|
44
|
+
const n = Number(value);
|
|
45
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
46
|
+
return { ok: false, message: `--max-chars must be a positive integer, got "${value}".\n\n${USAGE}` };
|
|
47
|
+
}
|
|
48
|
+
maxChars = n;
|
|
49
|
+
i += 2;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
unknown.push(arg);
|
|
53
|
+
i++;
|
|
54
|
+
}
|
|
55
|
+
if (unknown.length > 0) {
|
|
56
|
+
return { ok: false, message: `Unknown argument(s) for 'monolith brief': ${unknown.join(' ')}\n\n${USAGE}` };
|
|
57
|
+
}
|
|
58
|
+
if (reconcileMode && strict) {
|
|
59
|
+
return { ok: false, message: `--strict cannot be combined with --reconcile.\n\n${USAGE}` };
|
|
60
|
+
}
|
|
61
|
+
return reconcileMode ? { ok: true, maxChars, strict, reconcile: true } : { ok: true, maxChars, strict };
|
|
62
|
+
}
|
|
63
|
+
export async function runBrief(args, targetDir = process.cwd()) {
|
|
64
|
+
const parsed = parseBriefArgs(args);
|
|
65
|
+
if (!parsed.ok) {
|
|
66
|
+
throw new Error(parsed.message);
|
|
67
|
+
}
|
|
68
|
+
const model = await collect(targetDir);
|
|
69
|
+
if (parsed.reconcile) {
|
|
70
|
+
if (!model.freshness || !model.freshnessEvidence) {
|
|
71
|
+
throw new Error('monolith brief --reconcile: no freshness evidence could be collected.');
|
|
72
|
+
}
|
|
73
|
+
process.stdout.write(renderReconciliation(reconcile(model.freshnessEvidence, model.freshness), parsed.maxChars).text);
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
const { text, completeness } = render(model, parsed.maxChars);
|
|
77
|
+
process.stdout.write(text);
|
|
78
|
+
if (completeness === 'INCOMPLETE') {
|
|
79
|
+
console.error('monolith brief: result is INCOMPLETE — see the Diagnostics section above.');
|
|
80
|
+
}
|
|
81
|
+
if (parsed.strict && completeness === 'INCOMPLETE') {
|
|
82
|
+
process.exit(2);
|
|
83
|
+
}
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `collect()` — the read-only Collection stage of the brief pipeline.
|
|
3
|
+
*
|
|
4
|
+
* readMonolithRoot → collect() → BriefModel → render(model, budget) → stdout
|
|
5
|
+
*
|
|
6
|
+
* Strictly read-only: no directories, lock files, or state.json are ever
|
|
7
|
+
* created; `mutateState()` is never imported here. state.json is read at
|
|
8
|
+
* most once (one `readFile` call) per invocation — every section that needs
|
|
9
|
+
* Coordinatorium data derives it from that single snapshot via the pure
|
|
10
|
+
* selectors in selectors.ts, never by calling `listMissions()`/
|
|
11
|
+
* `listCheckpoints()` (which each independently re-read the file).
|
|
12
|
+
*/
|
|
13
|
+
import type { BriefModel } from './types.js';
|
|
14
|
+
/** SEC-001B.1: `active.md` is a short hand-written context file (~3 KB today). */
|
|
15
|
+
export declare const MAX_ACTIVE_CONTEXT_BYTES: number;
|
|
16
|
+
export declare function collect(targetDir: string): Promise<BriefModel>;
|
|
17
|
+
//# sourceMappingURL=collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../src/brief/collect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAwBH,OAAO,KAAK,EACV,UAAU,EAMX,MAAM,YAAY,CAAC;AAepB,kFAAkF;AAClF,eAAO,MAAM,wBAAwB,QAAa,CAAC;AAsRnD,wBAAsB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAkEpE"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `collect()` — the read-only Collection stage of the brief pipeline.
|
|
3
|
+
*
|
|
4
|
+
* readMonolithRoot → collect() → BriefModel → render(model, budget) → stdout
|
|
5
|
+
*
|
|
6
|
+
* Strictly read-only: no directories, lock files, or state.json are ever
|
|
7
|
+
* created; `mutateState()` is never imported here. state.json is read at
|
|
8
|
+
* most once (one `readFile` call) per invocation — every section that needs
|
|
9
|
+
* Coordinatorium data derives it from that single snapshot via the pure
|
|
10
|
+
* selectors in selectors.ts, never by calling `listMissions()`/
|
|
11
|
+
* `listCheckpoints()` (which each independently re-read the file).
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync } from 'fs';
|
|
14
|
+
import { join, relative, posix } from 'path';
|
|
15
|
+
import { readBriefConfig } from './config.js';
|
|
16
|
+
import { resolveMonolithRootValue, checkContainedFile, readBoundedFileStable, isReadSecurityViolation, findWriteArtifacts, describeWriteArtifacts, } from '../root/index.js';
|
|
17
|
+
import { discoverDocs } from './docs.js';
|
|
18
|
+
import { extractCurrentStory } from './story.js';
|
|
19
|
+
import { selectActiveMissions, selectRecentFinishedMissions, selectLatestCheckpoint, hasCheckpointOrderMismatch, } from './selectors.js';
|
|
20
|
+
import { stateFilePath, validateState, createInitialState, MAX_STATE_BYTES } from '../coordinatorium/index.js';
|
|
21
|
+
import { ACTIVE_MD_REL, PROTOCOLS_DIR, ROADMAP_INDEX_REL, STORY_INDEX_REL, HANDOFF_REL } from './constants.js';
|
|
22
|
+
import { evaluateFreshness, parseActiveStoryClaim, parseRoadmapActiveStory, parseRoadmapStatuses, parseStoryIndexStatuses, } from './freshness.js';
|
|
23
|
+
import { readGitSignals } from './git-signals.js';
|
|
24
|
+
const RECENT_FINISHED_MISSIONS_LIMIT = 5;
|
|
25
|
+
/** SEC-001B.1: `active.md` is a short hand-written context file (~3 KB today). */
|
|
26
|
+
export const MAX_ACTIVE_CONTEXT_BYTES = 256 * 1024;
|
|
27
|
+
function isPlainObject(v) {
|
|
28
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
29
|
+
}
|
|
30
|
+
async function readStateForBrief(targetDir, monolithRoot, root, diagnostics) {
|
|
31
|
+
const stateDir = join(targetDir, monolithRoot);
|
|
32
|
+
const filePath = stateFilePath(stateDir);
|
|
33
|
+
const provenance = { source: relative(targetDir, filePath) };
|
|
34
|
+
// SEC-001A: never read a state.json that resolves outside the project.
|
|
35
|
+
const escape = checkContainedFile(root, 'state.json');
|
|
36
|
+
if (escape) {
|
|
37
|
+
diagnostics.push({
|
|
38
|
+
code: 'PATH_OUTSIDE_PROJECT',
|
|
39
|
+
severity: 'error',
|
|
40
|
+
message: `${provenance.source} was not read: ${escape.message}`,
|
|
41
|
+
});
|
|
42
|
+
return { summary: { status: 'unreadable', provenance }, state: createInitialState() };
|
|
43
|
+
}
|
|
44
|
+
// Rule (section 4, #1): existence is established by the same bounded,
|
|
45
|
+
// non-blocking open that reads the file (SEC-001B.1) — a FIFO or an
|
|
46
|
+
// over-limit file is rejected before any content is read.
|
|
47
|
+
const read = readBoundedFileStable(filePath, provenance.source, { boundary: targetDir, maxBytes: MAX_STATE_BYTES, allowEmpty: true }, true);
|
|
48
|
+
if (!read.ok && read.code === 'FILE_MISSING') {
|
|
49
|
+
diagnostics.push({
|
|
50
|
+
code: 'STATE_NOT_INITIALIZED',
|
|
51
|
+
severity: 'info',
|
|
52
|
+
message: `${provenance.source} does not exist — no missions have ever been recorded.`,
|
|
53
|
+
readCode: 'FILE_MISSING',
|
|
54
|
+
});
|
|
55
|
+
return { summary: { status: 'absent', provenance }, state: createInitialState() };
|
|
56
|
+
}
|
|
57
|
+
if (!read.ok) {
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
code: 'STATE_UNREADABLE',
|
|
60
|
+
severity: 'error',
|
|
61
|
+
message: `${provenance.source} exists but could not be read: ${read.code}: ${read.detail}`,
|
|
62
|
+
readCode: read.code,
|
|
63
|
+
});
|
|
64
|
+
return { summary: { status: 'unreadable', provenance }, state: createInitialState() };
|
|
65
|
+
}
|
|
66
|
+
const raw = 'content' in read ? read.content : '';
|
|
67
|
+
// A literally empty file is distinguished from corrupted content
|
|
68
|
+
// (DEC-CONTEXT-002: "corrupted state.json must not be treated as empty
|
|
69
|
+
// state") precisely so the STATE_UNREADABLE path below is never reached
|
|
70
|
+
// for this case by accident — it gets its own diagnostic instead.
|
|
71
|
+
//
|
|
72
|
+
// Review finding F-1: an empty state.json means no actual state could be
|
|
73
|
+
// read — that is a loss of information, not a benign "zero missions"
|
|
74
|
+
// result (which only a successfully parsed, valid file can report).
|
|
75
|
+
// severity 'error' here is what makes render() treat this as INCOMPLETE
|
|
76
|
+
// (and --strict exit 2), the same way STATE_UNREADABLE already does.
|
|
77
|
+
if (raw.trim() === '') {
|
|
78
|
+
diagnostics.push({
|
|
79
|
+
code: 'STATE_EMPTY',
|
|
80
|
+
severity: 'error',
|
|
81
|
+
message: `${provenance.source} exists but is empty — no state could be read.`,
|
|
82
|
+
});
|
|
83
|
+
return { summary: { status: 'empty', provenance }, state: createInitialState() };
|
|
84
|
+
}
|
|
85
|
+
let parsedJson;
|
|
86
|
+
try {
|
|
87
|
+
parsedJson = JSON.parse(raw);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
91
|
+
diagnostics.push({
|
|
92
|
+
code: 'STATE_UNREADABLE',
|
|
93
|
+
severity: 'error',
|
|
94
|
+
message: `${provenance.source} is not valid JSON: ${msg}`,
|
|
95
|
+
});
|
|
96
|
+
return { summary: { status: 'unreadable', provenance }, state: createInitialState() };
|
|
97
|
+
}
|
|
98
|
+
const onDiskSchemaVersion = isPlainObject(parsedJson) && typeof parsedJson.schemaVersion === 'number' ? parsedJson.schemaVersion : undefined;
|
|
99
|
+
let state;
|
|
100
|
+
try {
|
|
101
|
+
// Supports schemaVersion 2 and an in-memory v1→v2 migration of legacy
|
|
102
|
+
// files — nothing is ever written back (section 4, #6/#7).
|
|
103
|
+
state = validateState(parsedJson);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
107
|
+
diagnostics.push({
|
|
108
|
+
code: 'STATE_UNREADABLE',
|
|
109
|
+
severity: 'error',
|
|
110
|
+
message: `${provenance.source} failed schema validation: ${msg}`,
|
|
111
|
+
});
|
|
112
|
+
return { summary: { status: 'unreadable', schemaVersionOnDisk: onDiskSchemaVersion, provenance }, state: createInitialState() };
|
|
113
|
+
}
|
|
114
|
+
if (onDiskSchemaVersion === 1) {
|
|
115
|
+
diagnostics.push({
|
|
116
|
+
code: 'STATE_LEGACY_SCHEMA_MIGRATED',
|
|
117
|
+
severity: 'info',
|
|
118
|
+
message: `${provenance.source} is schemaVersion 1 — read via the existing in-memory v1→v2 migration; nothing was written to disk.`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return { summary: { status: 'ok', schemaVersionOnDisk: onDiskSchemaVersion, provenance }, state };
|
|
122
|
+
}
|
|
123
|
+
function collectMissions(targetDir, monolithRoot, state, diagnostics) {
|
|
124
|
+
const active = selectActiveMissions(state);
|
|
125
|
+
const recentFinished = selectRecentFinishedMissions(state, RECENT_FINISHED_MISSIONS_LIMIT);
|
|
126
|
+
if (active.length > 1) {
|
|
127
|
+
diagnostics.push({
|
|
128
|
+
code: 'MULTIPLE_ACTIVE_MISSIONS',
|
|
129
|
+
severity: 'info',
|
|
130
|
+
message: `${active.length} missions are in_progress simultaneously: ${active.map((m) => m.id).join(', ')}.`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const filePath = stateFilePath(join(targetDir, monolithRoot));
|
|
134
|
+
return { active, recentFinished, provenance: { source: `${relative(targetDir, filePath)}#tactical.missions` } };
|
|
135
|
+
}
|
|
136
|
+
function collectCheckpoints(targetDir, monolithRoot, state, active, diagnostics) {
|
|
137
|
+
const filePath = stateFilePath(join(targetDir, monolithRoot));
|
|
138
|
+
const provenance = { source: `${relative(targetDir, filePath)}#tactical.checkpoints` };
|
|
139
|
+
return active.map((m) => {
|
|
140
|
+
const latest = selectLatestCheckpoint(state, m.id);
|
|
141
|
+
if (latest && hasCheckpointOrderMismatch(state, m.id, latest)) {
|
|
142
|
+
diagnostics.push({
|
|
143
|
+
code: 'CHECKPOINT_ORDER_MISMATCH',
|
|
144
|
+
severity: 'info',
|
|
145
|
+
message: `Mission "${m.id}": another checkpoint has a later createdAt than "${latest.id}" (the highest-ordinal one) — timestamp order and ID order disagree.`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return { missionId: m.id, latest, provenance };
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function collectStory(targetDir, monolithRoot, root, diagnostics) {
|
|
152
|
+
const activeMdAbs = join(targetDir, monolithRoot, ACTIVE_MD_REL);
|
|
153
|
+
const relPath = relative(targetDir, activeMdAbs);
|
|
154
|
+
// SEC-001A: never present a file outside the project as project context.
|
|
155
|
+
const escape = checkContainedFile(root, ACTIVE_MD_REL);
|
|
156
|
+
if (escape) {
|
|
157
|
+
diagnostics.push({
|
|
158
|
+
code: 'PATH_OUTSIDE_PROJECT',
|
|
159
|
+
severity: 'error',
|
|
160
|
+
message: `${relPath} was not read: ${escape.message}`,
|
|
161
|
+
});
|
|
162
|
+
return { fileExists: false, hasCurrentStorySection: false, excerpt: null, lineRange: null, provenance: null };
|
|
163
|
+
}
|
|
164
|
+
const read = readBoundedFileStable(activeMdAbs, relPath, { boundary: targetDir, maxBytes: MAX_ACTIVE_CONTEXT_BYTES, allowEmpty: true }, true);
|
|
165
|
+
if (!read.ok) {
|
|
166
|
+
// SEC-001B.1-R (M-1): three different situations, three different diagnostics.
|
|
167
|
+
// Only a really absent file is "missing" (a warning: a project may have no story
|
|
168
|
+
// yet). A file the safe-read policy refuses, or one that cannot be read, is an
|
|
169
|
+
// error: the story exists but was not shown, so the brief is INCOMPLETE. The
|
|
170
|
+
// detail never carries file content — only the label and the typed code.
|
|
171
|
+
if (read.code === 'FILE_MISSING') {
|
|
172
|
+
diagnostics.push({
|
|
173
|
+
code: 'ACTIVE_MD_MISSING',
|
|
174
|
+
severity: 'warn',
|
|
175
|
+
message: `${relPath} does not exist.`,
|
|
176
|
+
readCode: read.code,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else if (isReadSecurityViolation(read.code)) {
|
|
180
|
+
diagnostics.push({
|
|
181
|
+
code: 'ACTIVE_MD_REJECTED',
|
|
182
|
+
severity: 'error',
|
|
183
|
+
message: `${relPath} exists but was refused by the safe-read policy and not read: ${read.code}: ${read.detail}`,
|
|
184
|
+
readCode: read.code,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
diagnostics.push({
|
|
189
|
+
code: 'ACTIVE_MD_UNREADABLE',
|
|
190
|
+
severity: 'error',
|
|
191
|
+
message: `${relPath} exists but could not be read: ${read.code}: ${read.detail}`,
|
|
192
|
+
readCode: read.code,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return { fileExists: false, hasCurrentStorySection: false, excerpt: null, lineRange: null, provenance: null };
|
|
196
|
+
}
|
|
197
|
+
const content = 'content' in read ? read.content : '';
|
|
198
|
+
const extraction = extractCurrentStory(content);
|
|
199
|
+
if (!extraction.hasCurrentStorySection) {
|
|
200
|
+
diagnostics.push({
|
|
201
|
+
code: 'ACTIVE_MD_NO_CURRENT_STORY',
|
|
202
|
+
severity: 'info',
|
|
203
|
+
message: `${relPath} exists but has no "## Current Story" section.`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
fileExists: true,
|
|
208
|
+
hasCurrentStorySection: extraction.hasCurrentStorySection,
|
|
209
|
+
excerpt: extraction.excerpt,
|
|
210
|
+
lineRange: extraction.lineRange,
|
|
211
|
+
provenance: { source: extraction.lineRange ? `${relPath}:${extraction.lineRange}` : relPath },
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Best-effort read of one state document for the freshness check. A missing,
|
|
216
|
+
* refused or unreadable file simply contributes no evidence — freshness never
|
|
217
|
+
* adds a diagnostic of its own and never makes the brief INCOMPLETE.
|
|
218
|
+
*/
|
|
219
|
+
function readStateDocument(targetDir, monolithRoot, root, rel) {
|
|
220
|
+
if (checkContainedFile(root, rel))
|
|
221
|
+
return null;
|
|
222
|
+
const abs = join(targetDir, monolithRoot, rel);
|
|
223
|
+
const read = readBoundedFileStable(abs, relative(targetDir, abs), { boundary: targetDir, maxBytes: MAX_ACTIVE_CONTEXT_BYTES, allowEmpty: true }, true);
|
|
224
|
+
return read.ok && 'content' in read ? read.content : null;
|
|
225
|
+
}
|
|
226
|
+
function collectFreshness(targetDir, monolithRoot, root, story) {
|
|
227
|
+
const docs = [ACTIVE_MD_REL, ROADMAP_INDEX_REL, STORY_INDEX_REL, HANDOFF_REL];
|
|
228
|
+
const stateFiles = docs.map((rel) => posix.normalize(`${monolithRoot}/${rel}`));
|
|
229
|
+
const content = new Map(docs.map((rel) => [rel, readStateDocument(targetDir, monolithRoot, root, rel)]));
|
|
230
|
+
const roadmap = content.get(ROADMAP_INDEX_REL) ?? null;
|
|
231
|
+
const storyIndex = content.get(STORY_INDEX_REL) ?? null;
|
|
232
|
+
const evidence = {
|
|
233
|
+
git: readGitSignals(targetDir, stateFiles),
|
|
234
|
+
activeStory: story.fileExists && story.hasCurrentStorySection ? parseActiveStoryClaim(story.excerpt) : { kind: 'unknown' },
|
|
235
|
+
roadmapActiveStory: roadmap === null ? null : parseRoadmapActiveStory(roadmap),
|
|
236
|
+
roadmapStatuses: roadmap === null ? new Map() : parseRoadmapStatuses(roadmap),
|
|
237
|
+
storyIndexStatuses: storyIndex === null ? new Map() : parseStoryIndexStatuses(storyIndex),
|
|
238
|
+
recordedText: [...content.values()].filter((c) => c !== null).join('\n'),
|
|
239
|
+
stateFiles: new Set(stateFiles),
|
|
240
|
+
};
|
|
241
|
+
return { result: evaluateFreshness(evidence), evidence };
|
|
242
|
+
}
|
|
243
|
+
export async function collect(targetDir) {
|
|
244
|
+
const diagnostics = [];
|
|
245
|
+
const config = readBriefConfig(targetDir, diagnostics);
|
|
246
|
+
// Re-validates (and realpath-checks) the root readBriefConfig just accepted.
|
|
247
|
+
const root = resolveMonolithRootValue(targetDir, config.monolithRoot);
|
|
248
|
+
const monolithRootAbs = root.rootAbs;
|
|
249
|
+
const monolithInitialized = existsSync(monolithRootAbs);
|
|
250
|
+
if (!monolithInitialized) {
|
|
251
|
+
diagnostics.push({
|
|
252
|
+
code: 'MONOLITH_NOT_INITIALIZED',
|
|
253
|
+
severity: 'warn',
|
|
254
|
+
message: `No MONOLITH instance found at "${config.monolithRoot}" — nothing to summarize.`,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (monolithInitialized) {
|
|
258
|
+
// Read-only (SEC-001B.2): names only. A warning, never an error — it does
|
|
259
|
+
// not make the brief INCOMPLETE — and nothing is deleted or repaired.
|
|
260
|
+
const artifacts = findWriteArtifacts(monolithRootAbs);
|
|
261
|
+
if (artifacts.length > 0) {
|
|
262
|
+
diagnostics.push({
|
|
263
|
+
code: 'WRITE_ARTIFACTS_PRESENT',
|
|
264
|
+
severity: 'warn',
|
|
265
|
+
message: describeWriteArtifacts(artifacts, config.monolithRoot),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const identity = {
|
|
270
|
+
name: config.projectName,
|
|
271
|
+
monolithRoot: config.monolithRoot,
|
|
272
|
+
provenance: { source: 'monolith.config.json#project.name' },
|
|
273
|
+
};
|
|
274
|
+
const { summary: stateKeeper, state } = await readStateForBrief(targetDir, config.monolithRoot, root, diagnostics);
|
|
275
|
+
const missions = collectMissions(targetDir, config.monolithRoot, state, diagnostics);
|
|
276
|
+
const checkpoints = collectCheckpoints(targetDir, config.monolithRoot, state, missions.active, diagnostics);
|
|
277
|
+
const story = collectStory(targetDir, config.monolithRoot, root, diagnostics);
|
|
278
|
+
const { result: freshness, evidence: freshnessEvidence } = collectFreshness(targetDir, config.monolithRoot, root, story);
|
|
279
|
+
const protocolsRelDir = `${config.monolithRoot}/${PROTOCOLS_DIR}`;
|
|
280
|
+
const mandatoryProtocols = {
|
|
281
|
+
items: discoverDocs(targetDir, [protocolsRelDir], (fields) => fields.get('enforcement') === 'mandatory', diagnostics),
|
|
282
|
+
provenance: { source: protocolsRelDir },
|
|
283
|
+
};
|
|
284
|
+
const adrDirs = config.adrDirs;
|
|
285
|
+
const adrs = {
|
|
286
|
+
items: discoverDocs(targetDir, adrDirs, (fields) => fields.get('status') === 'accepted', diagnostics),
|
|
287
|
+
provenance: { source: adrDirs.join(', ') },
|
|
288
|
+
};
|
|
289
|
+
return {
|
|
290
|
+
identity,
|
|
291
|
+
monolithInitialized,
|
|
292
|
+
stateKeeper,
|
|
293
|
+
missions,
|
|
294
|
+
checkpoints,
|
|
295
|
+
story,
|
|
296
|
+
mandatoryProtocols,
|
|
297
|
+
adrs,
|
|
298
|
+
diagnostics,
|
|
299
|
+
freshness,
|
|
300
|
+
freshnessEvidence,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal `monolith.config.json` reader for the brief subsystem.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the existing `readMonolithRoot()` duplicated in
|
|
5
|
+
* `src/coordinatorium/cli.ts` and `src/validate/index.ts` — same
|
|
6
|
+
* "duplicate rather than couple CLI layers together" precedent. Adds one
|
|
7
|
+
* new, explicit, optional field: `contextGateway.adrDirs` (DEC-CONTEXT-003 /
|
|
8
|
+
* section 8 — the existing config contract had no way to name additional
|
|
9
|
+
* ADR directories, so this is the minimal extension to it, not a new
|
|
10
|
+
* configuration subsystem).
|
|
11
|
+
*
|
|
12
|
+
* Review finding F-6: a missing config file is a legitimate, silent
|
|
13
|
+
* default (no project has been configured yet). A *present but corrupted*
|
|
14
|
+
* config file is not the same thing and must not be masked as "healthy
|
|
15
|
+
* project, defaults apply" — it is reported via CONFIG_UNREADABLE and
|
|
16
|
+
* forces Completeness: INCOMPLETE, same severity model as STATE_UNREADABLE.
|
|
17
|
+
* Likewise, a present-but-wrong-shape `contextGateway.adrDirs` (not an
|
|
18
|
+
* array) is reported via CONFIG_ADR_DIRS_INVALID rather than silently
|
|
19
|
+
* collapsing to `[]`. Non-string entries *within* an otherwise-valid array
|
|
20
|
+
* are dropped with a lower-severity (warn) note — a partially-junk list is
|
|
21
|
+
* a lesser problem than the field having the wrong shape entirely.
|
|
22
|
+
*/
|
|
23
|
+
import type { BriefDiagnostic } from './types.js';
|
|
24
|
+
export interface BriefConfig {
|
|
25
|
+
monolithRoot: string;
|
|
26
|
+
projectName: string;
|
|
27
|
+
/**
|
|
28
|
+
* ADR directories to scan, relative to targetDir. Defaults to the canonical
|
|
29
|
+
* `docs/adr`; `monolith.config.json`'s `contextGateway.adrDirs` replaces
|
|
30
|
+
* (does not extend) that default. No legacy directory is scanned implicitly.
|
|
31
|
+
*/
|
|
32
|
+
adrDirs: string[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Throws RootContainmentError when `monolithRoot` violates the SEC-001A
|
|
36
|
+
* containment policy (see src/root) — the brief never degrades to a default
|
|
37
|
+
* root in that case.
|
|
38
|
+
*/
|
|
39
|
+
export declare function readBriefConfig(targetDir: string, diagnostics?: BriefDiagnostic[]): BriefConfig;
|
|
40
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/brief/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAMD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,GAAE,eAAe,EAAO,GAAG,WAAW,CAuDnG"}
|