arkaos 4.16.0 → 4.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/VERSION +1 -1
- package/arka/SKILL.md +1 -1
- package/config/agents-provenance.yaml +126 -0
- package/config/skills-provenance.yaml +357 -0
- package/core/agents/provenance.py +117 -0
- package/core/agents/registry_gen.py +75 -57
- package/core/cognition/insights/store.py +114 -7
- package/core/cognition/memory/schemas.py +45 -5
- package/core/governance/harness_scanner.py +776 -0
- package/core/governance/harness_scanner_cli.py +131 -0
- package/core/hooks/pre_tool_use.py +59 -0
- package/core/provenance.py +108 -0
- package/core/skills/__init__.py +21 -0
- package/core/skills/provenance.py +157 -0
- package/core/workflow/config_guard.py +177 -0
- package/core/workflow/transcript_scope.py +46 -0
- package/departments/dev/skills/click-path-audit/SKILL.md +76 -0
- package/departments/dev/skills/pr-test-analyzer/SKILL.md +79 -0
- package/departments/dev/skills/silent-failure-hunter/SKILL.md +72 -0
- package/departments/dev/skills/type-design-analyzer/SKILL.md +73 -0
- package/installer/cli.js +24 -0
- package/installer/doctor.js +46 -0
- package/knowledge/agents-registry-v2.json +259 -1
- package/knowledge/skills-manifest.json +1005 -237
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/marketplace_gen.py +57 -1
- package/scripts/skill_validator.py +133 -62
|
@@ -72,3 +72,49 @@ def split_from_path(transcript_path: str) -> ScopeSplit:
|
|
|
72
72
|
except OSError:
|
|
73
73
|
return ScopeSplit()
|
|
74
74
|
return split_by_scope(raw)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def recent_user_messages(raw_text: str, limit: int = 6) -> list[str]:
|
|
78
|
+
"""The most recent USER message texts, newest last.
|
|
79
|
+
|
|
80
|
+
``split_by_scope`` collects assistant records only. A gate that must
|
|
81
|
+
know what the OPERATOR asked — not what the agent said — needs the
|
|
82
|
+
other role, and reading the wrong one inverts the gate: the guarded
|
|
83
|
+
agent could authorise its own edit while the operator's real request
|
|
84
|
+
is never seen. Sidechain (subagent) user turns are excluded — a
|
|
85
|
+
dispatched agent's prompt is not the operator speaking.
|
|
86
|
+
"""
|
|
87
|
+
found: list[str] = []
|
|
88
|
+
for line in raw_text.splitlines():
|
|
89
|
+
if not line.strip():
|
|
90
|
+
continue
|
|
91
|
+
try:
|
|
92
|
+
record = json.loads(line)
|
|
93
|
+
except json.JSONDecodeError:
|
|
94
|
+
continue
|
|
95
|
+
if not isinstance(record, dict):
|
|
96
|
+
continue
|
|
97
|
+
message = record.get("message")
|
|
98
|
+
message = message if isinstance(message, dict) else {}
|
|
99
|
+
role = record.get("role") or message.get("role")
|
|
100
|
+
if role != "user" or record.get("isSidechain", False):
|
|
101
|
+
continue
|
|
102
|
+
content = record.get("content")
|
|
103
|
+
if content is None:
|
|
104
|
+
content = message.get("content")
|
|
105
|
+
text = _extract_text(content)
|
|
106
|
+
if text:
|
|
107
|
+
found.append(text)
|
|
108
|
+
return found[-limit:]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def user_messages_from_path(transcript_path: str, limit: int = 6) -> list[str]:
|
|
112
|
+
"""Recent operator messages from a transcript file. Never raises."""
|
|
113
|
+
path = Path(transcript_path) if transcript_path else None
|
|
114
|
+
if path is None or not path.exists():
|
|
115
|
+
return []
|
|
116
|
+
try:
|
|
117
|
+
raw = path.read_text(encoding="utf-8", errors="replace")
|
|
118
|
+
except OSError:
|
|
119
|
+
return []
|
|
120
|
+
return recent_user_messages(raw, limit)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev/click-path-audit
|
|
3
|
+
description: >
|
|
4
|
+
Simulates every interactive handler call-by-call to find bugs a normal
|
|
5
|
+
read skips over — shared-state side effects, handlers that silently undo
|
|
6
|
+
each other, and async races — then checks the final UI state matches
|
|
7
|
+
what the control promises. TRIGGER: "/dev click-path-audit", "the button
|
|
8
|
+
does nothing", "state side effects", "handlers cancel each other", "why
|
|
9
|
+
is the UI wrong after clicking", "o botão não faz nada", "audita o fluxo
|
|
10
|
+
de cliques"; run on an interactive component whose behaviour is wrong but
|
|
11
|
+
whose code looks right. SKIP: general pre-merge review ->
|
|
12
|
+
dev/code-review wins; visual/layout/brand review ->
|
|
13
|
+
brand/design-review wins.
|
|
14
|
+
allowed-tools: [Read, Grep, Glob]
|
|
15
|
+
metadata:
|
|
16
|
+
origin: ecc-derived
|
|
17
|
+
source: https://github.com/affaan-m/ecc
|
|
18
|
+
license: MIT
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Click-Path Audit — `/dev click-path-audit`
|
|
22
|
+
|
|
23
|
+
> **Agent:** Diana (Senior Frontend Developer) | **Framework:** Handler state-tracing, shared-state side-effect analysis
|
|
24
|
+
|
|
25
|
+
Some interface bugs survive every check that looks at one thing at a time.
|
|
26
|
+
Each handler compiles, each function returns the right type, each call
|
|
27
|
+
works when you test it alone — and the button still does the wrong thing,
|
|
28
|
+
because the second call quietly resets the state the first one set. This
|
|
29
|
+
lens catches those by simulating the handler on paper: it walks the calls
|
|
30
|
+
in the order they run and tracks what each reads, writes, and resets in
|
|
31
|
+
shared state, so a cancelling side effect shows up before a user ever
|
|
32
|
+
clicks.
|
|
33
|
+
|
|
34
|
+
## The bug this catches
|
|
35
|
+
|
|
36
|
+
A "New Email" button called `setComposeMode(true)` then `selectThread(null)`.
|
|
37
|
+
Each call was correct on its own, but `selectThread` reset `composeMode`
|
|
38
|
+
back to false as a side effect, so the button opened nothing — no type
|
|
39
|
+
error, no crash, and no unit test to catch it. Tracing the two calls in
|
|
40
|
+
order makes the reset obvious on the page.
|
|
41
|
+
|
|
42
|
+
## Process
|
|
43
|
+
|
|
44
|
+
For every interactive touchpoint in scope (`onClick`, `onSubmit`, `onChange`, effects):
|
|
45
|
+
|
|
46
|
+
1. **Identify** the handler.
|
|
47
|
+
2. **Trace** each call it makes, in order.
|
|
48
|
+
3. For each call record: what shared state it **reads**, what it **writes**, and any **side effect** — especially a state it clears or resets that it does not obviously own.
|
|
49
|
+
4. **Check** whether any later call undoes a state change an earlier call made.
|
|
50
|
+
5. **Check** the final state matches what the control's label promises.
|
|
51
|
+
6. **Check** for races — async calls whose resolution order changes the outcome (a stale response overwriting a fresh one).
|
|
52
|
+
|
|
53
|
+
## Proactive Triggers
|
|
54
|
+
|
|
55
|
+
Surface these WITHOUT being asked:
|
|
56
|
+
|
|
57
|
+
- two calls in one handler that write the same store slice → which one wins, and is that intended
|
|
58
|
+
- a setter with a side effect that resets unrelated state (a `select`/`reset`/`clear` that touches more than its name implies) → the earlier change it silently undoes
|
|
59
|
+
- an `await` inside a handler with no guard against an out-of-order resolution → the stale-overwrite race
|
|
60
|
+
|
|
61
|
+
## Output
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
## Click-Path Report
|
|
65
|
+
|
|
66
|
+
**Scope:** {component / area audited}
|
|
67
|
+
|
|
68
|
+
### {control / handler} — {file}:{line}
|
|
69
|
+
| # | Call | Reads | Writes | Side effect |
|
|
70
|
+
|---|------|-------|--------|-------------|
|
|
71
|
+
| 1 | {fn} | {state} | {state} | {reset/clear, or none} |
|
|
72
|
+
| 2 | {fn} | {state} | {state} | {undoes call 1's write?} |
|
|
73
|
+
|
|
74
|
+
**Promised by label:** {what the user expects} · **Actual final state:** {what happens}
|
|
75
|
+
**Verdict:** {works / silently broken} · **Fix:** {reorder, isolate the side effect, guard the race}
|
|
76
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev/pr-test-analyzer
|
|
3
|
+
description: >
|
|
4
|
+
Judges whether a PR's tests would catch the change breaking — maps
|
|
5
|
+
changed code to its tests, finds untested paths and edge cases, flags
|
|
6
|
+
assertion-free and flaky tests, and ranks the gaps by blast radius.
|
|
7
|
+
TRIGGER: "/dev pr-test-analyzer", "test coverage of this PR", "are these
|
|
8
|
+
tests enough", "behavioural coverage", "test gaps", "os testes cobrem
|
|
9
|
+
isto?", "isto está bem testado?"; run on a diff before approving its
|
|
10
|
+
merge. SKIP: general code review -> dev/code-review wins; writing the
|
|
11
|
+
missing tests -> dev/tdd-cycle wins; error-handling review ->
|
|
12
|
+
dev/silent-failure-hunter wins.
|
|
13
|
+
allowed-tools: [Read, Grep, Glob, Bash]
|
|
14
|
+
metadata:
|
|
15
|
+
origin: ecc-derived
|
|
16
|
+
source: https://github.com/affaan-m/ecc
|
|
17
|
+
license: MIT
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# PR Test Analyzer — `/dev pr-test-analyzer`
|
|
21
|
+
|
|
22
|
+
> **Agent:** Rita (QA Engineer) | **Framework:** Behavioural coverage, test-quality over line-coverage
|
|
23
|
+
|
|
24
|
+
Coverage is a measure of which lines ran, and it is routinely mistaken for
|
|
25
|
+
a measure of whether the change is safe. A PR can add a feature, run every
|
|
26
|
+
new line through one happy-path test that asserts almost nothing, and post
|
|
27
|
+
a green 100%. This lens reads the diff and its tests side by side and holds
|
|
28
|
+
them to the only standard that matters at merge time: if the change were
|
|
29
|
+
wrong, would a test go red?
|
|
30
|
+
|
|
31
|
+
## Analysis
|
|
32
|
+
|
|
33
|
+
### 1. Map changed code to tests
|
|
34
|
+
- List the functions, classes, and branches the diff added or altered.
|
|
35
|
+
- Locate the tests that exercise each. Name the changed paths with **no** test.
|
|
36
|
+
|
|
37
|
+
### 2. Behavioural coverage
|
|
38
|
+
- Does each new behaviour have a test that would fail if the behaviour broke?
|
|
39
|
+
- Are the edge cases covered — empty input, boundary values, the error path, not just the success path?
|
|
40
|
+
- Are the integrations the change touches (db, queue, external call) exercised, or only mocked away?
|
|
41
|
+
|
|
42
|
+
### 3. Test quality
|
|
43
|
+
- Flag tests that assert nothing beyond "it did not throw" — a green test that proves nothing.
|
|
44
|
+
- Flag flaky patterns: time/order dependence, shared mutable state, sleeps.
|
|
45
|
+
- Check that test names describe the behaviour, so a failure reads as a spec.
|
|
46
|
+
|
|
47
|
+
### 4. Rank the gaps
|
|
48
|
+
Rate each gap by the blast radius of the bug it would let ship:
|
|
49
|
+
critical (data loss, auth, money), important (a real feature path),
|
|
50
|
+
nice-to-have (a defensive branch unlikely to trigger).
|
|
51
|
+
|
|
52
|
+
## Proactive Triggers
|
|
53
|
+
|
|
54
|
+
Surface these WITHOUT being asked:
|
|
55
|
+
|
|
56
|
+
- a changed function with no test at all → critical or important by its blast radius; name it
|
|
57
|
+
- a new test with no assertion (or only `assert not raises`) → it proves nothing; say what it should assert
|
|
58
|
+
- an error/exception path added in the diff with only the happy path tested → the failure that ships untested
|
|
59
|
+
|
|
60
|
+
## Output
|
|
61
|
+
|
|
62
|
+
```markdown
|
|
63
|
+
## PR Test Report
|
|
64
|
+
|
|
65
|
+
**Diff:** {files / functions changed}
|
|
66
|
+
**Verdict:** {tests adequate to merge? yes / gaps below}
|
|
67
|
+
|
|
68
|
+
### Coverage summary
|
|
69
|
+
{what is covered well, in one or two lines}
|
|
70
|
+
|
|
71
|
+
### Critical gaps
|
|
72
|
+
- {file}:{func} — {the behaviour with no failing test} → {the test to add}
|
|
73
|
+
|
|
74
|
+
### Test-quality issues
|
|
75
|
+
- {file}:{test} — {assertion-free / flaky} → {fix}
|
|
76
|
+
|
|
77
|
+
### Positive
|
|
78
|
+
{what the tests do genuinely well}
|
|
79
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev/silent-failure-hunter
|
|
3
|
+
description: >
|
|
4
|
+
Hunts silent failures — swallowed exceptions, errors coerced to null,
|
|
5
|
+
fallbacks that mask a broken path, lost stack traces, and unguarded
|
|
6
|
+
network/file/db/transaction calls — and reports the concrete failure
|
|
7
|
+
each one hides, ranked by blast radius. TRIGGER: "/dev
|
|
8
|
+
silent-failure-hunter", "silent failures", "swallowed errors", "empty
|
|
9
|
+
catch", "error handling review", "falhas silenciosas", "erros
|
|
10
|
+
engolidos", "isto está a falhar em silêncio?"; run on the error paths
|
|
11
|
+
of any change touching I/O, network, db, or transactions. SKIP: general
|
|
12
|
+
pre-merge review -> dev/code-review wins; test coverage of the change ->
|
|
13
|
+
dev/pr-test-analyzer wins.
|
|
14
|
+
allowed-tools: [Read, Grep, Glob]
|
|
15
|
+
metadata:
|
|
16
|
+
origin: ecc-derived
|
|
17
|
+
source: https://github.com/affaan-m/ecc
|
|
18
|
+
license: MIT
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Silent Failure Hunter — `/dev silent-failure-hunter`
|
|
22
|
+
|
|
23
|
+
> **Agent:** Rita (QA Engineer) | **Framework:** Defensive error handling, fail-loud discipline
|
|
24
|
+
|
|
25
|
+
Every catch block is a decision about who finds out when something breaks.
|
|
26
|
+
Too many of them decide that nobody does: the exception is caught and
|
|
27
|
+
dropped, the failed call returns an empty list, the timeout is swallowed
|
|
28
|
+
and the caller carries on with half the data. This lens assumes the worst
|
|
29
|
+
about each one and asks a single question of it — when this path fails at
|
|
30
|
+
runtime, who learns, and how?
|
|
31
|
+
|
|
32
|
+
## Hunt Targets
|
|
33
|
+
|
|
34
|
+
| # | Class | What to flag |
|
|
35
|
+
|---|-------|--------------|
|
|
36
|
+
| 1 | Swallowed errors | `catch {}`, `except: pass`, an error coerced to `null`/`[]`/`0` with no log and no re-raise |
|
|
37
|
+
| 2 | Inadequate logging | logged at the wrong severity, logged without the context to diagnose it, log-and-continue where the caller needed to know |
|
|
38
|
+
| 3 | Masking fallbacks | a default that hides a real failure — `.catch(() => [])`, `?? {}` over a failed fetch — so the bug surfaces three layers downstream instead of here |
|
|
39
|
+
| 4 | Broken propagation | a lost stack trace, a generic re-raise that drops the cause, an `async` path with no `await` on the error, a promise with no rejection handler |
|
|
40
|
+
| 5 | Unguarded boundaries | a network/file/db call with no timeout and no error handling; transactional work with no rollback on the failure path |
|
|
41
|
+
|
|
42
|
+
## Process
|
|
43
|
+
|
|
44
|
+
1. Grep the diff (or the named area) for the target patterns — `catch`, `except`, `.catch(`, `try`, `?? `, bare `return null`.
|
|
45
|
+
2. For each hit, answer one question: **if this path fails at runtime, who finds out, and how?** If the answer is "nobody" or "a confused engineer three files away", it is a finding.
|
|
46
|
+
3. Trace the caller once — a swallowed error is only safe if the caller genuinely does not need to know, and that is rare.
|
|
47
|
+
4. Rank by blast radius: a masked failure on a payment or data-write path is critical; a swallowed log flush is minor.
|
|
48
|
+
|
|
49
|
+
## Proactive Triggers
|
|
50
|
+
|
|
51
|
+
Surface these WITHOUT being asked:
|
|
52
|
+
|
|
53
|
+
- an empty catch on any I/O, network, or database call → critical; name what fails silently
|
|
54
|
+
- `.catch(() => <default>)` or `except: pass` on a data-fetch path → the downstream bug this plants is worse than the crash it prevents
|
|
55
|
+
- a re-raise that drops the original exception (`raise NewError()` with no `from`) → the stack trace the on-call engineer needs is gone
|
|
56
|
+
|
|
57
|
+
## Output
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
## Silent Failure Report
|
|
61
|
+
|
|
62
|
+
**Scope:** {files / diff reviewed}
|
|
63
|
+
**Findings:** {n} ({c} critical, {h} high, {m} medium)
|
|
64
|
+
|
|
65
|
+
### {SEVERITY} — {file}:{line}
|
|
66
|
+
- **Hides:** {the concrete runtime failure that goes unseen}
|
|
67
|
+
- **Impact:** {who is affected and how far downstream the real bug surfaces}
|
|
68
|
+
- **Fix:** {the specific change — log with context, re-raise with cause, add the timeout}
|
|
69
|
+
|
|
70
|
+
### Clean
|
|
71
|
+
{paths checked that handle failure honestly — say so, briefly}
|
|
72
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev/type-design-analyzer
|
|
3
|
+
description: >
|
|
4
|
+
Scores whether a module's types make illegal states unrepresentable —
|
|
5
|
+
across encapsulation, invariant expression, invariant usefulness, and
|
|
6
|
+
type-system enforcement — and names the concrete bug each weak type
|
|
7
|
+
lets through. TRIGGER: "/dev type-design-analyzer", "type design",
|
|
8
|
+
"illegal states", "make invalid states unrepresentable", "domain
|
|
9
|
+
modelling review", "revê o design dos tipos", "estes tipos estão bem
|
|
10
|
+
desenhados?"; run on the public types of a module or a domain model.
|
|
11
|
+
SKIP: general code review -> dev/code-review wins; the data-model
|
|
12
|
+
schema of a new feature -> dev/db-design or dev/ddd-model wins.
|
|
13
|
+
allowed-tools: [Read, Grep, Glob]
|
|
14
|
+
metadata:
|
|
15
|
+
origin: ecc-derived
|
|
16
|
+
source: https://github.com/affaan-m/ecc
|
|
17
|
+
license: MIT
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Type Design Analyzer — `/dev type-design-analyzer`
|
|
21
|
+
|
|
22
|
+
> **Agent:** Gabriel (Software Architect) | **Framework:** Make-illegal-states-unrepresentable, type-driven design
|
|
23
|
+
|
|
24
|
+
The strongest bug fix is the one that makes the bug impossible to write.
|
|
25
|
+
A type that admits only valid values turns a whole class of runtime error
|
|
26
|
+
into a compile error nobody can ship past. This lens reads the shape of a
|
|
27
|
+
module's data — its enums, its models, its bounded fields — and asks what
|
|
28
|
+
invalid state that shape still permits, because every permitted invalid
|
|
29
|
+
state is a bug waiting for the caller who constructs it.
|
|
30
|
+
|
|
31
|
+
## Evaluation Criteria
|
|
32
|
+
|
|
33
|
+
For each type in scope, score four dimensions and justify each score
|
|
34
|
+
with the specific state it does or does not prevent.
|
|
35
|
+
|
|
36
|
+
| Dimension | The question |
|
|
37
|
+
|-----------|--------------|
|
|
38
|
+
| **Encapsulation** | Are internals hidden? Can an invariant be violated from outside — a public field mutated past a bound, a list appended to past its cap? |
|
|
39
|
+
| **Invariant expression** | Do the types encode the business rule? Is an impossible state (a shipped order with no address, a negative price) representable at all? |
|
|
40
|
+
| **Invariant usefulness** | Do these invariants prevent *real* bugs in this domain, or are they ceremony that constrains nothing anyone would get wrong? |
|
|
41
|
+
| **Enforcement** | Is the invariant enforced by the type system, or by a convention with an easy escape hatch (a raw `str` where an enum belongs, an `Optional` that is never `None` in practice but the checker cannot know)? |
|
|
42
|
+
|
|
43
|
+
## Process
|
|
44
|
+
|
|
45
|
+
1. Grep the module for its public types — dataclasses, Pydantic models, enums, TypedDicts, the domain nouns.
|
|
46
|
+
2. For each, ask the one diagnostic question: **what invalid value can I construct with this type that the domain forbids?** Every answer is a design gap.
|
|
47
|
+
3. Prefer the fix that moves the check to construction time — a validator, a narrower type, a smart constructor — over a runtime guard the caller must remember.
|
|
48
|
+
|
|
49
|
+
## Proactive Triggers
|
|
50
|
+
|
|
51
|
+
Surface these WITHOUT being asked:
|
|
52
|
+
|
|
53
|
+
- a domain value carried as a bare `str`/`int` where an enum or a bounded newtype belongs → the illegal value it now admits
|
|
54
|
+
- an `Optional`/nullable field that is never legitimately empty → make it required and delete the downstream `None` checks
|
|
55
|
+
- a boolean pair that encodes a state machine (`is_draft` + `is_published` both settable true) → the impossible state it allows; replace with one enum
|
|
56
|
+
|
|
57
|
+
## Output
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
## Type Design Report
|
|
61
|
+
|
|
62
|
+
**Scope:** {module / types reviewed}
|
|
63
|
+
|
|
64
|
+
### {type name} — {file}:{line}
|
|
65
|
+
| Dimension | Score /5 | Why |
|
|
66
|
+
|-----------|---------|-----|
|
|
67
|
+
| Encapsulation | {n} | {the state it lets outside code violate, or "hidden"} |
|
|
68
|
+
| Invariant expression | {n} | {the impossible state it does/does not prevent} |
|
|
69
|
+
| Invariant usefulness | {n} | {real bug prevented, or ceremony} |
|
|
70
|
+
| Enforcement | {n} | {type-enforced, or the escape hatch} |
|
|
71
|
+
|
|
72
|
+
**Overall:** {one line} · **Fix:** {the narrower type / validator / smart constructor}
|
|
73
|
+
```
|
package/installer/cli.js
CHANGED
|
@@ -68,6 +68,7 @@ Usage:
|
|
|
68
68
|
npx arkaos models set <role> <provider>/<model> Re-route a role
|
|
69
69
|
npx arkaos mcp start Start the arka-tools MCP server (stdio; --write enables writes)
|
|
70
70
|
npx arkaos update --skills <curated|full> Choose the deployed skill set (default: curated on fresh installs)
|
|
71
|
+
npx arkaos shield Scan the harness config for vulnerabilities (--json)
|
|
71
72
|
npx arkaos doctor Run health checks
|
|
72
73
|
npx arkaos uninstall Remove ArkaOS
|
|
73
74
|
|
|
@@ -90,6 +91,8 @@ Examples:
|
|
|
90
91
|
npx arkaos index Index knowledge base (Obsidian vault)
|
|
91
92
|
npx arkaos search "query" Search indexed knowledge
|
|
92
93
|
npx arkaos doctor Verify installation health
|
|
94
|
+
npx arkaos shield Scan the harness config (exit 2 = critical)
|
|
95
|
+
npx arkaos shield --json Machine-readable, for CI
|
|
93
96
|
`);
|
|
94
97
|
process.exit(0);
|
|
95
98
|
}
|
|
@@ -208,6 +211,27 @@ async function main() {
|
|
|
208
211
|
break;
|
|
209
212
|
}
|
|
210
213
|
|
|
214
|
+
case "shield": {
|
|
215
|
+
// The harness config as attack surface. Exit code is the contract
|
|
216
|
+
// CI gates on (0 = A/B, 1 = C/D, 2 = F or any CRITICAL), so the
|
|
217
|
+
// child's code is propagated verbatim rather than collapsed to 1.
|
|
218
|
+
const { spawnSync } = await import("node:child_process");
|
|
219
|
+
const repoRootShield = join(__dirname, "..");
|
|
220
|
+
const pyShield = getArkaosPython();
|
|
221
|
+
if (!pyShield) { console.error("No Python found. Run: npx arkaos install"); process.exit(1); }
|
|
222
|
+
const shieldArgs = process.argv.slice(3);
|
|
223
|
+
const shieldRun = spawnSync(
|
|
224
|
+
pyShield,
|
|
225
|
+
["-m", "core.governance.harness_scanner_cli", ...shieldArgs],
|
|
226
|
+
{
|
|
227
|
+
stdio: "inherit",
|
|
228
|
+
cwd: process.cwd(),
|
|
229
|
+
env: { ...process.env, ARKAOS_ROOT: repoRootShield, PYTHONPATH: repoRootShield },
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
process.exit(shieldRun.status === null ? 1 : shieldRun.status);
|
|
233
|
+
}
|
|
234
|
+
|
|
211
235
|
case "index": {
|
|
212
236
|
const { execFileSync } = await import("node:child_process");
|
|
213
237
|
const repoRoot = join(__dirname, "..");
|
package/installer/doctor.js
CHANGED
|
@@ -531,5 +531,51 @@ export async function doctor(options = {}) {
|
|
|
531
531
|
}
|
|
532
532
|
|
|
533
533
|
console.log(`\n Results: ${passed} passed, ${warned} warnings, ${failed} failures\n`);
|
|
534
|
+
await securityAdvisory();
|
|
534
535
|
if (failed > 0) process.exit(1);
|
|
535
536
|
}
|
|
537
|
+
|
|
538
|
+
// Doctor answers "is the install healthy?". It never answered "is the
|
|
539
|
+
// install SAFE?" — a config can be perfectly healthy and still hand a
|
|
540
|
+
// third party the right to run code on this machine. The scanner does
|
|
541
|
+
// that, and doctor is where the operator already looks.
|
|
542
|
+
//
|
|
543
|
+
// Advisory only: it prints the grade and never changes doctor's exit
|
|
544
|
+
// code. A health check that starts failing on a pre-existing security
|
|
545
|
+
// posture would be a breaking change to everyone's CI, and the way to
|
|
546
|
+
// get a security tool ignored is to make it block on day one.
|
|
547
|
+
async function securityAdvisory() {
|
|
548
|
+
const python = getArkaosPython();
|
|
549
|
+
const repoRoot = getRepoRoot();
|
|
550
|
+
if (!python || !repoRoot) return;
|
|
551
|
+
const { spawnSync } = await import("node:child_process");
|
|
552
|
+
const run = spawnSync(
|
|
553
|
+
python,
|
|
554
|
+
["-m", "core.governance.harness_scanner_cli", "--json"],
|
|
555
|
+
{
|
|
556
|
+
encoding: "utf-8",
|
|
557
|
+
cwd: process.cwd(),
|
|
558
|
+
env: { ...process.env, ARKAOS_ROOT: repoRoot, PYTHONPATH: repoRoot },
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
// A crash must not read as "clean". If the scanner did not return a
|
|
562
|
+
// parseable report, say so — the same crash-is-not-clean rule the
|
|
563
|
+
// scanner enforces internally.
|
|
564
|
+
let report = null;
|
|
565
|
+
if (run.status !== null && run.stdout) {
|
|
566
|
+
try { report = JSON.parse(run.stdout); } catch { report = null; }
|
|
567
|
+
}
|
|
568
|
+
if (!report || !Array.isArray(report.findings)) {
|
|
569
|
+
console.log(" Security: scan did not complete — run `npx arkaos shield` directly\n");
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const cross = process.stdout.isTTY ? "\x1b[31m✗\x1b[0m" : "x";
|
|
574
|
+
const n = report.findings.length;
|
|
575
|
+
const plural = n === 1 ? "finding" : "findings";
|
|
576
|
+
console.log(` Security: grade ${report.grade} (${report.score}/100), ${n} ${plural}`);
|
|
577
|
+
for (const finding of report.findings.filter((f) => f.severity === "critical").slice(0, 3)) {
|
|
578
|
+
console.log(` ${cross} ${finding.rule} — ${finding.where}`);
|
|
579
|
+
}
|
|
580
|
+
console.log(n > 0 ? " Run: npx arkaos shield\n" : "");
|
|
581
|
+
}
|