@latysh-development/monolith 0.0.0-stage → 0.1.0-alpha.1
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 +179 -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 +342 -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 +12 -0
- package/dist/brief/constants.d.ts.map +1 -0
- package/dist/brief/constants.js +11 -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 +67 -0
- package/dist/brief/render.d.ts.map +1 -0
- package/dist/brief/render.js +458 -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 +126 -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 +199 -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/discovery/compose.d.ts +28 -0
- package/dist/discovery/compose.d.ts.map +1 -0
- package/dist/discovery/compose.js +159 -0
- package/dist/discovery/contracts.d.ts +211 -0
- package/dist/discovery/contracts.d.ts.map +1 -0
- package/dist/discovery/contracts.js +555 -0
- package/dist/discovery/docs.d.ts +278 -0
- package/dist/discovery/docs.d.ts.map +1 -0
- package/dist/discovery/docs.js +1182 -0
- package/dist/discovery/facts.d.ts +49 -0
- package/dist/discovery/facts.d.ts.map +1 -0
- package/dist/discovery/facts.js +541 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +9 -0
- package/dist/discovery/orchestrate.d.ts +35 -0
- package/dist/discovery/orchestrate.d.ts.map +1 -0
- package/dist/discovery/orchestrate.js +32 -0
- package/dist/discovery/render.d.ts +36 -0
- package/dist/discovery/render.d.ts.map +1 -0
- package/dist/discovery/render.js +278 -0
- package/dist/discovery/traverse.d.ts +197 -0
- package/dist/discovery/traverse.d.ts.map +1 -0
- package/dist/discovery/traverse.js +510 -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 +192 -0
- package/dist/durable/errors.d.ts.map +1 -0
- package/dist/durable/errors.js +323 -0
- package/dist/durable/fs-store.d.ts +254 -0
- package/dist/durable/fs-store.d.ts.map +1 -0
- package/dist/durable/fs-store.js +1493 -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 +34 -0
- package/dist/durable/prune-check.d.ts.map +1 -0
- package/dist/durable/prune-check.js +72 -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 +499 -0
- package/dist/durable/runtime.d.ts.map +1 -0
- package/dist/durable/runtime.js +1288 -0
- package/dist/durable/seams.d.ts +44 -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/ideas/cli.d.ts +33 -0
- package/dist/ideas/cli.d.ts.map +1 -0
- package/dist/ideas/cli.js +158 -0
- package/dist/ideas/format.d.ts +38 -0
- package/dist/ideas/format.d.ts.map +1 -0
- package/dist/ideas/format.js +194 -0
- package/dist/ideas/index.d.ts +6 -0
- package/dist/ideas/index.d.ts.map +1 -0
- package/dist/ideas/index.js +3 -0
- package/dist/ideas/store.d.ts +55 -0
- package/dist/ideas/store.d.ts.map +1 -0
- package/dist/ideas/store.js +262 -0
- package/dist/ideas/types.d.ts +54 -0
- package/dist/ideas/types.d.ts.map +1 -0
- package/dist/ideas/types.js +26 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -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 +29 -0
- package/dist/init/scaffold.d.ts.map +1 -0
- package/dist/init/scaffold.js +208 -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/managed/index.d.ts +73 -0
- package/dist/managed/index.d.ts.map +1 -0
- package/dist/managed/index.js +222 -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 +186 -0
- package/dist/root/index.d.ts.map +1 -0
- package/dist/root/index.js +430 -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/update/cli.d.ts +8 -0
- package/dist/update/cli.d.ts.map +1 -0
- package/dist/update/cli.js +115 -0
- package/dist/update/index.d.ts +78 -0
- package/dist/update/index.d.ts.map +1 -0
- package/dist/update/index.js +285 -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 +59 -0
- package/templates/default-instance/instruction/.windsurfrules +59 -0
- package/templates/default-instance/instruction/CLAUDE.md +57 -0
- package/templates/default-instance/managed/agent.md +20 -0
- package/templates/default-instance/managed/bootstrap.md +7 -0
- package/templates/default-instance/managed/index-idea.md +5 -0
- package/templates/default-instance/managed/routing-idea.md +14 -0
- package/templates/default-instance/monolith/AGENT.md +1 -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 +151 -0
- package/templates/default-instance/monolith/context/maps/semantic-map.md +75 -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 +32 -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/idea-capture/SKILL.md +157 -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 +111 -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,342 @@
|
|
|
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, PARKED_IDEAS_LIMIT, 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
|
+
import { ideaStore, listIdeas } from '../ideas/index.js';
|
|
25
|
+
import { ideaNumber } from '../ideas/format.js';
|
|
26
|
+
const RECENT_FINISHED_MISSIONS_LIMIT = 5;
|
|
27
|
+
/** SEC-001B.1: `active.md` is a short hand-written context file (~3 KB today). */
|
|
28
|
+
export const MAX_ACTIVE_CONTEXT_BYTES = 256 * 1024;
|
|
29
|
+
function isPlainObject(v) {
|
|
30
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
31
|
+
}
|
|
32
|
+
async function readStateForBrief(targetDir, monolithRoot, root, diagnostics) {
|
|
33
|
+
const stateDir = join(targetDir, monolithRoot);
|
|
34
|
+
const filePath = stateFilePath(stateDir);
|
|
35
|
+
const provenance = { source: relative(targetDir, filePath) };
|
|
36
|
+
// SEC-001A: never read a state.json that resolves outside the project.
|
|
37
|
+
const escape = checkContainedFile(root, 'state.json');
|
|
38
|
+
if (escape) {
|
|
39
|
+
diagnostics.push({
|
|
40
|
+
code: 'PATH_OUTSIDE_PROJECT',
|
|
41
|
+
severity: 'error',
|
|
42
|
+
message: `${provenance.source} was not read: ${escape.message}`,
|
|
43
|
+
});
|
|
44
|
+
return { summary: { status: 'unreadable', provenance }, state: createInitialState() };
|
|
45
|
+
}
|
|
46
|
+
// Rule (section 4, #1): existence is established by the same bounded,
|
|
47
|
+
// non-blocking open that reads the file (SEC-001B.1) — a FIFO or an
|
|
48
|
+
// over-limit file is rejected before any content is read.
|
|
49
|
+
const read = readBoundedFileStable(filePath, provenance.source, { boundary: targetDir, maxBytes: MAX_STATE_BYTES, allowEmpty: true }, true);
|
|
50
|
+
if (!read.ok && read.code === 'FILE_MISSING') {
|
|
51
|
+
diagnostics.push({
|
|
52
|
+
code: 'STATE_NOT_INITIALIZED',
|
|
53
|
+
severity: 'info',
|
|
54
|
+
message: `${provenance.source} does not exist — no missions have ever been recorded.`,
|
|
55
|
+
readCode: 'FILE_MISSING',
|
|
56
|
+
});
|
|
57
|
+
return { summary: { status: 'absent', provenance }, state: createInitialState() };
|
|
58
|
+
}
|
|
59
|
+
if (!read.ok) {
|
|
60
|
+
diagnostics.push({
|
|
61
|
+
code: 'STATE_UNREADABLE',
|
|
62
|
+
severity: 'error',
|
|
63
|
+
message: `${provenance.source} exists but could not be read: ${read.code}: ${read.detail}`,
|
|
64
|
+
readCode: read.code,
|
|
65
|
+
});
|
|
66
|
+
return { summary: { status: 'unreadable', provenance }, state: createInitialState() };
|
|
67
|
+
}
|
|
68
|
+
const raw = 'content' in read ? read.content : '';
|
|
69
|
+
// A literally empty file is distinguished from corrupted content
|
|
70
|
+
// (DEC-CONTEXT-002: "corrupted state.json must not be treated as empty
|
|
71
|
+
// state") precisely so the STATE_UNREADABLE path below is never reached
|
|
72
|
+
// for this case by accident — it gets its own diagnostic instead.
|
|
73
|
+
//
|
|
74
|
+
// Review finding F-1: an empty state.json means no actual state could be
|
|
75
|
+
// read — that is a loss of information, not a benign "zero missions"
|
|
76
|
+
// result (which only a successfully parsed, valid file can report).
|
|
77
|
+
// severity 'error' here is what makes render() treat this as INCOMPLETE
|
|
78
|
+
// (and --strict exit 2), the same way STATE_UNREADABLE already does.
|
|
79
|
+
if (raw.trim() === '') {
|
|
80
|
+
diagnostics.push({
|
|
81
|
+
code: 'STATE_EMPTY',
|
|
82
|
+
severity: 'error',
|
|
83
|
+
message: `${provenance.source} exists but is empty — no state could be read.`,
|
|
84
|
+
});
|
|
85
|
+
return { summary: { status: 'empty', provenance }, state: createInitialState() };
|
|
86
|
+
}
|
|
87
|
+
let parsedJson;
|
|
88
|
+
try {
|
|
89
|
+
parsedJson = JSON.parse(raw);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
93
|
+
diagnostics.push({
|
|
94
|
+
code: 'STATE_UNREADABLE',
|
|
95
|
+
severity: 'error',
|
|
96
|
+
message: `${provenance.source} is not valid JSON: ${msg}`,
|
|
97
|
+
});
|
|
98
|
+
return { summary: { status: 'unreadable', provenance }, state: createInitialState() };
|
|
99
|
+
}
|
|
100
|
+
const onDiskSchemaVersion = isPlainObject(parsedJson) && typeof parsedJson.schemaVersion === 'number' ? parsedJson.schemaVersion : undefined;
|
|
101
|
+
let state;
|
|
102
|
+
try {
|
|
103
|
+
// Supports schemaVersion 2 and an in-memory v1→v2 migration of legacy
|
|
104
|
+
// files — nothing is ever written back (section 4, #6/#7).
|
|
105
|
+
state = validateState(parsedJson);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
109
|
+
diagnostics.push({
|
|
110
|
+
code: 'STATE_UNREADABLE',
|
|
111
|
+
severity: 'error',
|
|
112
|
+
message: `${provenance.source} failed schema validation: ${msg}`,
|
|
113
|
+
});
|
|
114
|
+
return { summary: { status: 'unreadable', schemaVersionOnDisk: onDiskSchemaVersion, provenance }, state: createInitialState() };
|
|
115
|
+
}
|
|
116
|
+
if (onDiskSchemaVersion === 1) {
|
|
117
|
+
diagnostics.push({
|
|
118
|
+
code: 'STATE_LEGACY_SCHEMA_MIGRATED',
|
|
119
|
+
severity: 'info',
|
|
120
|
+
message: `${provenance.source} is schemaVersion 1 — read via the existing in-memory v1→v2 migration; nothing was written to disk.`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return { summary: { status: 'ok', schemaVersionOnDisk: onDiskSchemaVersion, provenance }, state };
|
|
124
|
+
}
|
|
125
|
+
function collectMissions(targetDir, monolithRoot, state, diagnostics) {
|
|
126
|
+
const active = selectActiveMissions(state);
|
|
127
|
+
const recentFinished = selectRecentFinishedMissions(state, RECENT_FINISHED_MISSIONS_LIMIT);
|
|
128
|
+
if (active.length > 1) {
|
|
129
|
+
diagnostics.push({
|
|
130
|
+
code: 'MULTIPLE_ACTIVE_MISSIONS',
|
|
131
|
+
severity: 'info',
|
|
132
|
+
message: `${active.length} missions are in_progress simultaneously: ${active.map((m) => m.id).join(', ')}.`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const filePath = stateFilePath(join(targetDir, monolithRoot));
|
|
136
|
+
return { active, recentFinished, provenance: { source: `${relative(targetDir, filePath)}#tactical.missions` } };
|
|
137
|
+
}
|
|
138
|
+
function collectCheckpoints(targetDir, monolithRoot, state, active, diagnostics) {
|
|
139
|
+
const filePath = stateFilePath(join(targetDir, monolithRoot));
|
|
140
|
+
const provenance = { source: `${relative(targetDir, filePath)}#tactical.checkpoints` };
|
|
141
|
+
return active.map((m) => {
|
|
142
|
+
const latest = selectLatestCheckpoint(state, m.id);
|
|
143
|
+
if (latest && hasCheckpointOrderMismatch(state, m.id, latest)) {
|
|
144
|
+
diagnostics.push({
|
|
145
|
+
code: 'CHECKPOINT_ORDER_MISMATCH',
|
|
146
|
+
severity: 'info',
|
|
147
|
+
message: `Mission "${m.id}": another checkpoint has a later createdAt than "${latest.id}" (the highest-ordinal one) — timestamp order and ID order disagree.`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return { missionId: m.id, latest, provenance };
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function collectStory(targetDir, monolithRoot, root, diagnostics) {
|
|
154
|
+
const activeMdAbs = join(targetDir, monolithRoot, ACTIVE_MD_REL);
|
|
155
|
+
const relPath = relative(targetDir, activeMdAbs);
|
|
156
|
+
// SEC-001A: never present a file outside the project as project context.
|
|
157
|
+
const escape = checkContainedFile(root, ACTIVE_MD_REL);
|
|
158
|
+
if (escape) {
|
|
159
|
+
diagnostics.push({
|
|
160
|
+
code: 'PATH_OUTSIDE_PROJECT',
|
|
161
|
+
severity: 'error',
|
|
162
|
+
message: `${relPath} was not read: ${escape.message}`,
|
|
163
|
+
});
|
|
164
|
+
return { fileExists: false, hasCurrentStorySection: false, excerpt: null, lineRange: null, provenance: null };
|
|
165
|
+
}
|
|
166
|
+
const read = readBoundedFileStable(activeMdAbs, relPath, { boundary: targetDir, maxBytes: MAX_ACTIVE_CONTEXT_BYTES, allowEmpty: true }, true);
|
|
167
|
+
if (!read.ok) {
|
|
168
|
+
// SEC-001B.1-R (M-1): three different situations, three different diagnostics.
|
|
169
|
+
// Only a really absent file is "missing" (a warning: a project may have no story
|
|
170
|
+
// yet). A file the safe-read policy refuses, or one that cannot be read, is an
|
|
171
|
+
// error: the story exists but was not shown, so the brief is INCOMPLETE. The
|
|
172
|
+
// detail never carries file content — only the label and the typed code.
|
|
173
|
+
if (read.code === 'FILE_MISSING') {
|
|
174
|
+
diagnostics.push({
|
|
175
|
+
code: 'ACTIVE_MD_MISSING',
|
|
176
|
+
severity: 'warn',
|
|
177
|
+
message: `${relPath} does not exist.`,
|
|
178
|
+
readCode: read.code,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
else if (isReadSecurityViolation(read.code)) {
|
|
182
|
+
diagnostics.push({
|
|
183
|
+
code: 'ACTIVE_MD_REJECTED',
|
|
184
|
+
severity: 'error',
|
|
185
|
+
message: `${relPath} exists but was refused by the safe-read policy and not read: ${read.code}: ${read.detail}`,
|
|
186
|
+
readCode: read.code,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
diagnostics.push({
|
|
191
|
+
code: 'ACTIVE_MD_UNREADABLE',
|
|
192
|
+
severity: 'error',
|
|
193
|
+
message: `${relPath} exists but could not be read: ${read.code}: ${read.detail}`,
|
|
194
|
+
readCode: read.code,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return { fileExists: false, hasCurrentStorySection: false, excerpt: null, lineRange: null, provenance: null };
|
|
198
|
+
}
|
|
199
|
+
const content = 'content' in read ? read.content : '';
|
|
200
|
+
const extraction = extractCurrentStory(content);
|
|
201
|
+
if (!extraction.hasCurrentStorySection) {
|
|
202
|
+
diagnostics.push({
|
|
203
|
+
code: 'ACTIVE_MD_NO_CURRENT_STORY',
|
|
204
|
+
severity: 'info',
|
|
205
|
+
message: `${relPath} exists but has no "## Current Story" section.`,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
fileExists: true,
|
|
210
|
+
hasCurrentStorySection: extraction.hasCurrentStorySection,
|
|
211
|
+
excerpt: extraction.excerpt,
|
|
212
|
+
lineRange: extraction.lineRange,
|
|
213
|
+
provenance: { source: extraction.lineRange ? `${relPath}:${extraction.lineRange}` : relPath },
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const IDEAS_UNREADABLE_LISTED = 3;
|
|
217
|
+
/**
|
|
218
|
+
* IDEA-002A: the `inbox` ideas, via the Idea Capture reader (`listIdeas`: bounded,
|
|
219
|
+
* symlink/hardlink/special-file refusing, never writes). A file that cannot be read
|
|
220
|
+
* as an idea is not counted and is reported as one warning — it never breaks the
|
|
221
|
+
* brief. Idea content is untrusted; only ids and titles are kept.
|
|
222
|
+
*/
|
|
223
|
+
function collectParkedIdeas(targetDir, diagnostics) {
|
|
224
|
+
const empty = { total: 0, items: [] };
|
|
225
|
+
let entries;
|
|
226
|
+
try {
|
|
227
|
+
entries = listIdeas(ideaStore(targetDir));
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
const code = err instanceof Error && 'code' in err ? String(err.code) : 'ERROR';
|
|
231
|
+
diagnostics.push({
|
|
232
|
+
code: 'IDEAS_UNREADABLE',
|
|
233
|
+
severity: 'warn',
|
|
234
|
+
message: `The ideas directory was not read (${code}); parked ideas are not shown.`,
|
|
235
|
+
});
|
|
236
|
+
return empty;
|
|
237
|
+
}
|
|
238
|
+
const inbox = entries.flatMap((e) => (e.ok && e.idea.status === 'inbox' ? [e.idea] : []));
|
|
239
|
+
inbox.sort((a, b) => ideaNumber(b.id) - ideaNumber(a.id));
|
|
240
|
+
const bad = entries.flatMap((e) => (e.ok ? [] : [e]));
|
|
241
|
+
if (bad.length > 0) {
|
|
242
|
+
const listed = bad.slice(0, IDEAS_UNREADABLE_LISTED).map((e) => `${e.id} (${e.code})`).join(', ');
|
|
243
|
+
const more = bad.length > IDEAS_UNREADABLE_LISTED ? `, and ${bad.length - IDEAS_UNREADABLE_LISTED} more` : '';
|
|
244
|
+
diagnostics.push({
|
|
245
|
+
code: 'IDEAS_UNREADABLE',
|
|
246
|
+
severity: 'warn',
|
|
247
|
+
message: `${bad.length} idea file(s) could not be read and are not counted as parked: ${listed}${more}. Run \`monolith idea show <id>\`.`,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return { total: inbox.length, items: inbox.slice(0, PARKED_IDEAS_LIMIT).map((i) => ({ id: i.id, title: i.title })) };
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Best-effort read of one state document for the freshness check. A missing,
|
|
254
|
+
* refused or unreadable file simply contributes no evidence — freshness never
|
|
255
|
+
* adds a diagnostic of its own and never makes the brief INCOMPLETE.
|
|
256
|
+
*/
|
|
257
|
+
function readStateDocument(targetDir, monolithRoot, root, rel) {
|
|
258
|
+
if (checkContainedFile(root, rel))
|
|
259
|
+
return null;
|
|
260
|
+
const abs = join(targetDir, monolithRoot, rel);
|
|
261
|
+
const read = readBoundedFileStable(abs, relative(targetDir, abs), { boundary: targetDir, maxBytes: MAX_ACTIVE_CONTEXT_BYTES, allowEmpty: true }, true);
|
|
262
|
+
return read.ok && 'content' in read ? read.content : null;
|
|
263
|
+
}
|
|
264
|
+
function collectFreshness(targetDir, monolithRoot, root, story) {
|
|
265
|
+
const docs = [ACTIVE_MD_REL, ROADMAP_INDEX_REL, STORY_INDEX_REL, HANDOFF_REL];
|
|
266
|
+
const stateFiles = docs.map((rel) => posix.normalize(`${monolithRoot}/${rel}`));
|
|
267
|
+
const content = new Map(docs.map((rel) => [rel, readStateDocument(targetDir, monolithRoot, root, rel)]));
|
|
268
|
+
const roadmap = content.get(ROADMAP_INDEX_REL) ?? null;
|
|
269
|
+
const storyIndex = content.get(STORY_INDEX_REL) ?? null;
|
|
270
|
+
const evidence = {
|
|
271
|
+
git: readGitSignals(targetDir, stateFiles),
|
|
272
|
+
activeStory: story.fileExists && story.hasCurrentStorySection ? parseActiveStoryClaim(story.excerpt) : { kind: 'unknown' },
|
|
273
|
+
roadmapActiveStory: roadmap === null ? null : parseRoadmapActiveStory(roadmap),
|
|
274
|
+
roadmapStatuses: roadmap === null ? new Map() : parseRoadmapStatuses(roadmap),
|
|
275
|
+
storyIndexStatuses: storyIndex === null ? new Map() : parseStoryIndexStatuses(storyIndex),
|
|
276
|
+
recordedText: [...content.values()].filter((c) => c !== null).join('\n'),
|
|
277
|
+
stateFiles: new Set(stateFiles),
|
|
278
|
+
};
|
|
279
|
+
return { result: evaluateFreshness(evidence), evidence };
|
|
280
|
+
}
|
|
281
|
+
export async function collect(targetDir) {
|
|
282
|
+
const diagnostics = [];
|
|
283
|
+
const config = readBriefConfig(targetDir, diagnostics);
|
|
284
|
+
// Re-validates (and realpath-checks) the root readBriefConfig just accepted.
|
|
285
|
+
const root = resolveMonolithRootValue(targetDir, config.monolithRoot);
|
|
286
|
+
const monolithRootAbs = root.rootAbs;
|
|
287
|
+
const monolithInitialized = existsSync(monolithRootAbs);
|
|
288
|
+
if (!monolithInitialized) {
|
|
289
|
+
diagnostics.push({
|
|
290
|
+
code: 'MONOLITH_NOT_INITIALIZED',
|
|
291
|
+
severity: 'warn',
|
|
292
|
+
message: `No MONOLITH instance found at "${config.monolithRoot}" — nothing to summarize.`,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (monolithInitialized) {
|
|
296
|
+
// Read-only (SEC-001B.2): names only. A warning, never an error — it does
|
|
297
|
+
// not make the brief INCOMPLETE — and nothing is deleted or repaired.
|
|
298
|
+
const artifacts = findWriteArtifacts(monolithRootAbs);
|
|
299
|
+
if (artifacts.length > 0) {
|
|
300
|
+
diagnostics.push({
|
|
301
|
+
code: 'WRITE_ARTIFACTS_PRESENT',
|
|
302
|
+
severity: 'warn',
|
|
303
|
+
message: describeWriteArtifacts(artifacts, config.monolithRoot),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const identity = {
|
|
308
|
+
name: config.projectName,
|
|
309
|
+
monolithRoot: config.monolithRoot,
|
|
310
|
+
provenance: { source: 'monolith.config.json#project.name' },
|
|
311
|
+
};
|
|
312
|
+
const { summary: stateKeeper, state } = await readStateForBrief(targetDir, config.monolithRoot, root, diagnostics);
|
|
313
|
+
const missions = collectMissions(targetDir, config.monolithRoot, state, diagnostics);
|
|
314
|
+
const checkpoints = collectCheckpoints(targetDir, config.monolithRoot, state, missions.active, diagnostics);
|
|
315
|
+
const story = collectStory(targetDir, config.monolithRoot, root, diagnostics);
|
|
316
|
+
const { result: freshness, evidence: freshnessEvidence } = collectFreshness(targetDir, config.monolithRoot, root, story);
|
|
317
|
+
const protocolsRelDir = `${config.monolithRoot}/${PROTOCOLS_DIR}`;
|
|
318
|
+
const mandatoryProtocols = {
|
|
319
|
+
items: discoverDocs(targetDir, [protocolsRelDir], (fields) => fields.get('enforcement') === 'mandatory', diagnostics),
|
|
320
|
+
provenance: { source: protocolsRelDir },
|
|
321
|
+
};
|
|
322
|
+
const adrDirs = config.adrDirs;
|
|
323
|
+
const adrs = {
|
|
324
|
+
items: discoverDocs(targetDir, adrDirs, (fields) => fields.get('status') === 'accepted', diagnostics),
|
|
325
|
+
provenance: { source: adrDirs.join(', ') },
|
|
326
|
+
};
|
|
327
|
+
const parkedIdeas = monolithInitialized ? collectParkedIdeas(targetDir, diagnostics) : { total: 0, items: [] };
|
|
328
|
+
return {
|
|
329
|
+
identity,
|
|
330
|
+
monolithInitialized,
|
|
331
|
+
stateKeeper,
|
|
332
|
+
missions,
|
|
333
|
+
checkpoints,
|
|
334
|
+
story,
|
|
335
|
+
mandatoryProtocols,
|
|
336
|
+
adrs,
|
|
337
|
+
parkedIdeas,
|
|
338
|
+
diagnostics,
|
|
339
|
+
freshness,
|
|
340
|
+
freshnessEvidence,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
@@ -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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { readConfigFile, resolveMonolithRoot, DEFAULT_MONOLITH_ROOT } from '../root/index.js';
|
|
24
|
+
import { DEFAULT_ADR_DIR } from './constants.js';
|
|
25
|
+
const DEFAULT_PROJECT_NAME = 'project';
|
|
26
|
+
function errMsg(err) {
|
|
27
|
+
return err instanceof Error ? err.message : String(err);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Throws RootContainmentError when `monolithRoot` violates the SEC-001A
|
|
31
|
+
* containment policy (see src/root) — the brief never degrades to a default
|
|
32
|
+
* root in that case.
|
|
33
|
+
*/
|
|
34
|
+
export function readBriefConfig(targetDir, diagnostics = []) {
|
|
35
|
+
const defaults = { monolithRoot: DEFAULT_MONOLITH_ROOT, projectName: DEFAULT_PROJECT_NAME, adrDirs: [DEFAULT_ADR_DIR] };
|
|
36
|
+
const read = readConfigFile(targetDir);
|
|
37
|
+
if (read.status === 'absent') {
|
|
38
|
+
// Absent config is a legitimate, unconfigured project — not an error.
|
|
39
|
+
return defaults;
|
|
40
|
+
}
|
|
41
|
+
if (read.status !== 'ok') {
|
|
42
|
+
const message = read.status === 'unreadable'
|
|
43
|
+
? `monolith.config.json exists but could not be read: ${read.detail}. Proceeding with defaults.`
|
|
44
|
+
: read.status === 'invalid-json'
|
|
45
|
+
? `monolith.config.json is not valid JSON: ${read.detail}. Proceeding with defaults.`
|
|
46
|
+
: 'monolith.config.json does not contain a JSON object. Proceeding with defaults.';
|
|
47
|
+
diagnostics.push({ code: 'CONFIG_UNREADABLE', severity: 'error', message });
|
|
48
|
+
return defaults;
|
|
49
|
+
}
|
|
50
|
+
const cfg = read.cfg;
|
|
51
|
+
const monolithRoot = resolveMonolithRoot(targetDir, read).monolithRoot;
|
|
52
|
+
let projectName = DEFAULT_PROJECT_NAME;
|
|
53
|
+
const project = cfg.project;
|
|
54
|
+
if (typeof project === 'object' && project !== null && !Array.isArray(project)) {
|
|
55
|
+
const name = project.name;
|
|
56
|
+
if (typeof name === 'string' && name.length > 0)
|
|
57
|
+
projectName = name;
|
|
58
|
+
}
|
|
59
|
+
let adrDirs = [DEFAULT_ADR_DIR];
|
|
60
|
+
const contextGateway = cfg.contextGateway;
|
|
61
|
+
if (typeof contextGateway === 'object' && contextGateway !== null && !Array.isArray(contextGateway)) {
|
|
62
|
+
const configured = contextGateway.adrDirs;
|
|
63
|
+
if (configured !== undefined) {
|
|
64
|
+
if (!Array.isArray(configured)) {
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
code: 'CONFIG_ADR_DIRS_INVALID',
|
|
67
|
+
severity: 'error',
|
|
68
|
+
message: 'monolith.config.json#contextGateway.adrDirs is present but is not an array; using the default ADR directory.',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const stringEntries = configured.filter((d) => typeof d === 'string' && d.length > 0);
|
|
73
|
+
if (stringEntries.length !== configured.length) {
|
|
74
|
+
diagnostics.push({
|
|
75
|
+
code: 'CONFIG_ADR_DIRS_INVALID',
|
|
76
|
+
severity: 'warn',
|
|
77
|
+
message: 'monolith.config.json#contextGateway.adrDirs contains non-string entries; they were dropped.',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
adrDirs = stringEntries;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { monolithRoot, projectName, adrDirs };
|
|
85
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Shared path constants for the brief subsystem (relative to monolithRoot). */
|
|
2
|
+
export declare const ACTIVE_MD_REL = "memory/working/active.md";
|
|
3
|
+
/** Canonical ADR directory, relative to the project root (not monolithRoot). Used when `contextGateway.adrDirs` is not configured. */
|
|
4
|
+
export declare const DEFAULT_ADR_DIR = "docs/adr";
|
|
5
|
+
export declare const PROTOCOLS_DIR = "protocols";
|
|
6
|
+
/** State documents whose last commit marks the "last recorded state update" (SELF-AWARENESS-001). */
|
|
7
|
+
export declare const ROADMAP_INDEX_REL = "context/maps/roadmap-index.md";
|
|
8
|
+
export declare const STORY_INDEX_REL = "memory/working/story-index.md";
|
|
9
|
+
export declare const HANDOFF_REL = "context/boot/HANDOFF.md";
|
|
10
|
+
/** IDEA-002A: most parked ideas the brief ever lists; the rest is a "+N more" count. */
|
|
11
|
+
export declare const PARKED_IDEAS_LIMIT = 3;
|
|
12
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/brief/constants.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,aAAa,6BAA6B,CAAC;AACxD,sIAAsI;AACtI,eAAO,MAAM,eAAe,aAAa,CAAC;AAC1C,eAAO,MAAM,aAAa,cAAc,CAAC;AACzC,qGAAqG;AACrG,eAAO,MAAM,iBAAiB,kCAAkC,CAAC;AACjE,eAAO,MAAM,eAAe,kCAAkC,CAAC;AAC/D,eAAO,MAAM,WAAW,4BAA4B,CAAC;AACrD,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,IAAI,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Shared path constants for the brief subsystem (relative to monolithRoot). */
|
|
2
|
+
export const ACTIVE_MD_REL = 'memory/working/active.md';
|
|
3
|
+
/** Canonical ADR directory, relative to the project root (not monolithRoot). Used when `contextGateway.adrDirs` is not configured. */
|
|
4
|
+
export const DEFAULT_ADR_DIR = 'docs/adr';
|
|
5
|
+
export const PROTOCOLS_DIR = 'protocols';
|
|
6
|
+
/** State documents whose last commit marks the "last recorded state update" (SELF-AWARENESS-001). */
|
|
7
|
+
export const ROADMAP_INDEX_REL = 'context/maps/roadmap-index.md';
|
|
8
|
+
export const STORY_INDEX_REL = 'memory/working/story-index.md';
|
|
9
|
+
export const HANDOFF_REL = 'context/boot/HANDOFF.md';
|
|
10
|
+
/** IDEA-002A: most parked ideas the brief ever lists; the rest is a "+N more" count. */
|
|
11
|
+
export const PARKED_IDEAS_LIMIT = 3;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic, deterministic markdown-frontmatter discovery used for both
|
|
3
|
+
* Mandatory Protocols (section 7) and ADR Discovery (section 8). Reuses
|
|
4
|
+
* Scriptorium's existing frontmatter parser (per the brief contract) — does
|
|
5
|
+
* NOT use `compileMarkdownArtifact()` or the ERG graph builder, both of
|
|
6
|
+
* which are explicitly out of scope here.
|
|
7
|
+
*
|
|
8
|
+
* Resilience (review findings F-2/F-3): a single unreadable directory,
|
|
9
|
+
* unreadable file, dangling symlink, symlink cycle, or a configured
|
|
10
|
+
* directory that escapes the project root must never crash the whole brief
|
|
11
|
+
* or silently disappear. Each failure is caught, reported as a diagnostic
|
|
12
|
+
* (forcing Completeness: INCOMPLETE), and discovery continues with
|
|
13
|
+
* whatever else is readable.
|
|
14
|
+
*
|
|
15
|
+
* Symlinks (file or directory entries) are never followed while walking —
|
|
16
|
+
* this is what makes a symlink cycle (ELOOP) impossible by construction,
|
|
17
|
+
* and is also the first line of defense against using a nested symlink to
|
|
18
|
+
* tunnel outside a configured directory. The *top-level* directories named
|
|
19
|
+
* in `dirs` (i.e. `contextGateway.adrDirs` and the standard protocols
|
|
20
|
+
* dir) get an explicit containment check in addition: both a string-level
|
|
21
|
+
* check (rejects `..` segments in the configured path itself) and a
|
|
22
|
+
* real-path check after resolving symlinks (rejects a configured directory
|
|
23
|
+
* that is itself a symlink escaping the project root). Neither check alone
|
|
24
|
+
* is treated as sufficient — path.join()/path.relative() string comparison
|
|
25
|
+
* alone does not account for symlinks on disk.
|
|
26
|
+
*
|
|
27
|
+
* `monolithRoot` itself, and the fixed files below it (state.json,
|
|
28
|
+
* active.md), are vetted separately by the shared resolver in src/root
|
|
29
|
+
* (SEC-001A); this check covers document directories only.
|
|
30
|
+
*/
|
|
31
|
+
import type { BriefDiagnostic, DocRef } from './types.js';
|
|
32
|
+
/** SEC-001B.1: protocol / ADR documents (largest in this repo is ~80 KB). */
|
|
33
|
+
export declare const MAX_MARKDOWN_DOC_BYTES: number;
|
|
34
|
+
export interface ParsedDoc {
|
|
35
|
+
/** Relative to targetDir. */
|
|
36
|
+
relPath: string;
|
|
37
|
+
id: string | undefined;
|
|
38
|
+
/** frontmatter `title:`, or (only when no frontmatter title exists) the first H1 — resolved once, at parse time. */
|
|
39
|
+
resolvedTitle: string | undefined;
|
|
40
|
+
/** All frontmatter fields (first value per key), for filter predicates. */
|
|
41
|
+
fields: Map<string, string>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Discover documents under `dirs` (relative to targetDir, deduplicated,
|
|
45
|
+
* each walked recursively) whose frontmatter matches `predicate`. Only
|
|
46
|
+
* documents with a usable frontmatter `id:` are included — a markdown file
|
|
47
|
+
* without one isn't a candidate ADR/protocol record, it's silently excluded
|
|
48
|
+
* from this listing (not an error). `diagnostics` collects every resilience
|
|
49
|
+
* finding described in the module doc comment; callers that don't care can
|
|
50
|
+
* omit it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function discoverDocs(targetDir: string, dirs: string[], predicate: (fields: Map<string, string>) => boolean, diagnostics?: BriefDiagnostic[]): DocRef[];
|
|
53
|
+
//# sourceMappingURL=docs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/brief/docs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAE1D,6EAA6E;AAC7E,eAAO,MAAM,sBAAsB,QAAc,CAAC;AAIlD,MAAM,WAAW,SAAS;IACxB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,oHAAoH;IACpH,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,2EAA2E;IAC3E,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AA+KD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EAAE,EACd,SAAS,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,EACnD,WAAW,GAAE,eAAe,EAAO,GAClC,MAAM,EAAE,CAmBV"}
|