@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,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only git signals for the freshness check (SELF-AWARENESS-001).
|
|
3
|
+
*
|
|
4
|
+
* Runs only `git log` and `git rev-parse`, never a command that writes, never
|
|
5
|
+
* through a shell, with config that could launch helper programs switched off.
|
|
6
|
+
* Any failure degrades to `{ status: 'unavailable' }` — git being absent,
|
|
7
|
+
* broken or refusing the directory must never fail the brief.
|
|
8
|
+
*/
|
|
9
|
+
import { execFileSync } from 'child_process';
|
|
10
|
+
/** Upper bound on commits read after the last state update. Hitting it marks the result truncated. */
|
|
11
|
+
export const MAX_COMMITS_SINCE = 500;
|
|
12
|
+
const GIT_TIMEOUT_MS = 10_000;
|
|
13
|
+
const GIT_MAX_BUFFER = 8 * 1024 * 1024;
|
|
14
|
+
const SAFE_CONFIG = [
|
|
15
|
+
'-c', 'core.fsmonitor=false',
|
|
16
|
+
'-c', 'core.pager=cat',
|
|
17
|
+
'-c', 'log.showSignature=false',
|
|
18
|
+
'-c', 'diff.external=',
|
|
19
|
+
];
|
|
20
|
+
class GitMissingError extends Error {
|
|
21
|
+
}
|
|
22
|
+
function git(cwd, args) {
|
|
23
|
+
try {
|
|
24
|
+
return execFileSync('git', [...SAFE_CONFIG, ...args], {
|
|
25
|
+
cwd,
|
|
26
|
+
encoding: 'utf8',
|
|
27
|
+
timeout: GIT_TIMEOUT_MS,
|
|
28
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
29
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
30
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: '0', GIT_TERMINAL_PROMPT: '0', GIT_EXTERNAL_DIFF: '' },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
if (err.code === 'ENOENT')
|
|
35
|
+
throw new GitMissingError('git not installed');
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const RS = '\x1e'; // record separator, starts each commit
|
|
40
|
+
const US = '\x1f'; // unit separator, between header fields
|
|
41
|
+
/**
|
|
42
|
+
* @param targetDir project directory (git is run from here; paths are relative to it)
|
|
43
|
+
* @param stateFiles state documents, relative to `targetDir`. Only existing ones need be passed.
|
|
44
|
+
*/
|
|
45
|
+
export function readGitSignals(targetDir, stateFiles) {
|
|
46
|
+
try {
|
|
47
|
+
const inside = git(targetDir, ['rev-parse', '--is-inside-work-tree']).trim();
|
|
48
|
+
if (inside !== 'true')
|
|
49
|
+
return { status: 'unavailable', reason: 'NOT_A_GIT_REPOSITORY' };
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
if (err instanceof GitMissingError)
|
|
53
|
+
return { status: 'unavailable', reason: 'GIT_NOT_INSTALLED' };
|
|
54
|
+
const stderr = String(err.stderr ?? '');
|
|
55
|
+
return { status: 'unavailable', reason: /not a git repository/i.test(stderr) ? 'NOT_A_GIT_REPOSITORY' : 'GIT_FAILED' };
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
// A repository without commits has no HEAD: nothing to compare with.
|
|
59
|
+
try {
|
|
60
|
+
git(targetDir, ['rev-parse', '--verify', '--quiet', 'HEAD']);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return { status: 'ok', lastStateUpdate: null, commitsSince: [], truncated: false };
|
|
64
|
+
}
|
|
65
|
+
let lastStateUpdate = null;
|
|
66
|
+
if (stateFiles.length > 0) {
|
|
67
|
+
const out = git(targetDir, ['log', '-1', `--format=%H${US}%cI`, '--', ...stateFiles]).trim();
|
|
68
|
+
if (out) {
|
|
69
|
+
const [sha, date] = out.split(US);
|
|
70
|
+
lastStateUpdate = { sha, date };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!lastStateUpdate)
|
|
74
|
+
return { status: 'ok', lastStateUpdate: null, commitsSince: [], truncated: false };
|
|
75
|
+
const raw = git(targetDir, [
|
|
76
|
+
'log',
|
|
77
|
+
`-n${MAX_COMMITS_SINCE + 1}`,
|
|
78
|
+
'--no-ext-diff',
|
|
79
|
+
'--no-textconv',
|
|
80
|
+
'--relative',
|
|
81
|
+
'--name-only',
|
|
82
|
+
`--format=${RS}%H${US}%cI${US}%s`,
|
|
83
|
+
`${lastStateUpdate.sha}..HEAD`,
|
|
84
|
+
]);
|
|
85
|
+
const commits = [];
|
|
86
|
+
for (const rec of raw.split(RS)) {
|
|
87
|
+
if (!rec.trim())
|
|
88
|
+
continue;
|
|
89
|
+
const nl = rec.indexOf('\n');
|
|
90
|
+
const header = nl === -1 ? rec : rec.slice(0, nl);
|
|
91
|
+
const [sha, date, subject] = header.split(US);
|
|
92
|
+
const files = (nl === -1 ? '' : rec.slice(nl + 1)).split('\n').filter((l) => l.trim() !== '');
|
|
93
|
+
commits.push({ sha, date, subject: subject ?? '', files });
|
|
94
|
+
}
|
|
95
|
+
const truncated = commits.length > MAX_COMMITS_SINCE;
|
|
96
|
+
return { status: 'ok', lastStateUpdate, commitsSince: commits.slice(0, MAX_COMMITS_SINCE), truncated };
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return { status: 'unavailable', reason: 'GIT_FAILED' };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { BriefModel, BriefDiagnostic, BriefDiagnosticCode, DiagnosticSeverity, Provenance, ProjectIdentity, StateKeeperStatus, StateKeeperSummary, MissionsSummary, CheckpointSummary, StorySummary, DocRef, AdrRef, AdrIndex, ProtocolIndex, } from './types.js';
|
|
2
|
+
export { collect } from './collect.js';
|
|
3
|
+
export { render, DEFAULT_MAX_CHARS } from './render.js';
|
|
4
|
+
export type { RenderResult, Completeness } from './render.js';
|
|
5
|
+
export { runBrief, parseBriefArgs } from './cli.js';
|
|
6
|
+
export type { BriefArgsResult, BriefArgsOk, BriefArgsErr } from './cli.js';
|
|
7
|
+
export { evaluateFreshness } from './freshness.js';
|
|
8
|
+
export type { FreshnessStatus, FreshnessResult, FreshnessEvidence, FreshnessReason, GitSignals } from './freshness.js';
|
|
9
|
+
export { reconcile, renderReconciliation } from './reconcile.js';
|
|
10
|
+
export type { ReconciliationReport, ReconciliationFinding, FindingKind, UntrackedWorkItem, ReviewDocument } from './reconcile.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/brief/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,MAAM,EACN,MAAM,EACN,QAAQ,EACR,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACvH,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACjE,YAAY,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { collect } from './collect.js';
|
|
2
|
+
export { render, DEFAULT_MAX_CHARS } from './render.js';
|
|
3
|
+
export { runBrief, parseBriefArgs } from './cli.js';
|
|
4
|
+
export { evaluateFreshness } from './freshness.js';
|
|
5
|
+
export { reconcile, renderReconciliation } from './reconcile.js';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Reconciliation Report (SELF-AWARENESS-002).
|
|
3
|
+
*
|
|
4
|
+
* SELF-AWARENESS-001 says *whether* the recorded state looks outdated. This
|
|
5
|
+
* module says *what* the evidence is and *what a human should check*. It is
|
|
6
|
+
* pure: it takes the same `FreshnessEvidence` / `FreshnessResult` that
|
|
7
|
+
* `brief` already computed, reads nothing and writes nothing.
|
|
8
|
+
*
|
|
9
|
+
* Two rules shape every line of the report:
|
|
10
|
+
* - A git commit is not evidence that a work item is complete, and a work-item
|
|
11
|
+
* id in a commit subject is not evidence that its recorded status is right.
|
|
12
|
+
* The report therefore only ever says "referenced" and "not mentioned",
|
|
13
|
+
* never "done" or "missing from the roadmap because it was finished".
|
|
14
|
+
* - Recommendations are things for a person to verify or decide. Nothing here
|
|
15
|
+
* edits a document or concludes anything on the person's behalf.
|
|
16
|
+
*
|
|
17
|
+
* Findings are graded:
|
|
18
|
+
* confirmed a concrete, checkable discrepancy (documents contradict each other,
|
|
19
|
+
* or git names a work item that no state document mentions)
|
|
20
|
+
* suspected something that may be a discrepancy but cannot be shown from the data
|
|
21
|
+
* unknown the evidence needed to decide is missing (no git, truncated history, ...)
|
|
22
|
+
*/
|
|
23
|
+
import { type FreshnessEvidence, type FreshnessResult, type FreshnessStatus } from './freshness.js';
|
|
24
|
+
export type FindingKind = 'confirmed' | 'suspected' | 'unknown';
|
|
25
|
+
export type FindingCode = 'STATE_CONTRADICTION' | 'UNRECORDED_WORK' | 'UNATTRIBUTED_ACTIVITY' | 'ACTIVITY_WITHOUT_ACTIVE_STORY' | 'GIT_UNAVAILABLE' | 'STATE_NOT_IN_GIT' | 'GIT_HISTORY_TRUNCATED' | 'ACTIVE_STORY_UNKNOWN';
|
|
26
|
+
export interface ReconciliationFinding {
|
|
27
|
+
kind: FindingKind;
|
|
28
|
+
code: FindingCode;
|
|
29
|
+
summary: string;
|
|
30
|
+
/** Short, checkable pointers: document names or `<sha7> <subject>`. */
|
|
31
|
+
evidence: string[];
|
|
32
|
+
}
|
|
33
|
+
export interface UntrackedWorkItem {
|
|
34
|
+
id: string;
|
|
35
|
+
/** Newest first, at most `MAX_COMMITS_PER_ITEM`. */
|
|
36
|
+
commits: {
|
|
37
|
+
sha: string;
|
|
38
|
+
subject: string;
|
|
39
|
+
}[];
|
|
40
|
+
totalCommits: number;
|
|
41
|
+
}
|
|
42
|
+
export interface ReviewDocument {
|
|
43
|
+
path: string;
|
|
44
|
+
reasons: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface ReconciliationReport {
|
|
47
|
+
freshness: FreshnessStatus;
|
|
48
|
+
lastStateUpdate: {
|
|
49
|
+
sha: string;
|
|
50
|
+
date: string;
|
|
51
|
+
} | null;
|
|
52
|
+
findings: ReconciliationFinding[];
|
|
53
|
+
/** Work-item ids git references after the last state update that no state document mentions. Sorted. */
|
|
54
|
+
untrackedWork: UntrackedWorkItem[];
|
|
55
|
+
/** Ids git references that some state document mentions. Mentioned is not correct. Sorted. */
|
|
56
|
+
mentionedWork: string[];
|
|
57
|
+
documents: ReviewDocument[];
|
|
58
|
+
limitations: string[];
|
|
59
|
+
actions: string[];
|
|
60
|
+
}
|
|
61
|
+
export declare const MAX_COMMITS_PER_ITEM = 3;
|
|
62
|
+
export declare function reconcile(ev: FreshnessEvidence, result: FreshnessResult): ReconciliationReport;
|
|
63
|
+
export interface RenderedReconciliation {
|
|
64
|
+
text: string;
|
|
65
|
+
/** True when a section was dropped to fit the budget. */
|
|
66
|
+
truncated: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Deterministic, bounded text. The header (freshness) and the closing
|
|
70
|
+
* "No files were modified." are mandatory and never dropped; the sections in
|
|
71
|
+
* between are added in priority order while they fit, and an omission is
|
|
72
|
+
* always announced.
|
|
73
|
+
*/
|
|
74
|
+
export declare function renderReconciliation(report: ReconciliationReport, maxChars: number): RenderedReconciliation;
|
|
75
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/brief/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,eAAe,EAErB,MAAM,gBAAgB,CAAC;AAGxB,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAC;AAEhE,MAAM,MAAM,WAAW,GACnB,qBAAqB,GACrB,iBAAiB,GACjB,uBAAuB,GACvB,+BAA+B,GAC/B,iBAAiB,GACjB,kBAAkB,GAClB,uBAAuB,GACvB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,oDAAoD;IACpD,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,eAAe,CAAC;IAC3B,eAAe,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACtD,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC,wGAAwG;IACxG,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,8FAA8F;IAC9F,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAiBtC,wBAAgB,SAAS,CAAC,EAAE,EAAE,iBAAiB,EAAE,MAAM,EAAE,eAAe,GAAG,oBAAoB,CAmI9F;AAiBD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,GAAG,sBAAsB,CAoF3G"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Reconciliation Report (SELF-AWARENESS-002).
|
|
3
|
+
*
|
|
4
|
+
* SELF-AWARENESS-001 says *whether* the recorded state looks outdated. This
|
|
5
|
+
* module says *what* the evidence is and *what a human should check*. It is
|
|
6
|
+
* pure: it takes the same `FreshnessEvidence` / `FreshnessResult` that
|
|
7
|
+
* `brief` already computed, reads nothing and writes nothing.
|
|
8
|
+
*
|
|
9
|
+
* Two rules shape every line of the report:
|
|
10
|
+
* - A git commit is not evidence that a work item is complete, and a work-item
|
|
11
|
+
* id in a commit subject is not evidence that its recorded status is right.
|
|
12
|
+
* The report therefore only ever says "referenced" and "not mentioned",
|
|
13
|
+
* never "done" or "missing from the roadmap because it was finished".
|
|
14
|
+
* - Recommendations are things for a person to verify or decide. Nothing here
|
|
15
|
+
* edits a document or concludes anything on the person's behalf.
|
|
16
|
+
*
|
|
17
|
+
* Findings are graded:
|
|
18
|
+
* confirmed a concrete, checkable discrepancy (documents contradict each other,
|
|
19
|
+
* or git names a work item that no state document mentions)
|
|
20
|
+
* suspected something that may be a discrepancy but cannot be shown from the data
|
|
21
|
+
* unknown the evidence needed to decide is missing (no git, truncated history, ...)
|
|
22
|
+
*/
|
|
23
|
+
import { extractWorkItemIds, mentionsId, } from './freshness.js';
|
|
24
|
+
import { sanitizeSingleLine as san } from './sanitize.js';
|
|
25
|
+
export const MAX_COMMITS_PER_ITEM = 3;
|
|
26
|
+
const MAX_LISTED_ITEMS = 10;
|
|
27
|
+
const MAX_SUBJECT_CHARS = 72;
|
|
28
|
+
const DOC_ORDER = ['active.md', 'roadmap-index.md', 'story-index.md', 'HANDOFF.md'];
|
|
29
|
+
function short(sha) {
|
|
30
|
+
return sha.slice(0, 7);
|
|
31
|
+
}
|
|
32
|
+
function clip(s) {
|
|
33
|
+
return s.length <= MAX_SUBJECT_CHARS ? s : `${s.slice(0, MAX_SUBJECT_CHARS - 1)}…`;
|
|
34
|
+
}
|
|
35
|
+
function commitLine(c) {
|
|
36
|
+
return `${short(c.sha)} ${clip(c.subject)}`;
|
|
37
|
+
}
|
|
38
|
+
export function reconcile(ev, result) {
|
|
39
|
+
const findings = [];
|
|
40
|
+
const reviewReasons = new Map();
|
|
41
|
+
const actions = [];
|
|
42
|
+
const limitations = [
|
|
43
|
+
'A commit is not evidence that a work item is complete; an id in a commit subject does not show its recorded status is correct.',
|
|
44
|
+
];
|
|
45
|
+
const pathOf = (name) => [...ev.stateFiles].find((f) => f === name || f.endsWith(`/${name}`)) ?? name;
|
|
46
|
+
const review = (name, reason) => {
|
|
47
|
+
const path = pathOf(name);
|
|
48
|
+
const list = reviewReasons.get(path) ?? [];
|
|
49
|
+
if (!list.includes(reason))
|
|
50
|
+
list.push(reason);
|
|
51
|
+
reviewReasons.set(path, list);
|
|
52
|
+
};
|
|
53
|
+
// 1. Contradictions between the state documents — need no git.
|
|
54
|
+
for (const r of result.reasons) {
|
|
55
|
+
if (r.code !== 'STATE_CONTRADICTION')
|
|
56
|
+
continue;
|
|
57
|
+
const docs = r.documents ?? [];
|
|
58
|
+
findings.push({ kind: 'confirmed', code: 'STATE_CONTRADICTION', summary: r.message, evidence: docs });
|
|
59
|
+
for (const d of docs)
|
|
60
|
+
review(d, 'contradicts another state document');
|
|
61
|
+
actions.push(`Decide which state document is correct: ${r.message}`);
|
|
62
|
+
}
|
|
63
|
+
// 2. Git-derived evidence.
|
|
64
|
+
const untrackedWork = [];
|
|
65
|
+
let mentionedWork = [];
|
|
66
|
+
if (ev.git.status === 'unavailable') {
|
|
67
|
+
findings.push({ kind: 'unknown', code: 'GIT_UNAVAILABLE', summary: reasonMessage(result, 'GIT_UNAVAILABLE'), evidence: [] });
|
|
68
|
+
actions.push('Run this command from a git checkout (git must be installed) to compare the state documents with history.');
|
|
69
|
+
limitations.push('Git is unavailable: only contradictions between the state documents can be detected.');
|
|
70
|
+
}
|
|
71
|
+
else if (ev.git.lastStateUpdate === null) {
|
|
72
|
+
findings.push({ kind: 'unknown', code: 'STATE_NOT_IN_GIT', summary: reasonMessage(result, 'STATE_NOT_IN_GIT'), evidence: [] });
|
|
73
|
+
actions.push('Commit the state documents to establish a baseline, then re-run.');
|
|
74
|
+
limitations.push('No commit touches the state documents, so there is no baseline to compare history with.');
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const activity = ev.git.commitsSince.filter((c) => c.files.some((f) => !ev.stateFiles.has(f)));
|
|
78
|
+
const byId = new Map();
|
|
79
|
+
const unattributed = [];
|
|
80
|
+
for (const c of activity) {
|
|
81
|
+
const ids = extractWorkItemIds(c.subject);
|
|
82
|
+
if (ids.length === 0)
|
|
83
|
+
unattributed.push(c);
|
|
84
|
+
for (const id of ids)
|
|
85
|
+
byId.set(id, [...(byId.get(id) ?? []), c]);
|
|
86
|
+
}
|
|
87
|
+
const ids = [...byId.keys()].sort();
|
|
88
|
+
mentionedWork = ids.filter((id) => mentionsId(ev.recordedText, id));
|
|
89
|
+
for (const id of ids.filter((i) => !mentionsId(ev.recordedText, i))) {
|
|
90
|
+
const commits = byId.get(id) ?? [];
|
|
91
|
+
untrackedWork.push({
|
|
92
|
+
id,
|
|
93
|
+
commits: commits.slice(0, MAX_COMMITS_PER_ITEM).map((c) => ({ sha: c.sha, subject: c.subject })),
|
|
94
|
+
totalCommits: commits.length,
|
|
95
|
+
});
|
|
96
|
+
findings.push({
|
|
97
|
+
kind: 'confirmed',
|
|
98
|
+
code: 'UNRECORDED_WORK',
|
|
99
|
+
summary: `${id} is referenced by ${commits.length} commit(s) after the last state update; no state document mentions it.`,
|
|
100
|
+
evidence: commits.slice(0, MAX_COMMITS_PER_ITEM).map(commitLine),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (untrackedWork.length > 0) {
|
|
104
|
+
review('roadmap-index.md', 'no entry for work items that git references');
|
|
105
|
+
review('HANDOFF.md', 'may not describe work items that git references');
|
|
106
|
+
if (ev.activeStory.kind !== 'ids')
|
|
107
|
+
review('active.md', 'confirm whether any of this work is current');
|
|
108
|
+
for (const u of untrackedWork.slice(0, MAX_LISTED_ITEMS)) {
|
|
109
|
+
actions.push(`Verify the status of ${u.id} and record it if appropriate (git references it; that does not show it is complete).`);
|
|
110
|
+
}
|
|
111
|
+
if (untrackedWork.length > MAX_LISTED_ITEMS) {
|
|
112
|
+
actions.push(`Verify the remaining ${untrackedWork.length - MAX_LISTED_ITEMS} untracked work item(s) listed above.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (unattributed.length > 0) {
|
|
116
|
+
findings.push({
|
|
117
|
+
kind: 'suspected',
|
|
118
|
+
code: 'UNATTRIBUTED_ACTIVITY',
|
|
119
|
+
summary: `${unattributed.length} commit(s) after the last state update change project files without naming a work item; whether the state documents cover them cannot be determined.`,
|
|
120
|
+
evidence: unattributed.slice(0, MAX_COMMITS_PER_ITEM).map(commitLine),
|
|
121
|
+
});
|
|
122
|
+
actions.push(`Review the ${unattributed.length} commit(s) that name no work item and decide whether the recorded state covers them.`);
|
|
123
|
+
}
|
|
124
|
+
if (activity.length > 0 && ev.activeStory.kind === 'none') {
|
|
125
|
+
findings.push({
|
|
126
|
+
kind: 'suspected',
|
|
127
|
+
code: 'ACTIVITY_WITHOUT_ACTIVE_STORY',
|
|
128
|
+
summary: 'Git shows project activity after the last state update, but no active story is recorded.',
|
|
129
|
+
evidence: [],
|
|
130
|
+
});
|
|
131
|
+
review('active.md', 'no active story recorded while git shows later activity');
|
|
132
|
+
actions.push('Review the active story: confirm whether any work is current. No active story does not mean the project is complete.');
|
|
133
|
+
}
|
|
134
|
+
if (ev.git.truncated) {
|
|
135
|
+
findings.push({ kind: 'unknown', code: 'GIT_HISTORY_TRUNCATED', summary: reasonMessage(result, 'GIT_HISTORY_TRUNCATED'), evidence: [] });
|
|
136
|
+
limitations.push('Git history after the last state update was cut at the read limit; older commits are not examined.');
|
|
137
|
+
actions.push('Review the history since the last state update manually; this report covers only the newest commits.');
|
|
138
|
+
}
|
|
139
|
+
limitations.push('The last state update is the last commit touching a state document; uncommitted edits count for "mentioned" but not for that baseline.');
|
|
140
|
+
}
|
|
141
|
+
if (ev.activeStory.kind === 'unknown') {
|
|
142
|
+
findings.push({
|
|
143
|
+
kind: 'unknown',
|
|
144
|
+
code: 'ACTIVE_STORY_UNKNOWN',
|
|
145
|
+
summary: 'The active story could not be determined from active.md.',
|
|
146
|
+
evidence: [],
|
|
147
|
+
});
|
|
148
|
+
review('active.md', 'its current story could not be parsed');
|
|
149
|
+
}
|
|
150
|
+
const documents = [...reviewReasons.entries()]
|
|
151
|
+
.map(([path, reasons]) => ({ path, reasons }))
|
|
152
|
+
.sort((a, b) => docRank(a.path) - docRank(b.path));
|
|
153
|
+
return {
|
|
154
|
+
freshness: result.status,
|
|
155
|
+
lastStateUpdate: result.lastStateUpdate,
|
|
156
|
+
findings,
|
|
157
|
+
untrackedWork,
|
|
158
|
+
mentionedWork,
|
|
159
|
+
documents,
|
|
160
|
+
limitations,
|
|
161
|
+
actions,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function docRank(path) {
|
|
165
|
+
const i = DOC_ORDER.findIndex((n) => path === n || path.endsWith(`/${n}`));
|
|
166
|
+
return i === -1 ? DOC_ORDER.length : i;
|
|
167
|
+
}
|
|
168
|
+
function reasonMessage(result, code) {
|
|
169
|
+
return result.reasons.find((r) => r.code === code)?.message ?? code;
|
|
170
|
+
}
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Rendering
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
const TRUNCATION_MARKER_RESERVE = 100;
|
|
175
|
+
/**
|
|
176
|
+
* Deterministic, bounded text. The header (freshness) and the closing
|
|
177
|
+
* "No files were modified." are mandatory and never dropped; the sections in
|
|
178
|
+
* between are added in priority order while they fit, and an omission is
|
|
179
|
+
* always announced.
|
|
180
|
+
*/
|
|
181
|
+
export function renderReconciliation(report, maxChars) {
|
|
182
|
+
const head = ['MONOLITH STATE RECONCILIATION', '', `Freshness: ${report.freshness}`];
|
|
183
|
+
if (report.lastStateUpdate) {
|
|
184
|
+
head.push(`Last state update: ${san(short(report.lastStateUpdate.sha))} (${san(report.lastStateUpdate.date.slice(0, 10))}, git)`);
|
|
185
|
+
}
|
|
186
|
+
const foot = '\nNo files were modified.\n';
|
|
187
|
+
const sections = [];
|
|
188
|
+
const bullets = (title, lines) => `\n${title}\n${lines.join('\n')}\n`;
|
|
189
|
+
const contradictions = report.findings.filter((f) => f.code === 'STATE_CONTRADICTION');
|
|
190
|
+
if (contradictions.length > 0) {
|
|
191
|
+
sections.push(bullets('Confirmed mismatches between state documents:', contradictions.map((f) => `- ${san(f.summary)}`)));
|
|
192
|
+
}
|
|
193
|
+
if (report.untrackedWork.length > 0) {
|
|
194
|
+
const lines = [];
|
|
195
|
+
for (const u of report.untrackedWork.slice(0, MAX_LISTED_ITEMS)) {
|
|
196
|
+
lines.push(`- ${u.id} (${u.totalCommits} commit(s) in git)`);
|
|
197
|
+
for (const c of u.commits)
|
|
198
|
+
lines.push(` ${san(commitLine(c))}`);
|
|
199
|
+
if (u.totalCommits > u.commits.length)
|
|
200
|
+
lines.push(` … and ${u.totalCommits - u.commits.length} more`);
|
|
201
|
+
}
|
|
202
|
+
if (report.untrackedWork.length > MAX_LISTED_ITEMS) {
|
|
203
|
+
lines.push(`- … and ${report.untrackedWork.length - MAX_LISTED_ITEMS} more work item(s)`);
|
|
204
|
+
}
|
|
205
|
+
sections.push(bullets('Untracked work items (referenced in git, mentioned in no state document):', lines));
|
|
206
|
+
}
|
|
207
|
+
for (const [kind, title] of [
|
|
208
|
+
['suspected', 'Suspected mismatches (not confirmable from the data):'],
|
|
209
|
+
['unknown', 'Unknown (evidence missing):'],
|
|
210
|
+
]) {
|
|
211
|
+
const list = report.findings.filter((f) => f.kind === kind);
|
|
212
|
+
if (list.length === 0)
|
|
213
|
+
continue;
|
|
214
|
+
const lines = [];
|
|
215
|
+
for (const f of list) {
|
|
216
|
+
lines.push(`- ${san(f.summary)}`);
|
|
217
|
+
for (const e of f.evidence)
|
|
218
|
+
lines.push(` ${san(e)}`);
|
|
219
|
+
}
|
|
220
|
+
sections.push(bullets(title, lines));
|
|
221
|
+
}
|
|
222
|
+
if (report.documents.length > 0) {
|
|
223
|
+
const lines = [];
|
|
224
|
+
for (const d of report.documents)
|
|
225
|
+
lines.push(`- ${san(d.path)} — ${d.reasons.map(san).join('; ')}`);
|
|
226
|
+
sections.push(bullets('Documents requiring review:', lines));
|
|
227
|
+
}
|
|
228
|
+
if (report.findings.length === 0) {
|
|
229
|
+
sections.push('\nNo discrepancy was found in the available evidence. This is not a guarantee the state is correct.\n');
|
|
230
|
+
}
|
|
231
|
+
if (report.mentionedWork.length > 0) {
|
|
232
|
+
const shown = report.mentionedWork.slice(0, MAX_LISTED_ITEMS).join(', ');
|
|
233
|
+
const more = report.mentionedWork.length > MAX_LISTED_ITEMS ? ` and ${report.mentionedWork.length - MAX_LISTED_ITEMS} more` : '';
|
|
234
|
+
sections.push(`\nWork items in git that state documents mention: ${shown}${more}. Mentioned does not mean the recorded status is correct.\n`);
|
|
235
|
+
}
|
|
236
|
+
sections.push(bullets('Limitations:', report.limitations.map((l) => `- ${san(l)}`)));
|
|
237
|
+
if (report.actions.length > 0) {
|
|
238
|
+
sections.push(bullets('Suggested actions (for a person to decide; none is applied automatically):', report.actions.map((a, i) => `${i + 1}. ${san(a)}`)));
|
|
239
|
+
}
|
|
240
|
+
const mandatory = head.join('\n') + '\n' + foot;
|
|
241
|
+
let used = mandatory.length;
|
|
242
|
+
const kept = [];
|
|
243
|
+
let omitted = 0;
|
|
244
|
+
for (const s of sections) {
|
|
245
|
+
// Priority order: once a section is dropped, every later one is too. Room for the marker is always reserved.
|
|
246
|
+
if (omitted === 0 && used + s.length <= maxChars - TRUNCATION_MARKER_RESERVE) {
|
|
247
|
+
kept.push(s);
|
|
248
|
+
used += s.length;
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
omitted++;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
let body = kept.join('');
|
|
255
|
+
if (omitted > 0) {
|
|
256
|
+
body += `\n[truncated: ${omitted} section(s) omitted to fit --max-chars ${maxChars}]\n`;
|
|
257
|
+
}
|
|
258
|
+
return { text: head.join('\n') + '\n' + body + foot, truncated: omitted > 0 };
|
|
259
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `render(model, maxChars)` — the Rendering stage of the brief pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Pure and synchronous: same BriefModel + same maxChars always produces the
|
|
5
|
+
* byte-identical string (section 11 — determinism). No Date.now(), no
|
|
6
|
+
* random IDs, no locale-dependent sorting; every list collect() hands in is
|
|
7
|
+
* already in a fixed, stably-sorted order.
|
|
8
|
+
*
|
|
9
|
+
* Budget accounting (section 10) in two phases to avoid a circular
|
|
10
|
+
* dependency between "does P0 fit" and "what does the Completeness line
|
|
11
|
+
* say" (rule: no diagnostic/marker line may be added without being counted
|
|
12
|
+
* in the budget that decided what else fits):
|
|
13
|
+
*
|
|
14
|
+
* Phase 1 — build all P0 content (identity, State Keeper status,
|
|
15
|
+
* collect-time diagnostics, every active mission identifier, every
|
|
16
|
+
* mandatory protocol pointer) with NO budget limit — P0 is never
|
|
17
|
+
* truncated or hidden (rule 5/7). If P0 alone (plus the reserved,
|
|
18
|
+
* fixed-size Completeness line) exceeds maxChars, that is reported via
|
|
19
|
+
* BUDGET_EXCEEDED_BY_REQUIRED (which also states explicitly that P1–P3
|
|
20
|
+
* are skipped entirely as a result — review finding F-5) and nothing
|
|
21
|
+
* else is attempted.
|
|
22
|
+
*
|
|
23
|
+
* Phase 2 — whatever chars remain after P0 + the reserved Completeness
|
|
24
|
+
* line are handed to P1 (Checkpoints, then Story — Checkpoints first so
|
|
25
|
+
* a long Story can never silently starve "Next steps" out of the budget;
|
|
26
|
+
* review finding F-5), then P2, then P3, in that fixed order. Each
|
|
27
|
+
* section either fits whole, or (list sections) includes as many whole
|
|
28
|
+
* items as fit plus an explicit "N more omitted" marker, or (the Story
|
|
29
|
+
* excerpt) is field-truncated with an explicit `[truncated N chars]`
|
|
30
|
+
* marker, or — if not even that fits — falls back to a short, fixed-size
|
|
31
|
+
* local omission marker so a section can never disappear from the output
|
|
32
|
+
* without some local trace of its omission (review finding F-5; the only
|
|
33
|
+
* residual case where a section can still vanish with zero local marker
|
|
34
|
+
* is when `remaining` is too small even for that minimal marker, which
|
|
35
|
+
* is itself already covered by the global Completeness: INCOMPLETE
|
|
36
|
+
* signal). Every one of these outcomes is itself measured and subtracted
|
|
37
|
+
* from the remaining budget before the next section is attempted —
|
|
38
|
+
* nothing is added "for free" after the fact.
|
|
39
|
+
*
|
|
40
|
+
* Completeness contract (N-3): `Completeness: COMPLETE` means no
|
|
41
|
+
* *substantive data* was lost, not that every section heading is present.
|
|
42
|
+
* An optional section that has nothing to show (an empty list, rendered
|
|
43
|
+
* only as "(none)") and the static P3 pointer block may be dropped under a
|
|
44
|
+
* tight budget without flipping the result to INCOMPLETE — dropping them
|
|
45
|
+
* loses no information. A section that holds data is never dropped
|
|
46
|
+
* silently: it is shown whole, shown partially with an explicit marker, or
|
|
47
|
+
* replaced by a pointer-bearing omission marker, and either of the latter
|
|
48
|
+
* two forces INCOMPLETE. P0 is never dropped. All project-controlled
|
|
49
|
+
* strings, provenance pointers included, pass through
|
|
50
|
+
* `sanitizeSingleLine` (N-1).
|
|
51
|
+
*
|
|
52
|
+
* Advisory section (IDEA-002B): Parked Ideas is the one data-holding section whose
|
|
53
|
+
* budget degradation does NOT force INCOMPLETE. It is user-saved side context, not
|
|
54
|
+
* project state; its degradation stays visible (fewer items, count, or a marker
|
|
55
|
+
* whenever they fit) but `Completeness` and `--strict` keep describing only the
|
|
56
|
+
* project context that existed before ideas did. Unreadable idea files are
|
|
57
|
+
* reported by the `IDEAS_UNREADABLE` warning, which is not an error.
|
|
58
|
+
*/
|
|
59
|
+
import type { BriefModel } from './types.js';
|
|
60
|
+
export declare const DEFAULT_MAX_CHARS = 6000;
|
|
61
|
+
export type Completeness = 'COMPLETE' | 'INCOMPLETE';
|
|
62
|
+
export interface RenderResult {
|
|
63
|
+
text: string;
|
|
64
|
+
completeness: Completeness;
|
|
65
|
+
}
|
|
66
|
+
export declare function render(model: BriefModel, maxChars?: number): RenderResult;
|
|
67
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/brief/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA0F,MAAM,YAAY,CAAC;AAYrI,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAGtC,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,YAAY,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;CAC5B;AAwXD,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,GAAE,MAA0B,GAAG,YAAY,CAgF5F"}
|