@aragon-agent/cli 0.6.44 → 0.6.45
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/CHANGELOG.md +44 -0
- package/README.md +69 -2
- package/dist/agent/controller.d.ts +69 -0
- package/dist/agent/controller.js +224 -11
- package/dist/agent/governance-view.d.ts +40 -0
- package/dist/agent/governance-view.js +40 -0
- package/dist/agent/governance-wiring.d.ts +162 -0
- package/dist/agent/governance-wiring.js +470 -0
- package/dist/agent/plan-prompt.d.ts +7 -0
- package/dist/agent/plan-prompt.js +2 -1
- package/dist/agent/reducer.d.ts +33 -1
- package/dist/agent/reducer.js +45 -9
- package/dist/agent/system-prompt.d.ts +19 -0
- package/dist/agent/system-prompt.js +9 -1
- package/dist/cli.js +45 -1
- package/dist/commands/builtins.js +10 -4
- package/dist/commands/governance-command.d.ts +35 -0
- package/dist/commands/governance-command.js +292 -0
- package/dist/commands/governance-runs-table.d.ts +40 -0
- package/dist/commands/governance-runs-table.js +130 -0
- package/dist/commands/registry.d.ts +12 -0
- package/dist/config/app-paths.d.ts +10 -0
- package/dist/config/app-paths.js +12 -0
- package/dist/config/cli-commands.d.ts +18 -0
- package/dist/config/cli-commands.js +47 -1
- package/dist/config/env.d.ts +2 -1
- package/dist/config/env.js +10 -1
- package/dist/config/governance-config.d.ts +125 -0
- package/dist/config/governance-config.js +164 -0
- package/dist/config/governance-launch.d.ts +71 -0
- package/dist/config/governance-launch.js +58 -0
- package/dist/config/load.d.ts +15 -0
- package/dist/config/load.js +26 -1
- package/dist/config/model-profile-store.d.ts +4 -2
- package/dist/config/schema.d.ts +22 -0
- package/dist/config/schema.js +6 -0
- package/dist/config/store.js +14 -1
- package/dist/config/ui-state.d.ts +11 -0
- package/dist/config/ui-state.js +11 -0
- package/dist/diagnostics/doctor.js +20 -0
- package/dist/governance/card-normalize.d.ts +24 -0
- package/dist/governance/card-normalize.js +76 -0
- package/dist/governance/copy.d.ts +109 -0
- package/dist/governance/copy.js +223 -0
- package/dist/governance/engine/aftermath.d.ts +49 -0
- package/dist/governance/engine/aftermath.js +134 -0
- package/dist/governance/engine/artifact-persist.d.ts +35 -0
- package/dist/governance/engine/artifact-persist.js +86 -0
- package/dist/governance/engine/budget.d.ts +52 -0
- package/dist/governance/engine/budget.js +112 -0
- package/dist/governance/engine/cards.d.ts +18 -0
- package/dist/governance/engine/cards.js +186 -0
- package/dist/governance/engine/digest.d.ts +32 -0
- package/dist/governance/engine/digest.js +63 -0
- package/dist/governance/engine/engine-core.d.ts +219 -0
- package/dist/governance/engine/engine-core.js +562 -0
- package/dist/governance/engine/engine.d.ts +68 -0
- package/dist/governance/engine/engine.js +314 -0
- package/dist/governance/engine/estimate.d.ts +38 -0
- package/dist/governance/engine/estimate.js +46 -0
- package/dist/governance/engine/inspector.d.ts +16 -0
- package/dist/governance/engine/inspector.js +396 -0
- package/dist/governance/engine/integration.d.ts +35 -0
- package/dist/governance/engine/integration.js +220 -0
- package/dist/governance/engine/lifecycle.d.ts +29 -0
- package/dist/governance/engine/lifecycle.js +656 -0
- package/dist/governance/engine/pause.d.ts +56 -0
- package/dist/governance/engine/pause.js +107 -0
- package/dist/governance/engine/phase-members.d.ts +15 -0
- package/dist/governance/engine/phase-members.js +34 -0
- package/dist/governance/engine/phase-planner.d.ts +53 -0
- package/dist/governance/engine/phase-planner.js +282 -0
- package/dist/governance/engine/rebuild.d.ts +27 -0
- package/dist/governance/engine/rebuild.js +57 -0
- package/dist/governance/engine/report.d.ts +66 -0
- package/dist/governance/engine/report.js +220 -0
- package/dist/governance/engine/run-digest.d.ts +20 -0
- package/dist/governance/engine/run-digest.js +42 -0
- package/dist/governance/engine/spec-builder.d.ts +117 -0
- package/dist/governance/engine/spec-builder.js +321 -0
- package/dist/governance/engine/stage-runner.d.ts +22 -0
- package/dist/governance/engine/stage-runner.js +664 -0
- package/dist/governance/engine/start-input.d.ts +19 -0
- package/dist/governance/engine/start-input.js +111 -0
- package/dist/governance/engine/state-machine.d.ts +101 -0
- package/dist/governance/engine/state-machine.js +586 -0
- package/dist/governance/engine/submission-queries.d.ts +42 -0
- package/dist/governance/engine/submission-queries.js +97 -0
- package/dist/governance/engine/submission-render.d.ts +12 -0
- package/dist/governance/engine/submission-render.js +92 -0
- package/dist/governance/engine/submissions.d.ts +27 -0
- package/dist/governance/engine/submissions.js +455 -0
- package/dist/governance/engine/visibility.d.ts +104 -0
- package/dist/governance/engine/visibility.js +223 -0
- package/dist/governance/governed-dispatch.d.ts +19 -0
- package/dist/governance/governed-dispatch.js +17 -0
- package/dist/governance/limits.d.ts +107 -0
- package/dist/governance/limits.js +131 -0
- package/dist/governance/posture-transition.d.ts +38 -0
- package/dist/governance/posture-transition.js +44 -0
- package/dist/governance/posture.d.ts +35 -0
- package/dist/governance/posture.js +59 -0
- package/dist/governance/profiles.d.ts +48 -0
- package/dist/governance/profiles.js +140 -0
- package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
- package/dist/governance/prompts/orchestrator-block.js +128 -0
- package/dist/governance/prompts/plan-addendum.d.ts +22 -0
- package/dist/governance/prompts/plan-addendum.js +60 -0
- package/dist/governance/prompts/refusals.d.ts +48 -0
- package/dist/governance/prompts/refusals.js +82 -0
- package/dist/governance/prompts/role-blocks.d.ts +40 -0
- package/dist/governance/prompts/role-blocks.js +255 -0
- package/dist/governance/report-reader.d.ts +31 -0
- package/dist/governance/report-reader.js +57 -0
- package/dist/governance/roles.d.ts +83 -0
- package/dist/governance/roles.js +142 -0
- package/dist/governance/rules/claims.d.ts +83 -0
- package/dist/governance/rules/claims.js +250 -0
- package/dist/governance/rules/elimination.d.ts +66 -0
- package/dist/governance/rules/elimination.js +105 -0
- package/dist/governance/rules/events-table.d.ts +46 -0
- package/dist/governance/rules/events-table.js +94 -0
- package/dist/governance/rules/failure-analysis.d.ts +28 -0
- package/dist/governance/rules/failure-analysis.js +93 -0
- package/dist/governance/rules/ledger-replay.d.ts +53 -0
- package/dist/governance/rules/ledger-replay.js +53 -0
- package/dist/governance/rules/ledger-state.d.ts +85 -0
- package/dist/governance/rules/ledger-state.js +175 -0
- package/dist/governance/rules/ledger.d.ts +228 -0
- package/dist/governance/rules/ledger.js +560 -0
- package/dist/governance/rules/rca.d.ts +68 -0
- package/dist/governance/rules/rca.js +220 -0
- package/dist/governance/rules/settlement.d.ts +129 -0
- package/dist/governance/rules/settlement.js +363 -0
- package/dist/governance/rules/suspicion.d.ts +66 -0
- package/dist/governance/rules/suspicion.js +169 -0
- package/dist/governance/store/artifacts.d.ts +81 -0
- package/dist/governance/store/artifacts.js +173 -0
- package/dist/governance/store/atomic.d.ts +79 -0
- package/dist/governance/store/atomic.js +119 -0
- package/dist/governance/store/events-log.d.ts +35 -0
- package/dist/governance/store/events-log.js +86 -0
- package/dist/governance/store/run-lock.d.ts +51 -0
- package/dist/governance/store/run-lock.js +122 -0
- package/dist/governance/store/run-store.d.ts +229 -0
- package/dist/governance/store/run-store.js +644 -0
- package/dist/governance/store/scoreboard-export.d.ts +53 -0
- package/dist/governance/store/scoreboard-export.js +65 -0
- package/dist/governance/tools/child-tools.d.ts +31 -0
- package/dist/governance/tools/child-tools.js +65 -0
- package/dist/governance/tools/guard.d.ts +71 -0
- package/dist/governance/tools/guard.js +194 -0
- package/dist/governance/tools/inspector-tools.d.ts +63 -0
- package/dist/governance/tools/inspector-tools.js +424 -0
- package/dist/governance/tools/lead-tools.d.ts +42 -0
- package/dist/governance/tools/lead-tools.js +226 -0
- package/dist/governance/tools/normalize-util.d.ts +101 -0
- package/dist/governance/tools/normalize-util.js +230 -0
- package/dist/governance/tools/scan-incomplete.d.ts +79 -0
- package/dist/governance/tools/scan-incomplete.js +486 -0
- package/dist/governance/tools/shared-tools.d.ts +44 -0
- package/dist/governance/tools/shared-tools.js +125 -0
- package/dist/governance/tools/submit-normalizers.d.ts +28 -0
- package/dist/governance/tools/submit-normalizers.js +474 -0
- package/dist/governance/tools/submit-tools.d.ts +26 -0
- package/dist/governance/tools/submit-tools.js +171 -0
- package/dist/governance/types.d.ts +661 -0
- package/dist/governance/types.js +27 -0
- package/dist/session/validate-session.js +32 -1
- package/dist/team/runtime.d.ts +8 -0
- package/dist/team/runtime.js +9 -0
- package/dist/team/subagent.d.ts +23 -0
- package/dist/team/subagent.js +14 -3
- package/dist/team/types.d.ts +6 -0
- package/dist/tools/index.d.ts +30 -7
- package/dist/tools/index.js +14 -0
- package/dist/tools/plan-tools.d.ts +14 -2
- package/dist/tools/plan-tools.js +5 -2
- package/dist/ui/App.js +145 -25
- package/dist/ui/BottomStatusRow.js +1 -1
- package/dist/ui/Composer.d.ts +7 -1
- package/dist/ui/Composer.js +9 -6
- package/dist/ui/GovernancePanel.d.ts +50 -0
- package/dist/ui/GovernancePanel.js +77 -0
- package/dist/ui/GovernanceRun.d.ts +18 -0
- package/dist/ui/GovernanceRun.js +11 -0
- package/dist/ui/StatusBar.d.ts +15 -2
- package/dist/ui/StatusBar.js +10 -4
- package/dist/ui/Transcript.js +3 -0
- package/dist/ui/entries/GovernanceCard.d.ts +30 -0
- package/dist/ui/entries/GovernanceCard.js +32 -0
- package/dist/ui/governance-copy.d.ts +69 -0
- package/dist/ui/governance-copy.js +104 -0
- package/dist/ui/governance-status.d.ts +145 -0
- package/dist/ui/governance-status.js +326 -0
- package/dist/ui/interaction-copy.d.ts +4 -0
- package/dist/ui/interaction-copy.js +5 -0
- package/dist/ui/interaction-hints.d.ts +17 -0
- package/dist/ui/interaction-hints.js +14 -2
- package/dist/ui/layout/governance-board.d.ts +121 -0
- package/dist/ui/layout/governance-board.js +382 -0
- package/dist/ui/layout/governance-card.d.ts +53 -0
- package/dist/ui/layout/governance-card.js +134 -0
- package/dist/ui/layout/governance-chip.d.ts +38 -0
- package/dist/ui/layout/governance-chip.js +64 -0
- package/dist/ui/layout/governance-panel.d.ts +85 -0
- package/dist/ui/layout/governance-panel.js +358 -0
- package/dist/ui/layout/governance-roster.d.ts +65 -0
- package/dist/ui/layout/governance-roster.js +275 -0
- package/dist/ui/layout/status-detail-layout.js +3 -3
- package/dist/ui/layout/status-layout.d.ts +5 -2
- package/dist/ui/layout/status-layout.js +10 -6
- package/dist/ui/layout/virtual-window.js +11 -0
- package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
- package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
- package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
- package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
- package/dist/ui/overlays/HelpOverlay.js +4 -3
- package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
- package/dist/ui/overlays/QuestionOverlay.js +4 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
- package/dist/ui/overlays/SettingsScreen.js +21 -1
- package/dist/ui/overlays/governance-settings.d.ts +79 -0
- package/dist/ui/overlays/governance-settings.js +140 -0
- package/dist/ui/overlays/model-profile-state.d.ts +6 -1
- package/dist/ui/overlays/model-profile-state.js +10 -0
- package/dist/ui/palettes.d.ts +17 -0
- package/dist/ui/palettes.js +9 -0
- package/dist/ui/scrollback/live-preview.js +2 -0
- package/dist/ui/scrollback/settled-boundary.js +3 -0
- package/dist/ui/shortcuts.js +2 -2
- package/dist/ui/theme.d.ts +7 -0
- package/dist/ui/theme.js +1 -0
- package/dist/ui/transcript-text.js +9 -0
- package/dist/ui/use-wheel-routing.js +3 -1
- package/package.json +2 -2
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gov_scan`: a deterministic scan for incomplete implementations (governance-mode 5.9).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
5
|
+
*
|
|
6
|
+
* EVIDENCE, NEVER A VERDICT. The scan is a line-and-brace heuristic over several languages,
|
|
7
|
+
* not a parser. It returns `file:line:kind` hits plus a suspected-skeleton ratio, and
|
|
8
|
+
* NOTHING in the engine deducts GT from it: a heuristic that scores would turn every false
|
|
9
|
+
* positive (a deliberate empty `catch`, a trivial constant getter) into an unfair penalty.
|
|
10
|
+
* Reviewers, testers and the inspector cite it as evidence and still read the code.
|
|
11
|
+
*
|
|
12
|
+
* READ-ONLY AND CONFINED. Paths resolve against the working directory and are refused when
|
|
13
|
+
* the REAL path (after symlinks) leaves it; sizes, file counts and depth are bounded so a
|
|
14
|
+
* hostile or huge tree cannot stall a stage. Binary files are skipped, not decoded.
|
|
15
|
+
*
|
|
16
|
+
* The pure core, `scanText`, is exported separately so the heuristics are unit-tested on
|
|
17
|
+
* fixtures without touching a disk.
|
|
18
|
+
*/
|
|
19
|
+
import { promises as fs } from 'node:fs';
|
|
20
|
+
import { extname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
21
|
+
import { textResult } from '@aragon-agent/core';
|
|
22
|
+
import { GOV_LIMITS } from '../limits.js';
|
|
23
|
+
const MAX_LINE_CHARS = 400;
|
|
24
|
+
const SNIPPET_CHARS = 100;
|
|
25
|
+
const MAX_BODY_LINES = 60;
|
|
26
|
+
const MAX_SIGNATURE_LINES = 6;
|
|
27
|
+
const LOOKAHEAD_LINES = 4;
|
|
28
|
+
function re(...parts) {
|
|
29
|
+
return new RegExp(parts.join(''));
|
|
30
|
+
}
|
|
31
|
+
/** Case-insensitive variant of `re`. */
|
|
32
|
+
function rei(...parts) {
|
|
33
|
+
return new RegExp(parts.join(''), 'i');
|
|
34
|
+
}
|
|
35
|
+
// --- Line markers ----------------------------------------------------------
|
|
36
|
+
const NOT_IMPLEMENTED = rei(String.raw `not\s+(?:yet\s+)?implemented|unimplemented|`, String.raw `\bNotImplemented(?:Error)?\b|todo!\s*\(`);
|
|
37
|
+
const TODO_MARKER = /\b(?:TODO|FIXME|XXX)\b/;
|
|
38
|
+
const EMPTY_CATCH_INLINE = /\bcatch\s*(?:\([^)]*\))?\s*\{\s*\}/;
|
|
39
|
+
const CATCH_OPEN = /\bcatch\s*(?:\([^)]*\))?\s*\{\s*$/;
|
|
40
|
+
const PY_EXCEPT = /^\s*except\b[^:]*:\s*$/;
|
|
41
|
+
const PY_DEF = /^(\s*)(?:async\s+)?def\s+(\w+)\s*\(/;
|
|
42
|
+
const PY_BARE = /^(?:pass|\.\.\.)$/;
|
|
43
|
+
// --- Declarations in brace languages (group 1 is the name) -----------------
|
|
44
|
+
const CONTROL_WORDS = new Set([
|
|
45
|
+
'if',
|
|
46
|
+
'for',
|
|
47
|
+
'while',
|
|
48
|
+
'switch',
|
|
49
|
+
'catch',
|
|
50
|
+
'return',
|
|
51
|
+
'else',
|
|
52
|
+
'with',
|
|
53
|
+
'synchronized',
|
|
54
|
+
'function',
|
|
55
|
+
]);
|
|
56
|
+
const BRACE_DECLARATIONS = [
|
|
57
|
+
// function foo(...)
|
|
58
|
+
re(String.raw `^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\b\s*\*?\s*(\w*)`, String.raw `\s*(?:<[^>]*>)?\s*\(`),
|
|
59
|
+
// const foo = (...) => ...
|
|
60
|
+
re(String.raw `^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*(?::[^=]+)?=\s*(?:async\s*)?`, String.raw `(?:\([^)]*\)|\w+)\s*(?::[^=]+?)?\s*=>`),
|
|
61
|
+
// Rust fn, Go func
|
|
62
|
+
/^(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+(\w+)/,
|
|
63
|
+
/^func\s+(?:\([^)]*\)\s*)?(\w+)/,
|
|
64
|
+
// class method: [modifiers] name(...) [: type] {
|
|
65
|
+
re(String.raw `^(?:(?:public|private|protected|static|async|override|abstract|get|set)\s+)*`, String.raw `#?(\w+)\s*(?:<[^>]*>)?\s*\([^)]*\)\s*(?::\s*[^{;=]+)?\s*\{`),
|
|
66
|
+
// C-like: [type] name(...) {
|
|
67
|
+
re(String.raw `^(?:[\w<>[\]*&:,?]+\s+)+(\w+)\s*\([^;]*\)\s*(?:const\s*)?`, String.raw `(?:throws\s+[\w, ]+)?\s*\{\s*$`),
|
|
68
|
+
];
|
|
69
|
+
/** A statement that does nothing useful: a constant return or an explicit "not done". */
|
|
70
|
+
const PLACEHOLDER_STATEMENT = rei(String.raw `^(?:return\s*(?:null|undefined|nil|None|0|false|""|''|\{\s*\}|\[\s*\])?\s*;?`, String.raw `|(?:throw|raise|panic)\b.*(?:not\s*(?:yet\s+)?implemented|todo|unimplemented).*`, String.raw `|(?:unimplemented|todo)!\s*\(.*\)\s*;?)$`);
|
|
71
|
+
const RETURN_STATEMENT = /^return\b/i;
|
|
72
|
+
function clipSnippet(line) {
|
|
73
|
+
const flat = line.trim().replace(/\s+/g, ' ');
|
|
74
|
+
return flat.length > SNIPPET_CHARS ? `${flat.slice(0, SNIPPET_CHARS - 3)}...` : flat;
|
|
75
|
+
}
|
|
76
|
+
/** Drop one-line strings and `//` / block comments so braces inside them are not counted. */
|
|
77
|
+
function stripForBraces(line) {
|
|
78
|
+
return line
|
|
79
|
+
.replace(/(["'`])(?:\\.|(?!\1).)*\1/g, '""')
|
|
80
|
+
.replace(/\/\*.*?\*\//g, '')
|
|
81
|
+
.replace(/\/\/.*$/, '');
|
|
82
|
+
}
|
|
83
|
+
function isCommentOnly(text) {
|
|
84
|
+
const t = text.trim();
|
|
85
|
+
return t.startsWith('//') || t.startsWith('#') || t.startsWith('*') || t.startsWith('/*');
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Line-level findings
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
/**
|
|
91
|
+
* The next non-blank line, comments INCLUDED: a handler holding only a comment is a
|
|
92
|
+
* deliberate, explained swallow and is not reported as an empty catch.
|
|
93
|
+
*/
|
|
94
|
+
function nextNonBlankLine(lines, from) {
|
|
95
|
+
for (let i = from; i < lines.length && i < from + LOOKAHEAD_LINES; i++) {
|
|
96
|
+
const t = lines[i].trim();
|
|
97
|
+
if (t !== '')
|
|
98
|
+
return t;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
/** The marker a single line carries, if any. Priority: not_implemented, todo, empty catch. */
|
|
103
|
+
function lineKind(lines, index) {
|
|
104
|
+
const line = lines[index];
|
|
105
|
+
if (NOT_IMPLEMENTED.test(line))
|
|
106
|
+
return 'not_implemented';
|
|
107
|
+
if (TODO_MARKER.test(line))
|
|
108
|
+
return 'todo';
|
|
109
|
+
if (EMPTY_CATCH_INLINE.test(line))
|
|
110
|
+
return 'empty_catch';
|
|
111
|
+
if (CATCH_OPEN.test(line) && nextNonBlankLine(lines, index + 1) === '}')
|
|
112
|
+
return 'empty_catch';
|
|
113
|
+
if (PY_EXCEPT.test(line) && nextNonBlankLine(lines, index + 1) === 'pass')
|
|
114
|
+
return 'empty_catch';
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const NOT_A_SKELETON = { skeleton: false, kind: null };
|
|
118
|
+
const TRIPLE_QUOTE = /("""|''')/;
|
|
119
|
+
const DOCSTRING_START = /^[rbuRBU]*("""|''')/;
|
|
120
|
+
function indentOf(raw) {
|
|
121
|
+
return raw.length - raw.trimStart().length;
|
|
122
|
+
}
|
|
123
|
+
/** Index of the first body line: the line after the (possibly wrapped) signature's `:`. */
|
|
124
|
+
function pythonBodyStart(lines, index) {
|
|
125
|
+
const last = Math.min(lines.length, index + MAX_SIGNATURE_LINES);
|
|
126
|
+
for (let i = index; i < last; i++) {
|
|
127
|
+
if (/:\s*(?:#.*)?$/.test(lines[i]))
|
|
128
|
+
return i + 1;
|
|
129
|
+
}
|
|
130
|
+
return -1;
|
|
131
|
+
}
|
|
132
|
+
/** The statements of a Python body: comments and a leading docstring are dropped. */
|
|
133
|
+
function pythonBodyStatements(lines, start, indent) {
|
|
134
|
+
const statements = [];
|
|
135
|
+
let inDocstring = false;
|
|
136
|
+
const end = Math.min(lines.length, start + MAX_BODY_LINES);
|
|
137
|
+
for (let i = start; i < end; i++) {
|
|
138
|
+
const raw = lines[i];
|
|
139
|
+
const t = raw.trim();
|
|
140
|
+
if (t === '')
|
|
141
|
+
continue;
|
|
142
|
+
if (!inDocstring && indentOf(raw) <= indent)
|
|
143
|
+
break;
|
|
144
|
+
const quote = TRIPLE_QUOTE.exec(t)?.[1];
|
|
145
|
+
if (inDocstring) {
|
|
146
|
+
if (quote)
|
|
147
|
+
inDocstring = false;
|
|
148
|
+
}
|
|
149
|
+
else if (quote && DOCSTRING_START.test(t)) {
|
|
150
|
+
inDocstring = t.indexOf(quote) === t.lastIndexOf(quote);
|
|
151
|
+
}
|
|
152
|
+
else if (!t.startsWith('#')) {
|
|
153
|
+
statements.push(t);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return statements;
|
|
157
|
+
}
|
|
158
|
+
/** Python: the body is the more-indented block after the (possibly wrapped) signature. */
|
|
159
|
+
function pythonFunction(lines, index) {
|
|
160
|
+
const m = PY_DEF.exec(lines[index]);
|
|
161
|
+
if (!m)
|
|
162
|
+
return null;
|
|
163
|
+
const start = pythonBodyStart(lines, index);
|
|
164
|
+
if (start < 0)
|
|
165
|
+
return NOT_A_SKELETON;
|
|
166
|
+
const statements = pythonBodyStatements(lines, start, m[1].length);
|
|
167
|
+
const bare = statements.every((s) => PY_BARE.test(s));
|
|
168
|
+
const placeholders = statements.every((s) => PY_BARE.test(s) || PLACEHOLDER_STATEMENT.test(s));
|
|
169
|
+
if (!placeholders)
|
|
170
|
+
return NOT_A_SKELETON;
|
|
171
|
+
if (bare)
|
|
172
|
+
return { skeleton: true, kind: 'bare_pass' };
|
|
173
|
+
const constantReturn = statements.some((s) => RETURN_STATEMENT.test(s));
|
|
174
|
+
return { skeleton: true, kind: constantReturn ? 'placeholder_return' : null };
|
|
175
|
+
}
|
|
176
|
+
/** The declaration name if `line` opens a brace-language function; else `null`. */
|
|
177
|
+
function braceDeclaration(line) {
|
|
178
|
+
const t = line.trim();
|
|
179
|
+
for (const pattern of BRACE_DECLARATIONS) {
|
|
180
|
+
const m = pattern.exec(t);
|
|
181
|
+
if (!m)
|
|
182
|
+
continue;
|
|
183
|
+
const name = m[1] ?? '';
|
|
184
|
+
return CONTROL_WORDS.has(name) ? null : name || 'function';
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Feed one (already stripped) line through the brace counter. Returns the text after the
|
|
190
|
+
* body's opening brace, and whether the closing brace was reached on this line.
|
|
191
|
+
*/
|
|
192
|
+
function consumeBraceLine(code, state) {
|
|
193
|
+
let segment = '';
|
|
194
|
+
for (const ch of code) {
|
|
195
|
+
if (ch === '{') {
|
|
196
|
+
state.depth++;
|
|
197
|
+
if (!state.started) {
|
|
198
|
+
state.started = true;
|
|
199
|
+
segment = '';
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else if (ch === '}') {
|
|
204
|
+
state.depth--;
|
|
205
|
+
if (state.started && state.depth === 0)
|
|
206
|
+
return { segment: segment.trim(), closed: true };
|
|
207
|
+
}
|
|
208
|
+
if (state.started)
|
|
209
|
+
segment += ch;
|
|
210
|
+
}
|
|
211
|
+
return { segment: segment.trim(), closed: false };
|
|
212
|
+
}
|
|
213
|
+
/** The statements between the first `{` at/after `index` and its closing brace. */
|
|
214
|
+
function braceBody(lines, index) {
|
|
215
|
+
const state = { depth: 0, started: false };
|
|
216
|
+
const statements = [];
|
|
217
|
+
const last = Math.min(lines.length, index + MAX_BODY_LINES);
|
|
218
|
+
for (let i = index; i < last; i++) {
|
|
219
|
+
if (!state.started && i > index + MAX_SIGNATURE_LINES)
|
|
220
|
+
return null;
|
|
221
|
+
const { segment, closed } = consumeBraceLine(stripForBraces(lines[i]), state);
|
|
222
|
+
if (segment !== '' && (closed || !isCommentOnly(segment)))
|
|
223
|
+
statements.push(segment);
|
|
224
|
+
if (closed) {
|
|
225
|
+
const hasTodo = lines.slice(index, i + 1).some((l) => TODO_MARKER.test(l));
|
|
226
|
+
return { statements, hasTodo };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
/** An arrow declaration whose body is an expression, not a block. */
|
|
232
|
+
function arrowExpressionBody(line) {
|
|
233
|
+
const m = /=>\s*(\S.*)$/.exec(line);
|
|
234
|
+
return m && !m[1].startsWith('{') ? m[1].replace(/;+\s*$/, '').trim() : null;
|
|
235
|
+
}
|
|
236
|
+
function braceFunction(lines, index) {
|
|
237
|
+
const line = lines[index];
|
|
238
|
+
if (braceDeclaration(line) === null)
|
|
239
|
+
return null;
|
|
240
|
+
const expression = arrowExpressionBody(line);
|
|
241
|
+
if (expression !== null) {
|
|
242
|
+
const isNothing = /^(?:null|undefined)$/.test(expression);
|
|
243
|
+
return { skeleton: isNothing, kind: isNothing ? 'placeholder_return' : null };
|
|
244
|
+
}
|
|
245
|
+
const body = braceBody(lines, index);
|
|
246
|
+
if (body === null)
|
|
247
|
+
return NOT_A_SKELETON;
|
|
248
|
+
const statements = body.statements.map((s) => s.replace(/;+\s*$/, '').trim()).filter(Boolean);
|
|
249
|
+
const skeleton = statements.length === 0
|
|
250
|
+
? body.hasTodo
|
|
251
|
+
: statements.every((s) => PLACEHOLDER_STATEMENT.test(s));
|
|
252
|
+
const constantReturn = statements.some((s) => RETURN_STATEMENT.test(s));
|
|
253
|
+
return { skeleton, kind: skeleton && constantReturn ? 'placeholder_return' : null };
|
|
254
|
+
}
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// scanText
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
/**
|
|
259
|
+
* Scan one file's text. Pure and deterministic: the same input always yields the same hits,
|
|
260
|
+
* in ascending line order, at most one per line and kind.
|
|
261
|
+
*/
|
|
262
|
+
export function scanText(text) {
|
|
263
|
+
const lines = text
|
|
264
|
+
.split(/\r?\n/)
|
|
265
|
+
.map((l) => (l.length > MAX_LINE_CHARS ? l.slice(0, MAX_LINE_CHARS) : l));
|
|
266
|
+
const found = new Map();
|
|
267
|
+
const add = (index, kind) => {
|
|
268
|
+
const key = `${index}:${kind}`;
|
|
269
|
+
if (found.has(key))
|
|
270
|
+
return;
|
|
271
|
+
found.set(key, { line: index + 1, kind, text: clipSnippet(lines[index]) });
|
|
272
|
+
};
|
|
273
|
+
let functions = 0;
|
|
274
|
+
let skeletonFunctions = 0;
|
|
275
|
+
for (let index = 0; index < lines.length; index++) {
|
|
276
|
+
const kind = lineKind(lines, index);
|
|
277
|
+
if (kind)
|
|
278
|
+
add(index, kind);
|
|
279
|
+
const fn = pythonFunction(lines, index) ?? braceFunction(lines, index);
|
|
280
|
+
if (!fn)
|
|
281
|
+
continue;
|
|
282
|
+
functions++;
|
|
283
|
+
if (!fn.skeleton)
|
|
284
|
+
continue;
|
|
285
|
+
skeletonFunctions++;
|
|
286
|
+
if (fn.kind)
|
|
287
|
+
add(index, fn.kind);
|
|
288
|
+
}
|
|
289
|
+
const findings = [...found.values()].sort((a, b) => a.line - b.line || a.kind.localeCompare(b.kind));
|
|
290
|
+
return { findings, functions, skeletonFunctions };
|
|
291
|
+
}
|
|
292
|
+
const DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
293
|
+
const DEFAULT_MAX_FILES = 200;
|
|
294
|
+
const MAX_WALK_DEPTH = 6;
|
|
295
|
+
const BINARY_PROBE_BYTES = 4096;
|
|
296
|
+
const SKIP_DIRECTORIES = new Set([
|
|
297
|
+
'node_modules',
|
|
298
|
+
'.git',
|
|
299
|
+
'dist',
|
|
300
|
+
'build',
|
|
301
|
+
'out',
|
|
302
|
+
'coverage',
|
|
303
|
+
'.next',
|
|
304
|
+
'target',
|
|
305
|
+
'__pycache__',
|
|
306
|
+
'.venv',
|
|
307
|
+
'venv',
|
|
308
|
+
'.tox',
|
|
309
|
+
'.cache',
|
|
310
|
+
]);
|
|
311
|
+
const CODE_EXTENSIONS = new Set([
|
|
312
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.go', '.rs', '.java', '.kt',
|
|
313
|
+
'.c', '.h', '.cc', '.cpp', '.hpp', '.cs', '.rb', '.php', '.swift', '.scala', '.lua', '.sh',
|
|
314
|
+
]);
|
|
315
|
+
function isInside(root, target) {
|
|
316
|
+
const rel = relative(root, target);
|
|
317
|
+
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
318
|
+
}
|
|
319
|
+
function displayPath(state, real) {
|
|
320
|
+
return relative(state.root, real).replace(/\\/g, '/') || '.';
|
|
321
|
+
}
|
|
322
|
+
async function scanFile(state, real, size) {
|
|
323
|
+
const shown = displayPath(state, real);
|
|
324
|
+
if (size > state.maxBytes) {
|
|
325
|
+
state.report.skipped.push({ path: shown, reason: `larger than ${state.maxBytes} bytes` });
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const buffer = await fs.readFile(real);
|
|
329
|
+
if (buffer.subarray(0, BINARY_PROBE_BYTES).includes(0)) {
|
|
330
|
+
state.report.skipped.push({ path: shown, reason: 'binary file' });
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const scan = scanText(buffer.toString('utf8'));
|
|
334
|
+
state.report.files++;
|
|
335
|
+
state.report.functions += scan.functions;
|
|
336
|
+
state.report.skeletonFunctions += scan.skeletonFunctions;
|
|
337
|
+
for (const f of scan.findings)
|
|
338
|
+
state.report.findings.push({ ...f, file: shown });
|
|
339
|
+
}
|
|
340
|
+
async function walkDirectory(state, dir, depth) {
|
|
341
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
342
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
343
|
+
for (const entry of entries) {
|
|
344
|
+
if (state.report.files >= state.maxFiles) {
|
|
345
|
+
state.report.truncated = true;
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const full = join(dir, entry.name);
|
|
349
|
+
if (entry.isDirectory()) {
|
|
350
|
+
const descend = depth < MAX_WALK_DEPTH && !SKIP_DIRECTORIES.has(entry.name);
|
|
351
|
+
if (descend)
|
|
352
|
+
await walkDirectory(state, full, depth + 1);
|
|
353
|
+
}
|
|
354
|
+
else if (entry.isFile() && CODE_EXTENSIONS.has(extname(entry.name).toLowerCase())) {
|
|
355
|
+
await visit(state, full, depth);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/** Scan one path (file or directory) once; refuses anything whose real path leaves the root. */
|
|
360
|
+
async function visit(state, raw, depth) {
|
|
361
|
+
let real;
|
|
362
|
+
try {
|
|
363
|
+
real = await fs.realpath(resolve(state.root, raw));
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
state.report.skipped.push({ path: raw, reason: 'not found' });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (!isInside(state.root, real)) {
|
|
370
|
+
state.report.skipped.push({ path: raw, reason: 'outside the working directory' });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (state.seen.has(real))
|
|
374
|
+
return;
|
|
375
|
+
state.seen.add(real);
|
|
376
|
+
const stat = await fs.stat(real);
|
|
377
|
+
if (stat.isDirectory())
|
|
378
|
+
return walkDirectory(state, real, depth);
|
|
379
|
+
if (!stat.isFile())
|
|
380
|
+
return;
|
|
381
|
+
if (state.report.files >= state.maxFiles) {
|
|
382
|
+
state.report.truncated = true;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
await scanFile(state, real, stat.size);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Scan files and directories under `cwd`. Directories are walked (bounded depth, known code
|
|
389
|
+
* extensions, build and vendor trees skipped); explicit files are scanned whatever their
|
|
390
|
+
* extension. Only the first `GOV_LIMITS.scanPathsMax` paths are used; the rest are counted
|
|
391
|
+
* as truncation.
|
|
392
|
+
*/
|
|
393
|
+
export async function scanPaths(options) {
|
|
394
|
+
const report = {
|
|
395
|
+
files: 0,
|
|
396
|
+
findings: [],
|
|
397
|
+
skipped: [],
|
|
398
|
+
functions: 0,
|
|
399
|
+
skeletonFunctions: 0,
|
|
400
|
+
skeletonRatio: 0,
|
|
401
|
+
truncated: false,
|
|
402
|
+
};
|
|
403
|
+
const state = {
|
|
404
|
+
root: await fs.realpath(resolve(options.cwd)),
|
|
405
|
+
maxBytes: options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES,
|
|
406
|
+
maxFiles: options.maxFiles ?? DEFAULT_MAX_FILES,
|
|
407
|
+
seen: new Set(),
|
|
408
|
+
report,
|
|
409
|
+
};
|
|
410
|
+
const unique = [...new Set(options.paths.map((p) => p.trim()).filter((p) => p !== ''))];
|
|
411
|
+
if (unique.length > GOV_LIMITS.scanPathsMax)
|
|
412
|
+
report.truncated = true;
|
|
413
|
+
for (const p of unique.slice(0, GOV_LIMITS.scanPathsMax))
|
|
414
|
+
await visit(state, p, 0);
|
|
415
|
+
report.skeletonRatio = report.functions === 0 ? 0 : report.skeletonFunctions / report.functions;
|
|
416
|
+
return report;
|
|
417
|
+
}
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
419
|
+
// Output and the tool
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
const MAX_FINDINGS_SHOWN = 80;
|
|
422
|
+
const MAX_SKIPPED_SHOWN = 10;
|
|
423
|
+
/** The text the model reads. The first line is `OK:` (7.1 rule 2). */
|
|
424
|
+
export function formatScanReport(report) {
|
|
425
|
+
const lines = [
|
|
426
|
+
`OK: scanned ${report.files} file(s): ${report.functions} function(s), ` +
|
|
427
|
+
`${report.skeletonFunctions} suspected skeleton, ` +
|
|
428
|
+
`skeletonRatio ${report.skeletonRatio.toFixed(2)}.`,
|
|
429
|
+
];
|
|
430
|
+
for (const f of report.findings.slice(0, MAX_FINDINGS_SHOWN)) {
|
|
431
|
+
lines.push(`${f.file}:${f.line}:${f.kind} ${f.text}`);
|
|
432
|
+
}
|
|
433
|
+
if (report.findings.length > MAX_FINDINGS_SHOWN) {
|
|
434
|
+
lines.push(`(${report.findings.length - MAX_FINDINGS_SHOWN} more hits not shown)`);
|
|
435
|
+
}
|
|
436
|
+
for (const s of report.skipped.slice(0, MAX_SKIPPED_SHOWN)) {
|
|
437
|
+
lines.push(`SKIPPED ${s.path}: ${s.reason}`);
|
|
438
|
+
}
|
|
439
|
+
if (report.skipped.length > MAX_SKIPPED_SHOWN) {
|
|
440
|
+
lines.push(`(${report.skipped.length - MAX_SKIPPED_SHOWN} more skipped paths not shown)`);
|
|
441
|
+
}
|
|
442
|
+
if (report.truncated) {
|
|
443
|
+
lines.push(`TRUNCATED: the scan stopped at its file or path cap.`);
|
|
444
|
+
}
|
|
445
|
+
lines.push(`A skeletonRatio above ${GOV_LIMITS.skeletonRatioThreshold} marks a skeleton submission. ` +
|
|
446
|
+
'This scan is heuristic evidence only: confirm each hit by reading the code.');
|
|
447
|
+
return lines.join('\n');
|
|
448
|
+
}
|
|
449
|
+
function pathsFrom(raw) {
|
|
450
|
+
const value = raw !== null && typeof raw === 'object' ? raw.paths : undefined;
|
|
451
|
+
if (typeof value === 'string')
|
|
452
|
+
return value.split(/\r?\n/);
|
|
453
|
+
return Array.isArray(value) ? value.filter((p) => typeof p === 'string') : [];
|
|
454
|
+
}
|
|
455
|
+
/** `gov_scan`: REV, TEST and INSP. Read-only. */
|
|
456
|
+
export function makeScanTool(ctx) {
|
|
457
|
+
return {
|
|
458
|
+
name: 'gov_scan',
|
|
459
|
+
label: 'Scan for incomplete work',
|
|
460
|
+
description: 'Scan files or directories for incomplete implementations: TODO/FIXME/XXX, "not ' +
|
|
461
|
+
'implemented", empty catch blocks, bodies that are only pass or "...", and functions ' +
|
|
462
|
+
'that only return a placeholder. Returns file:line:kind hits and a skeletonRatio. ' +
|
|
463
|
+
`Paths are relative to the working directory (at most ${GOV_LIMITS.scanPathsMax}; ` +
|
|
464
|
+
'paths outside it are refused). Heuristic evidence only: it never scores anyone.',
|
|
465
|
+
parameters: {
|
|
466
|
+
type: 'object',
|
|
467
|
+
properties: {
|
|
468
|
+
paths: {
|
|
469
|
+
type: 'array',
|
|
470
|
+
items: { type: 'string' },
|
|
471
|
+
description: 'Files or directories to scan.',
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
required: ['paths'],
|
|
475
|
+
},
|
|
476
|
+
async execute(_id, params) {
|
|
477
|
+
const paths = pathsFrom(params)
|
|
478
|
+
.map((p) => p.trim())
|
|
479
|
+
.filter((p) => p !== '');
|
|
480
|
+
if (paths.length === 0) {
|
|
481
|
+
return textResult('NOT ACCEPTED: gov_scan needs paths: a list of files or directories.');
|
|
482
|
+
}
|
|
483
|
+
return textResult(formatScanReport(await scanPaths({ cwd: ctx.getCwd(), paths })));
|
|
484
|
+
},
|
|
485
|
+
};
|
|
486
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools every governed child can call: `ledger_standings`, `gov_read`, `gov_view`
|
|
3
|
+
* (governance-mode 7.3, 8.3).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
6
|
+
*
|
|
7
|
+
* THESE THREE ARE THE ONLY WINDOWS ONTO GOVERNANCE STATE. A child has no file path to the
|
|
8
|
+
* governance store (and the read tripwire refuses calls that mention it, D-6); everything it
|
|
9
|
+
* may know about other agents arrives through `gov_read`, which the engine answers from the
|
|
10
|
+
* ONE visibility matrix, and `gov_view`, which the engine answers from a deterministic
|
|
11
|
+
* policy (D-9) and logs either way. The tools here only shape and validate the request; the
|
|
12
|
+
* decision about what a role may see is never made in this layer.
|
|
13
|
+
*
|
|
14
|
+
* Both request tools are identity-bound by their factories: the model cannot ask "as" a
|
|
15
|
+
* different agent.
|
|
16
|
+
*/
|
|
17
|
+
import { type AgentTool } from '@aragon-agent/core';
|
|
18
|
+
import type { GovernanceEngineApi, Identity } from '../types.js';
|
|
19
|
+
import { type Normalized } from './normalize-util.js';
|
|
20
|
+
/** What `gov_read` asks the engine for: a listing, or one artifact page. */
|
|
21
|
+
export interface ReadRequest {
|
|
22
|
+
list?: true;
|
|
23
|
+
id?: string;
|
|
24
|
+
offset?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface ViewRequest {
|
|
27
|
+
target: string;
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate a `gov_read` request. Pure.
|
|
32
|
+
*
|
|
33
|
+
* Repairs: no `id` and no `list` becomes a listing (the useful default); both given means
|
|
34
|
+
* the `id` wins; a negative `offset` becomes 0.
|
|
35
|
+
*/
|
|
36
|
+
export declare function normalizeReadRequest(raw: unknown): Normalized<ReadRequest>;
|
|
37
|
+
/** Validate a `gov_view` request. Pure. The reason must say WHY, not merely exist. */
|
|
38
|
+
export declare function normalizeViewRequest(raw: unknown): Normalized<ViewRequest>;
|
|
39
|
+
/** `ledger_standings`: the public scoreboard. Takes no arguments. */
|
|
40
|
+
export declare function makeStandingsTool(engine: GovernanceEngineApi): AgentTool;
|
|
41
|
+
/** `gov_read`: artifacts this role may see, paged. Visibility is the engine's decision. */
|
|
42
|
+
export declare function makeReadTool(engine: GovernanceEngineApi, identity: Identity): AgentTool;
|
|
43
|
+
/** `gov_view`: ask to see something outside the default matrix. Decided by fixed policy. */
|
|
44
|
+
export declare function makeViewTool(engine: GovernanceEngineApi, identity: Identity): AgentTool;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools every governed child can call: `ledger_standings`, `gov_read`, `gov_view`
|
|
3
|
+
* (governance-mode 7.3, 8.3).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
6
|
+
*
|
|
7
|
+
* THESE THREE ARE THE ONLY WINDOWS ONTO GOVERNANCE STATE. A child has no file path to the
|
|
8
|
+
* governance store (and the read tripwire refuses calls that mention it, D-6); everything it
|
|
9
|
+
* may know about other agents arrives through `gov_read`, which the engine answers from the
|
|
10
|
+
* ONE visibility matrix, and `gov_view`, which the engine answers from a deterministic
|
|
11
|
+
* policy (D-9) and logs either way. The tools here only shape and validate the request; the
|
|
12
|
+
* decision about what a role may see is never made in this layer.
|
|
13
|
+
*
|
|
14
|
+
* Both request tools are identity-bound by their factories: the model cannot ask "as" a
|
|
15
|
+
* different agent.
|
|
16
|
+
*/
|
|
17
|
+
import { textResult } from '@aragon-agent/core';
|
|
18
|
+
import { GOV_LIMITS } from '../limits.js';
|
|
19
|
+
import { Issues, formatNotAccepted, isRecord, readBool, readInteger, withNotes, } from './normalize-util.js';
|
|
20
|
+
const ARTIFACT_ID_CHARS = 120;
|
|
21
|
+
const VIEW_TARGET_CHARS = 200;
|
|
22
|
+
const VIEW_REASON_CHARS = 600;
|
|
23
|
+
/**
|
|
24
|
+
* Validate a `gov_read` request. Pure.
|
|
25
|
+
*
|
|
26
|
+
* Repairs: no `id` and no `list` becomes a listing (the useful default); both given means
|
|
27
|
+
* the `id` wins; a negative `offset` becomes 0.
|
|
28
|
+
*/
|
|
29
|
+
export function normalizeReadRequest(raw) {
|
|
30
|
+
const issues = new Issues();
|
|
31
|
+
const args = isRecord(raw) ? raw : {};
|
|
32
|
+
const id = issues.str(args.id, 'id', 400);
|
|
33
|
+
if (id.length > ARTIFACT_ID_CHARS) {
|
|
34
|
+
issues.fail(`id is not an artifact id (at most ${ARTIFACT_ID_CHARS} chars); use list.`);
|
|
35
|
+
}
|
|
36
|
+
const wantsList = readBool(args.list) === true;
|
|
37
|
+
const offset = readInteger(args.offset);
|
|
38
|
+
if (args.offset !== undefined && offset === undefined) {
|
|
39
|
+
issues.fail('offset must be a non-negative integer.');
|
|
40
|
+
}
|
|
41
|
+
if (id !== '' && wantsList)
|
|
42
|
+
issues.note('both id and list were given; reading the id.');
|
|
43
|
+
if (id === '' && !wantsList)
|
|
44
|
+
issues.note('neither id nor list was given; listing artifacts.');
|
|
45
|
+
if (offset !== undefined && offset < 0)
|
|
46
|
+
issues.note('offset was negative; using 0.');
|
|
47
|
+
const request = id !== ''
|
|
48
|
+
? { id, ...(offset !== undefined ? { offset: Math.max(0, offset) } : {}) }
|
|
49
|
+
: { list: true };
|
|
50
|
+
return issues.result(request);
|
|
51
|
+
}
|
|
52
|
+
/** Validate a `gov_view` request. Pure. The reason must say WHY, not merely exist. */
|
|
53
|
+
export function normalizeViewRequest(raw) {
|
|
54
|
+
const issues = new Issues();
|
|
55
|
+
const args = isRecord(raw) ? raw : {};
|
|
56
|
+
const target = issues.str(args.target, 'target', VIEW_TARGET_CHARS, true);
|
|
57
|
+
const reason = issues.str(args.reason, 'reason', VIEW_REASON_CHARS, true);
|
|
58
|
+
if (reason !== '' && reason.length < GOV_LIMITS.viewReasonMinChars) {
|
|
59
|
+
issues.fail(`reason must explain why you need it, at least ${GOV_LIMITS.viewReasonMinChars} ` +
|
|
60
|
+
`characters (got ${reason.length}).`);
|
|
61
|
+
}
|
|
62
|
+
return issues.result({ target, reason });
|
|
63
|
+
}
|
|
64
|
+
function runRequest(tool, normalized, forward) {
|
|
65
|
+
if (!normalized.ok)
|
|
66
|
+
return textResult(formatNotAccepted(tool, normalized.errors));
|
|
67
|
+
return textResult(withNotes(forward(normalized.value), normalized.notes));
|
|
68
|
+
}
|
|
69
|
+
/** `ledger_standings`: the public scoreboard. Takes no arguments. */
|
|
70
|
+
export function makeStandingsTool(engine) {
|
|
71
|
+
return {
|
|
72
|
+
name: 'ledger_standings',
|
|
73
|
+
label: 'GT standings',
|
|
74
|
+
description: 'Show the public GT standings of the run: every scored agent with confirmed GT, ' +
|
|
75
|
+
'escrowed GT (P), standing label and team health. Read-only.',
|
|
76
|
+
parameters: { type: 'object', properties: {} },
|
|
77
|
+
async execute() {
|
|
78
|
+
return textResult(engine.standings());
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** `gov_read`: artifacts this role may see, paged. Visibility is the engine's decision. */
|
|
83
|
+
export function makeReadTool(engine, identity) {
|
|
84
|
+
return {
|
|
85
|
+
name: 'gov_read',
|
|
86
|
+
label: 'Read governed artifact',
|
|
87
|
+
description: 'Read material from the governed run that your role may see. Call with list:true to ' +
|
|
88
|
+
'see the artifact ids you can read, then with id (and offset for the next page; a ' +
|
|
89
|
+
`page is about ${GOV_LIMITS.readPageChars} characters). A request for something your ` +
|
|
90
|
+
'role may not see is answered with NOT ACCEPTED and is logged.',
|
|
91
|
+
parameters: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
list: { type: 'boolean', description: 'List the artifacts you may read.' },
|
|
95
|
+
id: { type: 'string', description: 'Artifact id from the list.' },
|
|
96
|
+
offset: { type: 'number', description: 'Character offset of the page to read.' },
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
async execute(_id, params) {
|
|
100
|
+
return runRequest('gov_read', normalizeReadRequest(params), (request) => engine.readArtifact(identity, request));
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** `gov_view`: ask to see something outside the default matrix. Decided by fixed policy. */
|
|
105
|
+
export function makeViewTool(engine, identity) {
|
|
106
|
+
return {
|
|
107
|
+
name: 'gov_view',
|
|
108
|
+
label: 'Request a view',
|
|
109
|
+
description: 'Ask to see material outside your default view. target names what you want; reason ' +
|
|
110
|
+
`(at least ${GOV_LIMITS.viewReasonMinChars} characters) says why you need it. A fixed ` +
|
|
111
|
+
`policy decides; you get ${GOV_LIMITS.viewRequestsPerChild} request(s) and every ` +
|
|
112
|
+
'request is logged for the Inspector.',
|
|
113
|
+
parameters: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
target: { type: 'string', description: 'What you want to see.' },
|
|
117
|
+
reason: { type: 'string', description: 'Why you need it.' },
|
|
118
|
+
},
|
|
119
|
+
required: ['target', 'reason'],
|
|
120
|
+
},
|
|
121
|
+
async execute(_id, params) {
|
|
122
|
+
return runRequest('gov_view', normalizeViewRequest(params), (request) => engine.requestView(identity, request));
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizers for `gov_submit_work` / `gov_submit_review` / `gov_submit_test`
|
|
3
|
+
* (governance-mode 5.9, 7.1, 7.3).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY (`governance/` is glyph-scanned). Pure: no I/O, no clock.
|
|
6
|
+
*
|
|
7
|
+
* THE OUTPUT IS BUILT FROM KNOWN FIELDS ONLY. Each normalizer copies the keys it knows into
|
|
8
|
+
* a fresh object, so an `agentId` (or any other key) in the arguments can never reach the
|
|
9
|
+
* engine: identity is closed over by the tool factory, not argued by the model (5.9).
|
|
10
|
+
*
|
|
11
|
+
* EXPORTED FOR THE ENGINE. A submission that arrives by another path (resume, replay, a
|
|
12
|
+
* future REST surface) is validated by the same functions and gets the same verdict.
|
|
13
|
+
*
|
|
14
|
+
* CONTRADICTIONS ARE REJECTED, NOT GUESSED (D-15). A `PASS` verdict next to a blocker
|
|
15
|
+
* finding, or an `ALL_PASS` next to a `gap` row, is not repairable: either the verdict or
|
|
16
|
+
* the evidence is wrong and only the author knows which.
|
|
17
|
+
*/
|
|
18
|
+
import type { ReviewSubmission, TestSubmission, WorkSubmission } from '../types.js';
|
|
19
|
+
import { type Normalized } from './normalize-util.js';
|
|
20
|
+
export type WorkSubmissionInput = Pick<WorkSubmission, 'wpId' | 'summary' | 'filesChanged' | 'interfaceContracts' | 'verification'> & Partial<Pick<WorkSubmission, 'appeal' | 'escalate'>>;
|
|
21
|
+
/** Validate and repair a `gov_submit_work` argument object. Pure. */
|
|
22
|
+
export declare function normalizeWorkSubmission(raw: unknown): Normalized<WorkSubmissionInput>;
|
|
23
|
+
export type ReviewSubmissionInput = Pick<ReviewSubmission, 'wpId' | 'verdict' | 'checklist' | 'findings' | 'fixesApplied' | 'integrationAssessment' | 'appealResponses' | 'requiredChanges'> & Partial<Pick<ReviewSubmission, 'excellence' | 'securityFinding'>>;
|
|
24
|
+
/** Validate and repair a `gov_submit_review` argument object. Pure. */
|
|
25
|
+
export declare function normalizeReviewSubmission(raw: unknown): Normalized<ReviewSubmissionInput>;
|
|
26
|
+
export type TestSubmissionInput = Pick<TestSubmission, 'wpId' | 'verdict' | 'traceability' | 'commands' | 'issues' | 'boundaryTestsWritten' | 'claims'> & Partial<Pick<TestSubmission, 'skeletonRatio' | 'skeletonEvidence'>>;
|
|
27
|
+
/** Validate and repair a `gov_submit_test` argument object. Pure. */
|
|
28
|
+
export declare function normalizeTestSubmission(raw: unknown): Normalized<TestSubmissionInput>;
|