litgrok-ai 0.2.6 → 0.2.8
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/.grok/skills/lit-scientific-visualization/SKILL.md +19 -19
- package/.grok/skills/lit-scientific-visualization/scripts/verify-canonical-corpus.mjs +26 -3
- package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/references/_canonical-corpus/manifest.json +10 -0
- package/.grok/vendor/NOTICE.md +16 -0
- package/.grok/vendor/licenses/045_scientific-visualization-MIT.txt +21 -0
- package/.grok/vendor/provenance/045_scientific-visualization.md +37 -0
- package/CHANGELOG.md +22 -0
- package/README.md +10 -10
- package/README_ko-KR.md +10 -10
- package/bin/litgrok.mjs +277 -17
- package/package.json +2 -1
- package/plugin.json +1 -1
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/assets/color_palettes.py +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/assets/nature.mplstyle +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/assets/presentation.mplstyle +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/assets/publication.mplstyle +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/evals/evals.json +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/references/color_palettes.md +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/references/journal_requirements.md +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/references/matplotlib_examples.md +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/references/mdanalysis_martini_visualization.md +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/references/publication_guidelines.md +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/references/seaborn_for_publications.md +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/scripts/figure_export.py +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/scripts/style_presets.py +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/tests/test_figure_export.py +0 -0
- /package/.grok/{skills/lit-scientific-visualization → vendor/045_scientific-visualization}/tests/test_style_presets.py +0 -0
|
@@ -32,7 +32,7 @@ artifact_type: grok_build_skill
|
|
|
32
32
|
surface: .grok/skills/lit-scientific-visualization/SKILL.md
|
|
33
33
|
invocation: user-invocable skill or an explicit scientific-visualization request
|
|
34
34
|
activation_banner: "🔥 LITBURN IGNITED · lit-scientific-visualization 🔥"
|
|
35
|
-
resource_root:
|
|
35
|
+
resource_root: ../../vendor/045_scientific-visualization
|
|
36
36
|
verdicts: [PASS, DEGRADED, FAIL, BLOCKED]
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -62,20 +62,20 @@ Resolve all resource paths relative to this entrypoint's directory. The followin
|
|
|
62
62
|
shipped resources; load only what the request needs, but run the verifier before relying on any
|
|
63
63
|
helper or style:
|
|
64
64
|
|
|
65
|
-
- Load
|
|
66
|
-
- Load
|
|
67
|
-
- Load
|
|
68
|
-
- Load
|
|
69
|
-
- Load
|
|
70
|
-
- Load
|
|
71
|
-
- [style presets](scripts/style_presets.py), [figure export helpers](scripts/figure_export.py),
|
|
65
|
+
- Load `../../vendor/045_scientific-visualization/references/matplotlib_examples.md` when a worked plotting pattern is needed.
|
|
66
|
+
- Load `../../vendor/045_scientific-visualization/references/publication_guidelines.md` when choosing publication geometry or export rules.
|
|
67
|
+
- Load `../../vendor/045_scientific-visualization/references/journal_requirements.md` when a venue-specific requirement is in scope.
|
|
68
|
+
- Load `../../vendor/045_scientific-visualization/references/color_palettes.md` when choosing categorical or accessible colors.
|
|
69
|
+
- Load `../../vendor/045_scientific-visualization/references/seaborn_for_publications.md` when a seaborn workflow is requested.
|
|
70
|
+
- Load `../../vendor/045_scientific-visualization/references/mdanalysis_martini_visualization.md` when a molecular trajectory is in scope.
|
|
71
|
+
- [style presets](../../vendor/045_scientific-visualization/scripts/style_presets.py), [figure export helpers](../../vendor/045_scientific-visualization/scripts/figure_export.py),
|
|
72
72
|
and the [canonical corpus verifier](scripts/verify-canonical-corpus.mjs).
|
|
73
|
-
- [color palettes](assets/color_palettes.py), [publication style](assets/publication.mplstyle),
|
|
74
|
-
[Nature style](assets/nature.mplstyle), and [presentation style](assets/presentation.mplstyle).
|
|
75
|
-
- [evaluation cases](evals/evals.json) describe the behavioral checks this resource set is meant
|
|
76
|
-
to support. The [source manifest](references/_canonical-corpus/manifest.json) pins the 15
|
|
77
|
-
companion files and their digests. The Python [helper tests](tests/test_style_presets.py) and
|
|
78
|
-
[export tests](tests/test_figure_export.py) are regression fixtures, not user data.
|
|
73
|
+
- [color palettes](../../vendor/045_scientific-visualization/assets/color_palettes.py), [publication style](../../vendor/045_scientific-visualization/assets/publication.mplstyle),
|
|
74
|
+
[Nature style](../../vendor/045_scientific-visualization/assets/nature.mplstyle), and [presentation style](../../vendor/045_scientific-visualization/assets/presentation.mplstyle).
|
|
75
|
+
- [evaluation cases](../../vendor/045_scientific-visualization/evals/evals.json) describe the behavioral checks this resource set is meant
|
|
76
|
+
to support. The [source manifest](../../vendor/045_scientific-visualization/references/_canonical-corpus/manifest.json) pins the 15
|
|
77
|
+
companion files and their digests. The Python [helper tests](../../vendor/045_scientific-visualization/tests/test_style_presets.py) and
|
|
78
|
+
[export tests](../../vendor/045_scientific-visualization/tests/test_figure_export.py) are regression fixtures, not user data.
|
|
79
79
|
|
|
80
80
|
Run `node scripts/verify-canonical-corpus.mjs` from this skill directory before relying on any
|
|
81
81
|
helper. A nonzero result is a `BLOCKED` source-integrity condition: do not run a modified helper
|
|
@@ -143,11 +143,11 @@ from pathlib import Path
|
|
|
143
143
|
import sys
|
|
144
144
|
|
|
145
145
|
PROJECT_ROOT = Path.cwd().resolve()
|
|
146
|
-
|
|
147
|
-
if not (
|
|
148
|
-
raise FileNotFoundError(f"package-owned visualization
|
|
149
|
-
SCRIPTS =
|
|
150
|
-
ASSETS =
|
|
146
|
+
CORPUS_ROOT = PROJECT_ROOT / ".grok" / "vendor" / "045_scientific-visualization"
|
|
147
|
+
if not (CORPUS_ROOT / "references" / "_canonical-corpus" / "manifest.json").is_file():
|
|
148
|
+
raise FileNotFoundError(f"package-owned visualization corpus is missing: {CORPUS_ROOT}")
|
|
149
|
+
SCRIPTS = CORPUS_ROOT / "scripts"
|
|
150
|
+
ASSETS = CORPUS_ROOT / "assets"
|
|
151
151
|
sys.path.insert(0, str(SCRIPTS))
|
|
152
152
|
sys.path.insert(0, str(ASSETS))
|
|
153
153
|
|
|
@@ -5,10 +5,18 @@ import { existsSync, lstatSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
5
5
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
|
|
8
|
-
const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '
|
|
8
|
+
const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../../vendor/045_scientific-visualization');
|
|
9
9
|
const MANIFEST_RELATIVE = 'references/_canonical-corpus/manifest.json';
|
|
10
10
|
const EXPECTED = Object.freeze({
|
|
11
11
|
schemaVersion: 'litgrok.scientific-visualization-corpus/v1',
|
|
12
|
+
license: 'MIT',
|
|
13
|
+
author: 'Woojin',
|
|
14
|
+
upstreamCommit: '235ed3af614a7becaee6ef1d1a18e5c4b13994f4',
|
|
15
|
+
omittedFile: Object.freeze({
|
|
16
|
+
path: 'SKILL.md',
|
|
17
|
+
sha256: 'd6084a7e3adf283157820ea20dbe1b46fa22fa1be17b138ab1203be550f4ef68',
|
|
18
|
+
reason: 'deliberately replaced by the Grok-native adapter',
|
|
19
|
+
}),
|
|
12
20
|
fileCount: 15,
|
|
13
21
|
byteCount: 114577,
|
|
14
22
|
aggregateSha256: 'bf4719645e2c784f788993bb1d9f17d99dfb26301b1df474b1d26e9fcb9b4962',
|
|
@@ -58,7 +66,7 @@ function listFiles(directory, prefix = '') {
|
|
|
58
66
|
function parseRoot(argv) {
|
|
59
67
|
if (argv.length === 0) return SKILL_ROOT;
|
|
60
68
|
if (argv.length === 2 && argv[0] === '--root' && argv[1] && !argv[1].startsWith('-')) return resolve(argv[1]);
|
|
61
|
-
throw new Error('usage: verify-canonical-corpus.mjs [--root <
|
|
69
|
+
throw new Error('usage: verify-canonical-corpus.mjs [--root <corpus-directory>]');
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
function verify(skillRoot) {
|
|
@@ -73,11 +81,26 @@ function verify(skillRoot) {
|
|
|
73
81
|
return [`manifest unreadable: ${error instanceof Error ? error.message : String(error)}`];
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
const expectedManifestKeys = [
|
|
84
|
+
const expectedManifestKeys = [
|
|
85
|
+
'aggregate_sha256',
|
|
86
|
+
'author',
|
|
87
|
+
'byte_count',
|
|
88
|
+
'file_count',
|
|
89
|
+
'files',
|
|
90
|
+
'license',
|
|
91
|
+
'omitted_files',
|
|
92
|
+
'schema_version',
|
|
93
|
+
'source',
|
|
94
|
+
'upstream_commit',
|
|
95
|
+
];
|
|
77
96
|
if (JSON.stringify(Object.keys(manifest).sort()) !== JSON.stringify(expectedManifestKeys.sort())) {
|
|
78
97
|
failures.push('manifest keys mismatch');
|
|
79
98
|
}
|
|
80
99
|
if (manifest.schema_version !== EXPECTED.schemaVersion) failures.push('schema_version mismatch');
|
|
100
|
+
if (manifest.license !== EXPECTED.license) failures.push('license mismatch');
|
|
101
|
+
if (manifest.author !== EXPECTED.author) failures.push('author mismatch');
|
|
102
|
+
if (manifest.upstream_commit !== EXPECTED.upstreamCommit) failures.push('upstream_commit mismatch');
|
|
103
|
+
if (JSON.stringify(manifest.omitted_files) !== JSON.stringify([EXPECTED.omittedFile])) failures.push('omitted_files mismatch');
|
|
81
104
|
if (manifest.file_count !== EXPECTED.fileCount) failures.push(`file_count expected ${EXPECTED.fileCount}, got ${manifest.file_count}`);
|
|
82
105
|
if (manifest.byte_count !== EXPECTED.byteCount) failures.push(`byte_count expected ${EXPECTED.byteCount}, got ${manifest.byte_count}`);
|
|
83
106
|
if (manifest.aggregate_sha256 !== EXPECTED.aggregateSha256) failures.push('aggregate_sha256 mismatch');
|
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
"reviewed_on": "2026-08-30",
|
|
6
6
|
"selection": "15 companion files retained; the skill entrypoint is a Grok-native adapter"
|
|
7
7
|
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "Woojin",
|
|
10
|
+
"upstream_commit": "235ed3af614a7becaee6ef1d1a18e5c4b13994f4",
|
|
11
|
+
"omitted_files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "SKILL.md",
|
|
14
|
+
"sha256": "d6084a7e3adf283157820ea20dbe1b46fa22fa1be17b138ab1203be550f4ef68",
|
|
15
|
+
"reason": "deliberately replaced by the Grok-native adapter"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
8
18
|
"file_count": 15,
|
|
9
19
|
"byte_count": 114577,
|
|
10
20
|
"aggregate_sha256": "bf4719645e2c784f788993bb1d9f17d99dfb26301b1df474b1d26e9fcb9b4962",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Scientific visualization notice
|
|
2
|
+
|
|
3
|
+
The LitGrok `lit-scientific-visualization` skill includes 15 retained companion files from the
|
|
4
|
+
owner-authorized 16-file canonical source snapshot. They are distributed under the adjacent MIT
|
|
5
|
+
license with explicit authorization from the owner.
|
|
6
|
+
|
|
7
|
+
The upstream `SKILL.md` has SHA-256
|
|
8
|
+
`d6084a7e3adf283157820ea20dbe1b46fa22fa1be17b138ab1203be550f4ef68` and is deliberately omitted,
|
|
9
|
+
replaced by the Grok-native adapter at `.grok/skills/lit-scientific-visualization/SKILL.md`. The
|
|
10
|
+
retained-file hashes and this omission are recorded in
|
|
11
|
+
`045_scientific-visualization/references/_canonical-corpus/manifest.json` and
|
|
12
|
+
`provenance/045_scientific-visualization.md`.
|
|
13
|
+
|
|
14
|
+
The corpus contains scientific guidance, examples, and references to external tools and publishers.
|
|
15
|
+
Those names do not imply endorsement; external software, websites, datasets, and author instructions
|
|
16
|
+
remain subject to their own terms and should be checked at the time of use.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Woojin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Lit scientific visualization provenance
|
|
2
|
+
|
|
3
|
+
- Source repository: `~/skills` (local canonical source; path is provenance only, never a runtime dependency)
|
|
4
|
+
- Packaged corpus root: `.grok/vendor/045_scientific-visualization/`
|
|
5
|
+
- Source commit: `235ed3af614a7becaee6ef1d1a18e5c4b13994f4`
|
|
6
|
+
- Distribution authorization: public MIT inclusion authorized by the owner for LitFamily packaging.
|
|
7
|
+
- License: MIT, Copyright (c) 2026 Woojin
|
|
8
|
+
- Canonical file count: 16 Git-tracked authored files.
|
|
9
|
+
- Retained file count: 15 companion files.
|
|
10
|
+
- Omitted file: upstream `SKILL.md`, SHA-256 `d6084a7e3adf283157820ea20dbe1b46fa22fa1be17b138ab1203be550f4ef68`, deliberately replaced by the Grok-native adapter at `.grok/skills/lit-scientific-visualization/SKILL.md`.
|
|
11
|
+
- Approved retained-byte manifest SHA-256: `bf4719645e2c784f788993bb1d9f17d99dfb26301b1df474b1d26e9fcb9b4962`.
|
|
12
|
+
|
|
13
|
+
The retained-file manifest is SHA-256 over records of the form
|
|
14
|
+
`<sha256(raw bytes)><two spaces><repo-relative path><LF>`. Modes, sizes, xattrs,
|
|
15
|
+
and generated caches are excluded.
|
|
16
|
+
|
|
17
|
+
| Relative path | SHA-256 |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `assets/color_palettes.py` | `ffea28da930406ecb11bbeaebfc530dfac40b772827a7653f449cb3b0bb35309` |
|
|
20
|
+
| `assets/nature.mplstyle` | `6a7343788bf772b7e1bc813d094f7bafa97c1e5544586e7b76002ad8547229b6` |
|
|
21
|
+
| `assets/presentation.mplstyle` | `e3ee23f0470d7fb07a0be75cd1210e231becfc2f5267aa404e4186aa077a3339` |
|
|
22
|
+
| `assets/publication.mplstyle` | `18447af3bc47310d23fc27255413c23d8bbe3ff441463cc54fcecdfacd205bea` |
|
|
23
|
+
| `evals/evals.json` | `366dc61b6e042f08f28bf33f2534feea80219d771b84497ec7094b30263e935b` |
|
|
24
|
+
| `references/color_palettes.md` | `0298691c8de8379570488a7b7768663971bc20af1fb05d464c5438d43a21dcfa` |
|
|
25
|
+
| `references/journal_requirements.md` | `56fdde590a9d778547dbcb609b77d86f1f31865e803bcecca5d8c4c72b91b3c7` |
|
|
26
|
+
| `references/matplotlib_examples.md` | `c99cd4f83e2452773e9580e2fa0984e61433c7a9b57ca0d2562dc400dfe4f83d` |
|
|
27
|
+
| `references/mdanalysis_martini_visualization.md` | `abcb3c61f1c3984ba9014d9ae197b726d23c1df844dc90988ecc4d8f0e349bfe` |
|
|
28
|
+
| `references/publication_guidelines.md` | `d9f5d0f115872c4c190a11d83432d44635e38ef9f1740db471fcc70f4c91dd2c` |
|
|
29
|
+
| `references/seaborn_for_publications.md` | `2da2147ae8974b4b5d16096c1484b982d5d1e5f91113808ebfd12111a0a6597a` |
|
|
30
|
+
| `scripts/figure_export.py` | `b22c7708afaf2a1cfa4f821eb9230d4262f1d52948af7f0815855aa9d0960403` |
|
|
31
|
+
| `scripts/style_presets.py` | `e9d450bd4ab6b11303b02d5029177c8d49466cc597648d12de0ecdb7620f64c4` |
|
|
32
|
+
| `tests/test_figure_export.py` | `b18414369e6721ad93d417914114d71af006248675eb20bb1f4989c48ec9a58e` |
|
|
33
|
+
| `tests/test_style_presets.py` | `ff0e190196480848f1fea2398220038771f386ee7967a0ef122b0dfbca3aed46` |
|
|
34
|
+
|
|
35
|
+
The Grok-native adapter is outside the retained companion-file table. The omitted upstream
|
|
36
|
+
entrypoint remains identified in the manifest so the 15 retained files and the 16-file canonical
|
|
37
|
+
source are both auditable.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.8 (pending) — 2026-09-02
|
|
4
|
+
|
|
5
|
+
- Move the scientific-visualization corpus to `.grok/vendor/` so the skill
|
|
6
|
+
adapter stays a wrapper over the original files.
|
|
7
|
+
- Count vendor license, provenance, and NOTICE companions in family
|
|
8
|
+
payload-parity so packed skills keep the material they name.
|
|
9
|
+
|
|
10
|
+
## 0.2.7 (pending) — 2026-09-02
|
|
11
|
+
|
|
12
|
+
- Repair payload upgrades across the ownership-manifest transition: installs now
|
|
13
|
+
record a schema-checked `.grok/.litgrok-install-manifest.json` of SHA-256
|
|
14
|
+
payload hashes, classify files matching recorded hashes as installer-owned,
|
|
15
|
+
and atomically replace owned files during upgrades. Uninstall removes owned
|
|
16
|
+
files and the manifest only after payload removal succeeds.
|
|
17
|
+
- Migrate a pristine manifest-free 0.2.5 payload through the bundled seven-file
|
|
18
|
+
legacy hash snapshot, while malformed manifests and modified or unrecognized
|
|
19
|
+
files still fail closed before payload writes.
|
|
20
|
+
- Add regression coverage for historical migration, legacy user edits,
|
|
21
|
+
malformed ownership metadata, and manifest cleanup; enable full-history CI
|
|
22
|
+
checkout for the historical archive fixture and document the ownership
|
|
23
|
+
behavior in both READMEs.
|
|
24
|
+
|
|
3
25
|
## 0.2.6 (pending) — 2026-09-02
|
|
4
26
|
|
|
5
27
|
- Harden inode-reuse handling across skill-loop state locks and the
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</p>
|
|
23
23
|
<p align="center">
|
|
24
24
|
<img src="https://img.shields.io/badge/npm-litgrok--ai-cb3837" alt="npm: litgrok-ai" />
|
|
25
|
-
<img src="https://img.shields.io/badge/version-0.2.
|
|
25
|
+
<img src="https://img.shields.io/badge/version-0.2.8-2ea44f" alt="version 0.2.8" />
|
|
26
26
|
<img src="https://img.shields.io/badge/Grok%20Build-skills%20%2B%20rules-111827" alt="Grok Build skills and project rules" />
|
|
27
27
|
<img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license" />
|
|
28
28
|
</p>
|
|
@@ -105,7 +105,7 @@ npx --yes litgrok-ai@latest install
|
|
|
105
105
|
Pin a version when you need a reproducible install:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
npx --yes litgrok-ai@0.2.
|
|
108
|
+
npx --yes litgrok-ai@0.2.8 install
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
Preview without writing files:
|
|
@@ -120,7 +120,7 @@ Default install writes the whole payload into the current project's `.grok/` tre
|
|
|
120
120
|
npx --yes litgrok-ai@latest install --user
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
`--user` targets `~/.grok/skills`, `~/.grok/rules`, and `~/.grok/hooks`. Dry-run, `NO_COLOR`, and non-interactive runs do not mutate files.
|
|
123
|
+
`--user` targets `~/.grok/skills`, `~/.grok/rules`, and `~/.grok/hooks`. Dry-run, `NO_COLOR`, and non-interactive runs do not mutate files. The installer records SHA-256 ownership in `.grok/.litgrok-install-manifest.json`: byte-identical files stay untouched, and files matching a previously recorded payload hash may be replaced during an upgrade. A user-modified or otherwise unrecognized file refuses the whole install rather than being overwritten. A pristine pre-manifest 0.2.5 payload is recognized through the bundled migration snapshot while no manifest exists and receives a new manifest after the upgrade.
|
|
124
124
|
|
|
125
125
|
### Install output
|
|
126
126
|
|
|
@@ -146,7 +146,7 @@ npx --yes litgrok-ai@latest uninstall
|
|
|
146
146
|
npx --yes litgrok-ai@latest uninstall --user
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
If any installed payload file was changed, uninstall refuses the whole removal. Reinstall by running the matching `install` command again; byte-identical files are left untouched.
|
|
149
|
+
If any installed payload file was changed, uninstall refuses the whole removal. Files recorded as installer-owned are removed too, and the ownership manifest is removed only after the payload removal succeeds. Reinstall by running the matching `install` command again; byte-identical files are left untouched.
|
|
150
150
|
|
|
151
151
|
## First use
|
|
152
152
|
|
|
@@ -198,12 +198,12 @@ Review is proposal-only. `autoApply` is always false, transcript text is inert d
|
|
|
198
198
|
From an installed project, use the package CLI directly when you want a replayable foreground command:
|
|
199
199
|
|
|
200
200
|
```bash
|
|
201
|
-
npx --yes litgrok-ai@0.2.
|
|
202
|
-
npx --yes litgrok-ai@0.2.
|
|
203
|
-
npx --yes litgrok-ai@0.2.
|
|
204
|
-
npx --yes litgrok-ai@0.2.
|
|
205
|
-
npx --yes litgrok-ai@0.2.
|
|
206
|
-
npx --yes litgrok-ai@0.2.
|
|
201
|
+
npx --yes litgrok-ai@0.2.8 skill-loop review <session-id>
|
|
202
|
+
npx --yes litgrok-ai@0.2.8 skill-loop list
|
|
203
|
+
npx --yes litgrok-ai@0.2.8 skill-loop apply <proposal-id>
|
|
204
|
+
npx --yes litgrok-ai@0.2.8 skill-loop reject <proposal-id>
|
|
205
|
+
npx --yes litgrok-ai@0.2.8 skill-loop rollback <ledger-id>
|
|
206
|
+
npx --yes litgrok-ai@0.2.8 skill-loop curator
|
|
207
207
|
```
|
|
208
208
|
|
|
209
209
|
`apply` prints the ledger id needed for rollback. The deterministic curator is also user-invoked: by default it runs at most once every seven days, requires at least two idle hours, marks eligible unpinned agent-owned skills stale after 30 idle days, and archives them outside discovery after 90 idle days. It makes a full backup before a transition and never deletes a skill package; `--force` bypasses only the seven-day run interval for an explicit test or maintenance invocation.
|
package/README_ko-KR.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</p>
|
|
23
23
|
<p align="center">
|
|
24
24
|
<img src="https://img.shields.io/badge/npm-litgrok--ai-cb3837" alt="npm: litgrok-ai" />
|
|
25
|
-
<img src="https://img.shields.io/badge/version-0.2.
|
|
25
|
+
<img src="https://img.shields.io/badge/version-0.2.8-2ea44f" alt="version 0.2.8" />
|
|
26
26
|
<img src="https://img.shields.io/badge/Grok%20Build-skills%20%2B%20rules-111827" alt="Grok Build skills and project rules" />
|
|
27
27
|
<img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license" />
|
|
28
28
|
</p>
|
|
@@ -105,7 +105,7 @@ npx --yes litgrok-ai@latest install
|
|
|
105
105
|
재현 가능한 설치는 버전을 고정합니다.
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
npx --yes litgrok-ai@0.2.
|
|
108
|
+
npx --yes litgrok-ai@0.2.8 install
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
파일을 쓰지 않고 미리보기:
|
|
@@ -120,7 +120,7 @@ npx --yes litgrok-ai@latest install --dry-run
|
|
|
120
120
|
npx --yes litgrok-ai@latest install --user
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
`--user`는 `~/.grok/skills`, `~/.grok/rules`, `~/.grok/hooks`를 대상으로 합니다. dry-run, `NO_COLOR`, non-interactive는 파일을 바꾸지 않습니다. byte가 같은 payload
|
|
123
|
+
`--user`는 `~/.grok/skills`, `~/.grok/rules`, `~/.grok/hooks`를 대상으로 합니다. dry-run, `NO_COLOR`, non-interactive는 파일을 바꾸지 않습니다. installer는 `.grok/.litgrok-install-manifest.json`에 SHA-256 소유권을 기록합니다. byte가 같은 파일은 다시 쓰지 않고, 이전 payload hash와 일치하는 installer 소유 파일은 업그레이드에서 교체할 수 있습니다. 사용자가 바꿨거나 소유권을 확인할 수 없는 파일이 있으면 전체 설치를 거절하고 덮어쓰지 않습니다. manifest가 없을 때 0.2.5의 pristine payload는 내장 migration snapshot으로 인식하며 업그레이드 뒤 새 manifest를 기록합니다.
|
|
124
124
|
|
|
125
125
|
### 설치 출력
|
|
126
126
|
|
|
@@ -146,7 +146,7 @@ npx --yes litgrok-ai@latest uninstall
|
|
|
146
146
|
npx --yes litgrok-ai@latest uninstall --user
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
설치된 payload 파일 하나라도 바뀌었다면 uninstall은 전체 삭제를 거절합니다. 재설치는 같은 `install` 명령을 다시 실행합니다. byte가 같은 파일은 다시 쓰지 않습니다.
|
|
149
|
+
설치된 payload 파일 하나라도 바뀌었다면 uninstall은 전체 삭제를 거절합니다. installer 소유로 기록된 파일도 삭제하며, payload 삭제가 성공한 뒤에만 ownership manifest를 삭제합니다. 재설치는 같은 `install` 명령을 다시 실행합니다. byte가 같은 파일은 다시 쓰지 않습니다.
|
|
150
150
|
|
|
151
151
|
## 처음 사용하기
|
|
152
152
|
|
|
@@ -198,12 +198,12 @@ Review는 proposal만 만듭니다. `autoApply`는 항상 false이고 transcript
|
|
|
198
198
|
설치된 project에서는 다음 package CLI를 foreground command로 실행할 수 있습니다.
|
|
199
199
|
|
|
200
200
|
```bash
|
|
201
|
-
npx --yes litgrok-ai@0.2.
|
|
202
|
-
npx --yes litgrok-ai@0.2.
|
|
203
|
-
npx --yes litgrok-ai@0.2.
|
|
204
|
-
npx --yes litgrok-ai@0.2.
|
|
205
|
-
npx --yes litgrok-ai@0.2.
|
|
206
|
-
npx --yes litgrok-ai@0.2.
|
|
201
|
+
npx --yes litgrok-ai@0.2.8 skill-loop review <session-id>
|
|
202
|
+
npx --yes litgrok-ai@0.2.8 skill-loop list
|
|
203
|
+
npx --yes litgrok-ai@0.2.8 skill-loop apply <proposal-id>
|
|
204
|
+
npx --yes litgrok-ai@0.2.8 skill-loop reject <proposal-id>
|
|
205
|
+
npx --yes litgrok-ai@0.2.8 skill-loop rollback <ledger-id>
|
|
206
|
+
npx --yes litgrok-ai@0.2.8 skill-loop curator
|
|
207
207
|
```
|
|
208
208
|
|
|
209
209
|
`apply`는 rollback에 필요한 ledger id를 출력합니다. Deterministic curator도 user-invoked입니다. 기본적으로 7일에 한 번만 실행되고, 마지막 activity 뒤 최소 2시간이 지나야 하며, pin되지 않은 agent-owned skill을 30일 idle 뒤 stale로 표시하고 90일 idle 뒤 discovery 밖으로 archive합니다. Transition 전에는 전체 backup을 만들고 skill package를 삭제하지 않습니다. `--force`는 명시적인 test 또는 maintenance 실행에서 7일 interval만 건너뜁니다.
|
package/bin/litgrok.mjs
CHANGED
|
@@ -1,12 +1,50 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
import { constants, copyFileSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
5
|
import { homedir } from 'node:os';
|
|
5
|
-
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
6
|
+
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
|
|
8
9
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
10
|
const PAYLOAD_ROOT = join(PACKAGE_ROOT, '.grok');
|
|
11
|
+
const INSTALL_MANIFEST_NAME = '.litgrok-install-manifest.json';
|
|
12
|
+
const INSTALL_MANIFEST_SCHEMA = 'litgrok.install-manifest/v1';
|
|
13
|
+
const PACKAGE_NAME = 'litgrok-ai';
|
|
14
|
+
|
|
15
|
+
// 0.2.5 was the last release before ownership manifests existed. These are the
|
|
16
|
+
// payload hashes changed since that release, so a pristine 0.2.5 install can
|
|
17
|
+
// migrate while no manifest exists without treating package-owned files as conflicts.
|
|
18
|
+
const PRE_MANIFEST_PAYLOAD_HASHES = Object.freeze({
|
|
19
|
+
'skills/autoconference/references/_canonical-corpus/manifest.json': '4554ee4041b73577a844abc27a97db91e941b8412f1888209203251dc1fee500',
|
|
20
|
+
'skills/autoconference/scripts/init_conference.py': 'cb83623c1338b7e3a5be7a7beac7a17b8ae362fc24fbc01d1caff634db290dfc',
|
|
21
|
+
'skills/autoconference/scripts/verify-canonical-corpus.mjs': '4a4afb7dc0b58519a4c521c987cb8998495afc09b89fb87cafb0f1db0047c28d',
|
|
22
|
+
'skills/autoresearch/references/_canonical-corpus/manifest.json': '9667f852081d4877c579d14e63c0759ebbe863e4170a9c3d5e6487cdb1f0c323',
|
|
23
|
+
'skills/autoresearch/scripts/init_research.py': 'a5434711928eb4d0487d933df5e1b374f8553140004c6ab8e38fe577ae074d05',
|
|
24
|
+
'skills/autoresearch/scripts/verify-canonical-corpus.mjs': '80a85ed4ba0c168bd7f7abef5754c4089a629975a827b6ddb3f01cd813602274',
|
|
25
|
+
'skills/lit-scientific-visualization/SKILL.md': 'ffc3b2ea49dd7e7c4c7d4568f289df294a3a30f953c11039f98aaf6f8fb983ee',
|
|
26
|
+
'skills/lit-scientific-visualization/references/_canonical-corpus/manifest.json': '9cafbdc5e1259aea053a5bf637e18d034f8e169be74b2c5732fc5bfd30f7c5bd',
|
|
27
|
+
'skills/lit-scientific-visualization/scripts/verify-canonical-corpus.mjs': 'a1cfe0578195098dc4afc2d25f0d391f38aeaa57cd3970fa7dca51e20cbf929d',
|
|
28
|
+
'skills/skill-observer/scripts/skill-loop.mjs': '3f3f5265928c0d71175b0f23f71b836d1dc05e7efba407bfdc2d263c6aaed0ce',
|
|
29
|
+
});
|
|
30
|
+
const PRE_MANIFEST_LEGACY_PAYLOAD_HASHES = Object.freeze({
|
|
31
|
+
'skills/lit-scientific-visualization/assets/color_palettes.py': 'ffea28da930406ecb11bbeaebfc530dfac40b772827a7653f449cb3b0bb35309',
|
|
32
|
+
'skills/lit-scientific-visualization/assets/nature.mplstyle': '6a7343788bf772b7e1bc813d094f7bafa97c1e5544586e7b76002ad8547229b6',
|
|
33
|
+
'skills/lit-scientific-visualization/assets/presentation.mplstyle': 'e3ee23f0470d7fb07a0be75cd1210e231becfc2f5267aa404e4186aa077a3339',
|
|
34
|
+
'skills/lit-scientific-visualization/assets/publication.mplstyle': '18447af3bc47310d23fc27255413c23d8bbe3ff441463cc54fcecdfacd205bea',
|
|
35
|
+
'skills/lit-scientific-visualization/evals/evals.json': '366dc61b6e042f08f28bf33f2534feea80219d771b84497ec7094b30263e935b',
|
|
36
|
+
'skills/lit-scientific-visualization/references/_canonical-corpus/manifest.json': '9cafbdc5e1259aea053a5bf637e18d034f8e169be74b2c5732fc5bfd30f7c5bd',
|
|
37
|
+
'skills/lit-scientific-visualization/references/color_palettes.md': '0298691c8de8379570488a7b7768663971bc20af1fb05d464c5438d43a21dcfa',
|
|
38
|
+
'skills/lit-scientific-visualization/references/journal_requirements.md': '56fdde590a9d778547dbcb609b77d86f1f31865e803bcecca5d8c4c72b91b3c7',
|
|
39
|
+
'skills/lit-scientific-visualization/references/matplotlib_examples.md': 'c99cd4f83e2452773e9580e2fa0984e61433c7a9b57ca0d2562dc400dfe4f83d',
|
|
40
|
+
'skills/lit-scientific-visualization/references/mdanalysis_martini_visualization.md': 'abcb3c61f1c3984ba9014d9ae197b726d23c1df844dc90988ecc4d8f0e349bfe',
|
|
41
|
+
'skills/lit-scientific-visualization/references/publication_guidelines.md': 'd9f5d0f115872c4c190a11d83432d44635e38ef9f1740db471fcc70f4c91dd2c',
|
|
42
|
+
'skills/lit-scientific-visualization/references/seaborn_for_publications.md': '2da2147ae8974b4b5d16096c1484b982d5d1e5f91113808ebfd12111a0a6597a',
|
|
43
|
+
'skills/lit-scientific-visualization/scripts/figure_export.py': 'b22c7708afaf2a1cfa4f821eb9230d4262f1d52948af7f0815855aa9d0960403',
|
|
44
|
+
'skills/lit-scientific-visualization/scripts/style_presets.py': 'e9d450bd4ab6b11303b02d5029177c8d49466cc597648d12de0ecdb7620f64c4',
|
|
45
|
+
'skills/lit-scientific-visualization/tests/test_figure_export.py': 'b18414369e6721ad93d417914114d71af006248675eb20bb1f4989c48ec9a58e',
|
|
46
|
+
'skills/lit-scientific-visualization/tests/test_style_presets.py': 'ff0e190196480848f1fea2398220038771f386ee7967a0ef122b0dfbca3aed46',
|
|
47
|
+
});
|
|
10
48
|
const USAGE = 'Usage: litgrok [install|uninstall] [--user|--project] [--dry-run] [--no-color] [--yes]\n litgrok skill-loop <review|propose|list|apply|reject|rollback|curator> ...\n';
|
|
11
49
|
const BRAILLE_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
12
50
|
const SPINNER_INTERVAL_MS = 80;
|
|
@@ -235,17 +273,196 @@ function reportFailure(error, { stderr, stdout, options, root, colors, step }) {
|
|
|
235
273
|
stdout.write(failureCard(options, root, colors, error?.step ?? step, errorText(error)));
|
|
236
274
|
}
|
|
237
275
|
|
|
238
|
-
function
|
|
276
|
+
function normalizedRelativePath(relativePath) {
|
|
277
|
+
return relativePath.split(sep).join('/');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function sha256File(filePath) {
|
|
281
|
+
return createHash('sha256').update(readFileSync(filePath)).digest('hex');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function ownershipManifestFailure(reason) {
|
|
285
|
+
const message = `Refusing to use ${INSTALL_MANIFEST_NAME}: ${reason}`;
|
|
286
|
+
const error = new Error(message);
|
|
287
|
+
error.step = 'OWNERSHIP';
|
|
288
|
+
error.stderrLines = [message];
|
|
289
|
+
return error;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function ownershipManifestPath(root) {
|
|
293
|
+
return join(root, INSTALL_MANIFEST_NAME);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function readOwnershipManifest(root) {
|
|
297
|
+
const target = ownershipManifestPath(root);
|
|
298
|
+
let stat;
|
|
299
|
+
try {
|
|
300
|
+
stat = lstatSync(target);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (error?.code === 'ENOENT') return null;
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
if (stat.isSymbolicLink() || !stat.isFile()) throw ownershipManifestFailure('path is not a regular file');
|
|
306
|
+
|
|
307
|
+
let parsed;
|
|
308
|
+
try {
|
|
309
|
+
parsed = JSON.parse(readFileSync(target, 'utf8'));
|
|
310
|
+
} catch {
|
|
311
|
+
throw ownershipManifestFailure('file is not valid JSON');
|
|
312
|
+
}
|
|
313
|
+
if (
|
|
314
|
+
!parsed ||
|
|
315
|
+
parsed.schema !== INSTALL_MANIFEST_SCHEMA ||
|
|
316
|
+
parsed.package !== PACKAGE_NAME ||
|
|
317
|
+
typeof parsed.version !== 'string' ||
|
|
318
|
+
parsed.version.length === 0 ||
|
|
319
|
+
!parsed.files ||
|
|
320
|
+
Array.isArray(parsed.files) ||
|
|
321
|
+
typeof parsed.files !== 'object'
|
|
322
|
+
) {
|
|
323
|
+
throw ownershipManifestFailure('schema is invalid');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const hashes = new Map();
|
|
327
|
+
for (const [relativePath, hash] of Object.entries(parsed.files)) {
|
|
328
|
+
const segments = relativePath.split('/');
|
|
329
|
+
if (
|
|
330
|
+
relativePath.length === 0 ||
|
|
331
|
+
relativePath.startsWith('/') ||
|
|
332
|
+
relativePath.includes('\\') ||
|
|
333
|
+
segments.some((segment) => segment.length === 0 || segment === '.' || segment === '..')
|
|
334
|
+
) {
|
|
335
|
+
throw ownershipManifestFailure(`path is invalid: ${relativePath}`);
|
|
336
|
+
}
|
|
337
|
+
if (typeof hash !== 'string' || !/^[0-9a-f]{64}$/u.test(hash)) {
|
|
338
|
+
throw ownershipManifestFailure(`hash is invalid: ${relativePath}`);
|
|
339
|
+
}
|
|
340
|
+
hashes.set(relativePath, hash);
|
|
341
|
+
}
|
|
342
|
+
return { path: target, hashes };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function targetState(source, target, relativePath, manifest) {
|
|
239
346
|
try {
|
|
240
347
|
const targetStat = lstatSync(target);
|
|
241
348
|
if (!targetStat.isFile()) return 'conflict';
|
|
242
|
-
|
|
349
|
+
const targetHash = sha256File(target);
|
|
350
|
+
if (targetHash === sha256File(source)) return 'current';
|
|
351
|
+
const previousHash = manifest?.hashes.get(normalizedRelativePath(relativePath)) ??
|
|
352
|
+
(manifest === null ? PRE_MANIFEST_PAYLOAD_HASHES[normalizedRelativePath(relativePath)] : undefined);
|
|
353
|
+
return targetHash === previousHash ? 'owned' : 'conflict';
|
|
243
354
|
} catch (error) {
|
|
244
355
|
if (error?.code === 'ENOENT') return 'missing';
|
|
245
356
|
throw error;
|
|
246
357
|
}
|
|
247
358
|
}
|
|
248
359
|
|
|
360
|
+
function legacyTargetState(root, relativePath, expectedHash) {
|
|
361
|
+
const target = join(root, relativePath);
|
|
362
|
+
let ancestor = dirname(target);
|
|
363
|
+
while (ancestor !== root) {
|
|
364
|
+
try {
|
|
365
|
+
const ancestorStat = lstatSync(ancestor);
|
|
366
|
+
if (ancestorStat.isSymbolicLink() || !ancestorStat.isDirectory()) return 'conflict';
|
|
367
|
+
} catch (error) {
|
|
368
|
+
if (error?.code === 'ENOENT') break;
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
ancestor = dirname(ancestor);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const targetStat = lstatSync(target);
|
|
376
|
+
if (!targetStat.isFile()) return 'conflict';
|
|
377
|
+
return sha256File(target) === expectedHash ? 'owned' : 'conflict';
|
|
378
|
+
} catch (error) {
|
|
379
|
+
if (error?.code === 'ENOENT') return 'missing';
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function removeLegacyFiles(entries) {
|
|
385
|
+
let removed = 0;
|
|
386
|
+
for (const entry of entries) {
|
|
387
|
+
const state = legacyTargetState(entry.root, entry.relativePath, entry.expectedHash);
|
|
388
|
+
if (state === 'missing') continue;
|
|
389
|
+
if (state !== 'owned') throw new Error(`Refusing to remove different existing file: ${entry.target}`);
|
|
390
|
+
unlinkSync(entry.target);
|
|
391
|
+
removed += 1;
|
|
392
|
+
}
|
|
393
|
+
return removed;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function manifestContent(files) {
|
|
397
|
+
const hashes = Object.fromEntries(
|
|
398
|
+
files
|
|
399
|
+
.map(({ relativePath, source }) => [normalizedRelativePath(relativePath), sha256File(source)])
|
|
400
|
+
.sort(([left], [right]) => left.localeCompare(right)),
|
|
401
|
+
);
|
|
402
|
+
return `${JSON.stringify({
|
|
403
|
+
schema: INSTALL_MANIFEST_SCHEMA,
|
|
404
|
+
package: PACKAGE_NAME,
|
|
405
|
+
version: packageVersion(),
|
|
406
|
+
files: hashes,
|
|
407
|
+
}, null, 2)}\n`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function writeOwnershipManifest(root, files, previousManifest) {
|
|
411
|
+
const target = ownershipManifestPath(root);
|
|
412
|
+
const content = manifestContent(files);
|
|
413
|
+
let existing;
|
|
414
|
+
try {
|
|
415
|
+
const stat = lstatSync(target);
|
|
416
|
+
if (stat.isSymbolicLink() || !stat.isFile()) throw ownershipManifestFailure('path is not a regular file');
|
|
417
|
+
existing = readFileSync(target, 'utf8');
|
|
418
|
+
} catch (error) {
|
|
419
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
420
|
+
}
|
|
421
|
+
if (existing === content) return false;
|
|
422
|
+
if (previousManifest === null && existing !== undefined) {
|
|
423
|
+
throw ownershipManifestFailure('path appeared during installation');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const temporary = join(root, `.${basename(target)}.${randomUUID()}.tmp`);
|
|
427
|
+
try {
|
|
428
|
+
writeFileSync(temporary, content, { flag: 'wx', mode: 0o600 });
|
|
429
|
+
renameSync(temporary, target);
|
|
430
|
+
} finally {
|
|
431
|
+
try {
|
|
432
|
+
unlinkSync(temporary);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function replaceOwnedFile(source, target) {
|
|
441
|
+
const temporary = join(dirname(target), `.${basename(target)}.${randomUUID()}.tmp`);
|
|
442
|
+
try {
|
|
443
|
+
copyFileSync(source, temporary, constants.COPYFILE_EXCL);
|
|
444
|
+
renameSync(temporary, target);
|
|
445
|
+
} finally {
|
|
446
|
+
try {
|
|
447
|
+
unlinkSync(temporary);
|
|
448
|
+
} catch (error) {
|
|
449
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function removeOwnershipManifest(root, previousManifest) {
|
|
455
|
+
if (previousManifest === null) return;
|
|
456
|
+
const target = ownershipManifestPath(root);
|
|
457
|
+
try {
|
|
458
|
+
const stat = lstatSync(target);
|
|
459
|
+
if (stat.isSymbolicLink() || !stat.isFile()) throw ownershipManifestFailure('path is not a regular file');
|
|
460
|
+
unlinkSync(target);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
249
466
|
function discoverPayloadFiles(root = PAYLOAD_ROOT) {
|
|
250
467
|
const files = [];
|
|
251
468
|
|
|
@@ -383,25 +600,50 @@ export async function run(args, context = {}) {
|
|
|
383
600
|
|
|
384
601
|
step = 'OWNERSHIP';
|
|
385
602
|
const states = runStep(progress, 'OWNERSHIP', () => {
|
|
386
|
-
const
|
|
603
|
+
const manifest = readOwnershipManifest(root);
|
|
604
|
+
const owned = files.map(({ relativePath, source, target }) => targetState(source, target, relativePath, manifest));
|
|
605
|
+
const legacyEntries = manifest === null
|
|
606
|
+
? Object.entries(PRE_MANIFEST_LEGACY_PAYLOAD_HASHES).map(([relativePath, expectedHash]) => ({
|
|
607
|
+
root,
|
|
608
|
+
relativePath,
|
|
609
|
+
expectedHash,
|
|
610
|
+
target: join(root, relativePath),
|
|
611
|
+
}))
|
|
612
|
+
: [];
|
|
613
|
+
const legacyStates = legacyEntries.map(({ relativePath, expectedHash }) => legacyTargetState(root, relativePath, expectedHash));
|
|
387
614
|
const counts = {
|
|
388
615
|
missing: owned.filter((state) => state === 'missing').length,
|
|
389
616
|
current: owned.filter((state) => state === 'current').length,
|
|
617
|
+
owned: owned.filter((state) => state === 'owned').length,
|
|
390
618
|
conflicts: owned.filter((state) => state === 'conflict').length,
|
|
391
619
|
};
|
|
392
|
-
|
|
620
|
+
const legacyCounts = {
|
|
621
|
+
missing: legacyStates.filter((state) => state === 'missing').length,
|
|
622
|
+
owned: legacyStates.filter((state) => state === 'owned').length,
|
|
623
|
+
conflicts: legacyStates.filter((state) => state === 'conflict').length,
|
|
624
|
+
};
|
|
625
|
+
if (counts.conflicts > 0 || legacyCounts.conflicts > 0) {
|
|
393
626
|
const stderrLines = [];
|
|
394
627
|
for (let index = 0; index < files.length; index += 1) {
|
|
395
628
|
if (owned[index] === 'conflict') stderrLines.push(`Refusing to ${options.command === 'install' ? 'overwrite' : 'remove'} different existing file: ${files[index].target}`);
|
|
396
629
|
}
|
|
397
|
-
|
|
630
|
+
for (let index = 0; index < legacyEntries.length; index += 1) {
|
|
631
|
+
if (legacyStates[index] === 'conflict') stderrLines.push(`Refusing to ${options.command === 'install' ? 'overwrite' : 'remove'} different existing file: ${legacyEntries[index].target}`);
|
|
632
|
+
}
|
|
633
|
+
const legacyDetail = legacyEntries.length === 0
|
|
634
|
+
? ''
|
|
635
|
+
: ` · ${legacyCounts.missing} legacy missing · ${legacyCounts.owned} legacy owned · ${legacyCounts.conflicts} legacy conflicts`;
|
|
636
|
+
const error = new Error(`${counts.missing} missing · ${counts.current} already-current · ${counts.owned} owned · ${counts.conflicts} conflicts${legacyDetail}`);
|
|
398
637
|
error.step = 'OWNERSHIP';
|
|
399
638
|
error.stderrLines = stderrLines;
|
|
400
639
|
throw error;
|
|
401
640
|
}
|
|
641
|
+
const legacyDetail = legacyEntries.length === 0
|
|
642
|
+
? ''
|
|
643
|
+
: ` · ${legacyCounts.missing} legacy missing · ${legacyCounts.owned} legacy owned · ${legacyCounts.conflicts} legacy conflicts`;
|
|
402
644
|
return {
|
|
403
|
-
value: { states: owned, counts },
|
|
404
|
-
detail: `${counts.missing} missing · ${counts.current} already-current · ${counts.conflicts} conflicts`,
|
|
645
|
+
value: { states: owned, counts, manifest, legacyEntries, legacyStates, legacyCounts },
|
|
646
|
+
detail: `${counts.missing} missing · ${counts.current} already-current · ${counts.owned} owned · ${counts.conflicts} conflicts${legacyDetail}`,
|
|
405
647
|
};
|
|
406
648
|
});
|
|
407
649
|
|
|
@@ -410,19 +652,33 @@ export async function run(args, context = {}) {
|
|
|
410
652
|
const writeStarted = process.hrtime.bigint();
|
|
411
653
|
progress.start('WRITE');
|
|
412
654
|
let removed = 0;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
655
|
+
try {
|
|
656
|
+
for (let index = 0; index < files.length; index += 1) {
|
|
657
|
+
if (!['current', 'owned'].includes(states.states[index])) continue;
|
|
658
|
+
unlinkSync(files[index].target);
|
|
659
|
+
removed += 1;
|
|
660
|
+
}
|
|
661
|
+
removed += removeLegacyFiles(states.legacyEntries);
|
|
662
|
+
removeOwnershipManifest(root, states.manifest);
|
|
663
|
+
progress.finish('ok', 'WRITE', `${removed} removed · ${states.counts.missing} already absent`, elapsedSince(writeStarted));
|
|
664
|
+
} catch (error) {
|
|
665
|
+
progress.finish('failed', 'WRITE', errorText(error), elapsedSince(writeStarted));
|
|
666
|
+
if (error && typeof error === 'object') error.step = 'WRITE';
|
|
667
|
+
throw error;
|
|
417
668
|
}
|
|
418
|
-
progress.finish('ok', 'WRITE', `${removed} removed · ${states.counts.missing} already absent`, elapsedSince(writeStarted));
|
|
419
669
|
const receipt = receiptCard(options, displayRoot, colors, { status: 'Removed', written: 0, current: states.counts.current });
|
|
420
670
|
progress.close();
|
|
421
671
|
stdout.write(receipt);
|
|
422
672
|
return 0;
|
|
423
673
|
}
|
|
424
674
|
|
|
425
|
-
if (states.states.every((state) => state === 'current')) {
|
|
675
|
+
if (states.states.every((state) => state === 'current') && states.legacyStates.every((state) => state === 'missing')) {
|
|
676
|
+
try {
|
|
677
|
+
writeOwnershipManifest(root, files, states.manifest);
|
|
678
|
+
} catch (error) {
|
|
679
|
+
if (error && typeof error === 'object') error.step = 'WRITE';
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
426
682
|
skipStep(progress, 'WRITE', `SKIPPED · 0 written · ${states.counts.current} already-current`);
|
|
427
683
|
const receipt = receiptCard(options, displayRoot, colors, { status: 'Already current', written: 0, current: states.counts.current });
|
|
428
684
|
progress.close();
|
|
@@ -438,10 +694,14 @@ export async function run(args, context = {}) {
|
|
|
438
694
|
if (states.states[index] === 'current') continue;
|
|
439
695
|
const { source, target } = files[index];
|
|
440
696
|
mkdirSync(dirname(target), { recursive: true });
|
|
441
|
-
|
|
697
|
+
if (states.states[index] === 'owned') replaceOwnedFile(source, target);
|
|
698
|
+
else copyFileSync(source, target, constants.COPYFILE_EXCL);
|
|
442
699
|
installed += 1;
|
|
443
700
|
}
|
|
444
|
-
|
|
701
|
+
const removedLegacy = removeLegacyFiles(states.legacyEntries);
|
|
702
|
+
writeOwnershipManifest(root, files, states.manifest);
|
|
703
|
+
const legacyDetail = removedLegacy === 0 ? '' : ` · ${removedLegacy} legacy removed`;
|
|
704
|
+
progress.finish('ok', 'WRITE', `${installed} written · ${states.counts.current} already-current · ${states.counts.owned} owned${legacyDetail}`, elapsedSince(writeStarted));
|
|
445
705
|
} catch (error) {
|
|
446
706
|
progress.finish('failed', 'WRITE', errorText(error), elapsedSince(writeStarted));
|
|
447
707
|
if (error && typeof error === 'object') error.step = 'WRITE';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "litgrok-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Grok Build skills, project rules, and hooks installer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"plugin.json",
|
|
16
16
|
".grok/skills",
|
|
17
|
+
".grok/vendor",
|
|
17
18
|
".grok/agents",
|
|
18
19
|
".grok/rules",
|
|
19
20
|
".grok/hooks",
|
package/plugin.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|