@agentproto/corpus-cli 0.3.0 → 0.4.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/LICENSE +202 -21
- package/README.md +2 -0
- package/dist/cli.mjs +222 -29
- package/dist/cli.mjs.map +1 -1
- package/dist/specs/resources/aip-45/draft/AGENT-CLI.schema.json +5 -0
- package/package.json +5 -5
package/LICENSE
CHANGED
|
@@ -1,21 +1,202 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ pnpm add -g @agentproto/corpus-cli
|
|
|
10
10
|
corpus init marketing ./my-corpus # scaffold a starter workspace
|
|
11
11
|
corpus validate ./my-corpus # validate every AIP file
|
|
12
12
|
corpus lint ./my-corpus # run KNOWLEDGE.md-declared lints
|
|
13
|
+
corpus verify ./my-corpus --facets landscape,daemons # coverage + self-flag scan
|
|
13
14
|
corpus events:emit corpus.entry.promoted --payload '{"slug":"foo"}' ./my-corpus
|
|
14
15
|
corpus events:tail ./my-corpus # tail _log.md
|
|
15
16
|
```
|
|
@@ -21,6 +22,7 @@ Minimum-viable local host:
|
|
|
21
22
|
- `corpus init <vertical> [path]` — seed a starter workspace
|
|
22
23
|
- `corpus validate [path]` — JSON Schema check across every AIP file
|
|
23
24
|
- `corpus lint [path]` — run the lints declared in KNOWLEDGE.md
|
|
25
|
+
- `corpus verify [path] --facets a,b,... [--thin 8] [--apply] [--contaminated]` — SOP ⑤b VERIFY pass: active-entry coverage per facet tag (facets below `--thin` are reported `THIN`) plus a self-flag scan for entries whose own text signals a bad scrape. `--apply` moves flagged entries to `demoted/` (a sibling of `entries/`, non-destructive); `--contaminated` also demotes every entry sharing a source with a flagged entry.
|
|
24
26
|
- `corpus events:emit <kind> --payload <json> [path]` — append an event to `_log.md`
|
|
25
27
|
- `corpus events:tail [path]` — print `_log.md`
|
|
26
28
|
|
package/dist/cli.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import matter2 from 'gray-matter';
|
|
|
12
12
|
import { execFile } from 'child_process';
|
|
13
13
|
import { promisify } from 'util';
|
|
14
14
|
import { parseClaudeJsonOutput, spawnWithStdin } from '@agentproto/cli-exec';
|
|
15
|
-
import { stitchReport, buildPacks, reportConfigSchema } from '@agentproto/corpus/report';
|
|
15
|
+
import { stitchReport, buildPacks, lintReportConfig, reportConfigSchema } from '@agentproto/corpus/report';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @agentproto/corpus-cli v0.1.0-alpha
|
|
@@ -104,9 +104,9 @@ function resolvePackageJson(packageName) {
|
|
|
104
104
|
}
|
|
105
105
|
return null;
|
|
106
106
|
}
|
|
107
|
-
async function readJsonIfExists(
|
|
107
|
+
async function readJsonIfExists(path6) {
|
|
108
108
|
try {
|
|
109
|
-
const raw = await readFile(
|
|
109
|
+
const raw = await readFile(path6, "utf8");
|
|
110
110
|
return JSON.parse(raw);
|
|
111
111
|
} catch (err) {
|
|
112
112
|
if (err.code === "ENOENT") return void 0;
|
|
@@ -115,9 +115,9 @@ async function readJsonIfExists(path5) {
|
|
|
115
115
|
}
|
|
116
116
|
async function readConfiguredPresetPackages() {
|
|
117
117
|
const base = process.env["AGENTPROTO_HOME"] ?? join(homedir(), ".agentproto");
|
|
118
|
-
const
|
|
118
|
+
const path6 = join(base, "config.json");
|
|
119
119
|
try {
|
|
120
|
-
const raw = await readFile(
|
|
120
|
+
const raw = await readFile(path6, "utf8");
|
|
121
121
|
const parsed = JSON.parse(raw);
|
|
122
122
|
const list = parsed.corpusPresetPackages;
|
|
123
123
|
if (Array.isArray(list) && list.length > 0) return list;
|
|
@@ -631,6 +631,160 @@ async function runLint(args) {
|
|
|
631
631
|
);
|
|
632
632
|
return report.errorCount > 0 ? 1 : 0;
|
|
633
633
|
}
|
|
634
|
+
var FLAG_PATTERNS = [
|
|
635
|
+
/does not match/i,
|
|
636
|
+
/content is about/i,
|
|
637
|
+
/no substantive content/i,
|
|
638
|
+
/content mismatch/i,
|
|
639
|
+
/appears to be a (?:newsletter|navigation|login|error) page/i,
|
|
640
|
+
/stated title/i,
|
|
641
|
+
/placeholder page/i,
|
|
642
|
+
/cookie consent/i,
|
|
643
|
+
/page could not be/i
|
|
644
|
+
];
|
|
645
|
+
function parse(args) {
|
|
646
|
+
const out = {
|
|
647
|
+
workspace: void 0,
|
|
648
|
+
facets: [],
|
|
649
|
+
thin: 8,
|
|
650
|
+
apply: false,
|
|
651
|
+
contaminated: false
|
|
652
|
+
};
|
|
653
|
+
for (let i = 0; i < args.length; i++) {
|
|
654
|
+
const a = args[i];
|
|
655
|
+
const next = () => args[++i];
|
|
656
|
+
switch (a) {
|
|
657
|
+
case "--facets": {
|
|
658
|
+
const v = next();
|
|
659
|
+
if (v) out.facets = v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
case "--thin": {
|
|
663
|
+
const v = next();
|
|
664
|
+
if (v) out.thin = Number(v);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
case "--apply":
|
|
668
|
+
out.apply = true;
|
|
669
|
+
break;
|
|
670
|
+
case "--contaminated":
|
|
671
|
+
out.contaminated = true;
|
|
672
|
+
break;
|
|
673
|
+
default:
|
|
674
|
+
if (!a.startsWith("-") && out.workspace === void 0) out.workspace = a;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return out;
|
|
678
|
+
}
|
|
679
|
+
function firstFlag(haystack) {
|
|
680
|
+
for (const re of FLAG_PATTERNS) {
|
|
681
|
+
const m = haystack.match(re);
|
|
682
|
+
if (m) return m[0];
|
|
683
|
+
}
|
|
684
|
+
return void 0;
|
|
685
|
+
}
|
|
686
|
+
function entryStatus(entry) {
|
|
687
|
+
const corpus = entry.frontmatter.metadata?.corpus;
|
|
688
|
+
return corpus?.status ?? "active";
|
|
689
|
+
}
|
|
690
|
+
function tagsOf(entry) {
|
|
691
|
+
const tags = entry.frontmatter.tags;
|
|
692
|
+
return Array.isArray(tags) ? tags.map(String) : [];
|
|
693
|
+
}
|
|
694
|
+
function sourcesOf(entry) {
|
|
695
|
+
const sources = entry.frontmatter.sources;
|
|
696
|
+
return Array.isArray(sources) ? sources.map(String) : [];
|
|
697
|
+
}
|
|
698
|
+
async function runVerify(args) {
|
|
699
|
+
const parsed = parse(args);
|
|
700
|
+
if (parsed.facets.length === 0) {
|
|
701
|
+
return fail(
|
|
702
|
+
"verify requires --facets a,b,c. Usage: corpus verify <workspace> --facets landscape,daemons,... [--thin 8] [--apply] [--contaminated]",
|
|
703
|
+
2
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
const target = resolveWorkspacePath(parsed.workspace);
|
|
707
|
+
const fs = new NodeFsAdapter({ root: target });
|
|
708
|
+
if (!await fs.exists("entries")) {
|
|
709
|
+
return fail(`verify: no entries/ under ${target}`, 1);
|
|
710
|
+
}
|
|
711
|
+
const snapshot = await new CorpusWorkspaceReader({ fs }).read("");
|
|
712
|
+
const entries = snapshot.entries.filter((e) => entryStatus(e) === "active");
|
|
713
|
+
const perFacet = /* @__PURE__ */ new Map();
|
|
714
|
+
for (const facet of parsed.facets) perFacet.set(facet, 0);
|
|
715
|
+
for (const entry of entries) {
|
|
716
|
+
for (const tag of tagsOf(entry)) {
|
|
717
|
+
if (perFacet.has(tag)) perFacet.set(tag, (perFacet.get(tag) ?? 0) + 1);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
process.stdout.write(`== coverage (${entries.length} entries) ==
|
|
721
|
+
`);
|
|
722
|
+
const thin = [];
|
|
723
|
+
for (const facet of parsed.facets) {
|
|
724
|
+
const n = perFacet.get(facet) ?? 0;
|
|
725
|
+
if (n < parsed.thin) thin.push(facet);
|
|
726
|
+
const mark = n < parsed.thin ? " THIN \u26A0" : "";
|
|
727
|
+
process.stdout.write(` ${facet.padEnd(16)} ${String(n).padStart(4)}${mark}
|
|
728
|
+
`);
|
|
729
|
+
}
|
|
730
|
+
const flagged = [];
|
|
731
|
+
for (const entry of entries) {
|
|
732
|
+
const title = String(entry.frontmatter.title ?? "");
|
|
733
|
+
const why = firstFlag(`${title}
|
|
734
|
+
${entry.body}`);
|
|
735
|
+
if (why) flagged.push({ entry, why });
|
|
736
|
+
}
|
|
737
|
+
process.stdout.write(`
|
|
738
|
+
== self-flagged entries (${flagged.length}) ==
|
|
739
|
+
`);
|
|
740
|
+
const poisonedSources = /* @__PURE__ */ new Set();
|
|
741
|
+
for (const { entry, why } of flagged) {
|
|
742
|
+
process.stdout.write(` ${entry.path} [${why}]
|
|
743
|
+
`);
|
|
744
|
+
for (const s of sourcesOf(entry)) poisonedSources.add(s);
|
|
745
|
+
}
|
|
746
|
+
const flaggedPaths = new Set(flagged.map((f) => f.entry.path));
|
|
747
|
+
const contaminated = [];
|
|
748
|
+
if (parsed.contaminated && poisonedSources.size > 0) {
|
|
749
|
+
for (const entry of entries) {
|
|
750
|
+
if (flaggedPaths.has(entry.path)) continue;
|
|
751
|
+
if (sourcesOf(entry).some((s) => poisonedSources.has(s))) {
|
|
752
|
+
contaminated.push(entry);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
process.stdout.write(
|
|
756
|
+
`
|
|
757
|
+
== contaminated siblings (${contaminated.length}) from ${poisonedSources.size} poisoned source(s) ==
|
|
758
|
+
`
|
|
759
|
+
);
|
|
760
|
+
for (const entry of contaminated) {
|
|
761
|
+
process.stdout.write(` ${entry.path}
|
|
762
|
+
`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (parsed.apply) {
|
|
766
|
+
const toMove = [...flagged.map((f) => f.entry), ...contaminated];
|
|
767
|
+
for (const entry of toMove) {
|
|
768
|
+
const rel = path.relative("entries", entry.path);
|
|
769
|
+
const dest = path.join(target, "demoted", rel);
|
|
770
|
+
await mkdir(path.dirname(dest), { recursive: true });
|
|
771
|
+
await rename(path.join(target, entry.path), dest);
|
|
772
|
+
}
|
|
773
|
+
process.stdout.write(`
|
|
774
|
+
moved ${toMove.length} entries \u2192 demoted/
|
|
775
|
+
`);
|
|
776
|
+
} else if (flagged.length > 0) {
|
|
777
|
+
process.stdout.write(
|
|
778
|
+
"\n(re-run with --apply to quarantine; --contaminated to also demote siblings)\n"
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
if (thin.length > 0) {
|
|
782
|
+
process.stdout.write(`
|
|
783
|
+
thin facets \u2192 loop back to \u2461 DISCOVER: ${thin.join(", ")}
|
|
784
|
+
`);
|
|
785
|
+
}
|
|
786
|
+
return 0;
|
|
787
|
+
}
|
|
634
788
|
var OsIdentityAdapter = class {
|
|
635
789
|
cached;
|
|
636
790
|
constructor(opts) {
|
|
@@ -1297,18 +1451,18 @@ var AssemblyAiStt = class {
|
|
|
1297
1451
|
...job.language_code ? { language: job.language_code } : {}
|
|
1298
1452
|
};
|
|
1299
1453
|
}
|
|
1300
|
-
async post(
|
|
1301
|
-
const r = await fetch(`${this.baseUrl}${
|
|
1454
|
+
async post(path6, body) {
|
|
1455
|
+
const r = await fetch(`${this.baseUrl}${path6}`, {
|
|
1302
1456
|
method: "POST",
|
|
1303
1457
|
headers: { authorization: this.apiKey, "content-type": "application/json" },
|
|
1304
1458
|
body: JSON.stringify(body)
|
|
1305
1459
|
});
|
|
1306
|
-
if (!r.ok) throw new Error(`AssemblyAI POST ${
|
|
1460
|
+
if (!r.ok) throw new Error(`AssemblyAI POST ${path6} ${r.status}: ${(await r.text()).slice(0, 200)}`);
|
|
1307
1461
|
return AAI_TRANSCRIPT.parse(await r.json());
|
|
1308
1462
|
}
|
|
1309
|
-
async get(
|
|
1310
|
-
const r = await fetch(`${this.baseUrl}${
|
|
1311
|
-
if (!r.ok) throw new Error(`AssemblyAI GET ${
|
|
1463
|
+
async get(path6) {
|
|
1464
|
+
const r = await fetch(`${this.baseUrl}${path6}`, { headers: { authorization: this.apiKey } });
|
|
1465
|
+
if (!r.ok) throw new Error(`AssemblyAI GET ${path6} ${r.status}: ${(await r.text()).slice(0, 200)}`);
|
|
1312
1466
|
return AAI_TRANSCRIPT.parse(await r.json());
|
|
1313
1467
|
}
|
|
1314
1468
|
};
|
|
@@ -1593,7 +1747,7 @@ var SOURCE_URL_FRONTMATTER = z.object({
|
|
|
1593
1747
|
}).loose().optional().catch(void 0)
|
|
1594
1748
|
}).loose().optional().catch(void 0)
|
|
1595
1749
|
}).loose();
|
|
1596
|
-
function
|
|
1750
|
+
function parse2(args) {
|
|
1597
1751
|
const out = {
|
|
1598
1752
|
workspace: void 0,
|
|
1599
1753
|
urlsFile: void 0,
|
|
@@ -1723,7 +1877,7 @@ async function scanIngestedUrls(workspaceRoot) {
|
|
|
1723
1877
|
return seen;
|
|
1724
1878
|
}
|
|
1725
1879
|
async function runImportWeb(args) {
|
|
1726
|
-
const parsed =
|
|
1880
|
+
const parsed = parse2(args);
|
|
1727
1881
|
const target = resolveWorkspacePath(parsed.workspace);
|
|
1728
1882
|
const urls = await readUrls(parsed);
|
|
1729
1883
|
if (urls.length === 0) {
|
|
@@ -1863,7 +2017,7 @@ knowledge engine (RAG) via the WriterPort. Import only stages the sources.
|
|
|
1863
2017
|
function msg3(e) {
|
|
1864
2018
|
return e instanceof Error ? e.message : String(e);
|
|
1865
2019
|
}
|
|
1866
|
-
function
|
|
2020
|
+
function parse3(args) {
|
|
1867
2021
|
const out = {
|
|
1868
2022
|
topic: void 0,
|
|
1869
2023
|
outputPath: void 0,
|
|
@@ -1871,7 +2025,8 @@ function parse2(args) {
|
|
|
1871
2025
|
channels: /* @__PURE__ */ new Set(["web"]),
|
|
1872
2026
|
lang: void 0,
|
|
1873
2027
|
tags: [],
|
|
1874
|
-
doImport: false
|
|
2028
|
+
doImport: false,
|
|
2029
|
+
fresh: false
|
|
1875
2030
|
};
|
|
1876
2031
|
for (let i = 0; i < args.length; i++) {
|
|
1877
2032
|
const a = args[i];
|
|
@@ -1898,6 +2053,9 @@ function parse2(args) {
|
|
|
1898
2053
|
case "--import":
|
|
1899
2054
|
out.doImport = true;
|
|
1900
2055
|
break;
|
|
2056
|
+
case "--fresh":
|
|
2057
|
+
out.fresh = true;
|
|
2058
|
+
break;
|
|
1901
2059
|
default:
|
|
1902
2060
|
if (!a.startsWith("-")) {
|
|
1903
2061
|
if (out.topic === void 0) out.topic = a;
|
|
@@ -2025,10 +2183,18 @@ async function discoverSocial(_topic, _lang, _max) {
|
|
|
2025
2183
|
process.stdout.write(" social channel needs a Bureau session \u2014 skipped.\n");
|
|
2026
2184
|
return [];
|
|
2027
2185
|
}
|
|
2186
|
+
async function readExistingUrls(outFile) {
|
|
2187
|
+
try {
|
|
2188
|
+
const prior = await readFile(outFile, "utf-8");
|
|
2189
|
+
return prior.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2190
|
+
} catch {
|
|
2191
|
+
return [];
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2028
2194
|
async function runDiscover(args) {
|
|
2029
|
-
const parsed =
|
|
2195
|
+
const parsed = parse3(args);
|
|
2030
2196
|
if (!parsed.topic) {
|
|
2031
|
-
return fail("discover requires a <topic> argument. Usage: corpus discover <topic> [path] [--max N] [--channels web,youtube,social] [--lang fr] [--tags t] [--import]", 2);
|
|
2197
|
+
return fail("discover requires a <topic> argument. Usage: corpus discover <topic> [path] [--max N] [--channels web,youtube,social] [--lang fr] [--tags t] [--import] [--fresh]", 2);
|
|
2032
2198
|
}
|
|
2033
2199
|
const outputPath = parsed.outputPath ? resolveWorkspacePath(parsed.outputPath) : process.cwd();
|
|
2034
2200
|
process.stdout.write(`discover "${parsed.topic}"
|
|
@@ -2071,10 +2237,22 @@ async function runDiscover(args) {
|
|
|
2071
2237
|
}
|
|
2072
2238
|
await mkdir(outputPath, { recursive: true });
|
|
2073
2239
|
const outFile = join(outputPath, "urls.discovered.txt");
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2240
|
+
const existing = parsed.fresh ? [] : await readExistingUrls(outFile);
|
|
2241
|
+
const merged = [...existing];
|
|
2242
|
+
const known = new Set(existing);
|
|
2243
|
+
for (const url of allUrls) {
|
|
2244
|
+
if (!known.has(url)) {
|
|
2245
|
+
known.add(url);
|
|
2246
|
+
merged.push(url);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
const added = merged.length - existing.length;
|
|
2250
|
+
await writeFile(outFile, merged.join("\n") + "\n", "utf-8");
|
|
2251
|
+
process.stdout.write(
|
|
2252
|
+
`
|
|
2253
|
+
written \u2192 ${outFile} (${merged.length} URLs` + (parsed.fresh ? ", --fresh overwrite" : `, ${added} new, ${existing.length} kept from previous runs`) + `)
|
|
2254
|
+
`
|
|
2255
|
+
);
|
|
2078
2256
|
if (parsed.doImport) {
|
|
2079
2257
|
process.stdout.write("\n --import: chaining import-web\u2026\n");
|
|
2080
2258
|
const importArgs = [
|
|
@@ -2445,7 +2623,7 @@ var DISTILLER_ENGINES = {
|
|
|
2445
2623
|
])
|
|
2446
2624
|
)
|
|
2447
2625
|
};
|
|
2448
|
-
function
|
|
2626
|
+
function parse4(args) {
|
|
2449
2627
|
const out = {
|
|
2450
2628
|
workspace: void 0,
|
|
2451
2629
|
sourceId: void 0,
|
|
@@ -2500,13 +2678,13 @@ async function readSources(root) {
|
|
|
2500
2678
|
const out = [];
|
|
2501
2679
|
for (const e of entries) {
|
|
2502
2680
|
if (!e.isFile() || !e.name.endsWith(".md")) continue;
|
|
2503
|
-
const
|
|
2681
|
+
const path6 = join(e.parentPath, e.name);
|
|
2504
2682
|
try {
|
|
2505
|
-
const parsed = matter2(await readFile(
|
|
2683
|
+
const parsed = matter2(await readFile(path6, "utf-8"));
|
|
2506
2684
|
const fm = SOURCE_FRONTMATTER.parse(parsed.data);
|
|
2507
2685
|
if (!fm.id || !parsed.content.trim()) continue;
|
|
2508
2686
|
out.push({
|
|
2509
|
-
path:
|
|
2687
|
+
path: path6,
|
|
2510
2688
|
id: fm.id,
|
|
2511
2689
|
title: fm.title ?? fm.id,
|
|
2512
2690
|
body: parsed.content.trim(),
|
|
@@ -2542,7 +2720,7 @@ async function scanDistilledSourceIds(root) {
|
|
|
2542
2720
|
}
|
|
2543
2721
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2544
2722
|
async function runDistill(args) {
|
|
2545
|
-
const parsed =
|
|
2723
|
+
const parsed = parse4(args);
|
|
2546
2724
|
const target = resolveWorkspacePath(parsed.workspace);
|
|
2547
2725
|
const engine = DISTILLER_ENGINES[parsed.engine];
|
|
2548
2726
|
if (!engine) {
|
|
@@ -2681,7 +2859,12 @@ async function runReport(args) {
|
|
|
2681
2859
|
}
|
|
2682
2860
|
function loadReportConfig(configPath) {
|
|
2683
2861
|
const raw = readFileSync(path.resolve(process.cwd(), configPath), "utf8");
|
|
2684
|
-
|
|
2862
|
+
const parsed = JSON.parse(raw);
|
|
2863
|
+
for (const warning of lintReportConfig(parsed)) {
|
|
2864
|
+
process.stderr.write(`corpus report: ${warning}
|
|
2865
|
+
`);
|
|
2866
|
+
}
|
|
2867
|
+
return reportConfigSchema.parse(parsed);
|
|
2685
2868
|
}
|
|
2686
2869
|
async function runReportPacks(args) {
|
|
2687
2870
|
let dataset;
|
|
@@ -3162,12 +3345,20 @@ Commands:
|
|
|
3162
3345
|
(--preset seeds a full vertical; --list shows them)
|
|
3163
3346
|
validate [path] JSON Schema check across every AIP file
|
|
3164
3347
|
lint [path] Run lints declared in KNOWLEDGE.md
|
|
3348
|
+
verify [path] --facets a,b,... [--thin 8] [--apply] [--contaminated]
|
|
3349
|
+
SOP \u2464b: coverage per facet tag +
|
|
3350
|
+
quarantine of self-flagged bad scrapes.
|
|
3351
|
+
--apply moves flagged entries to
|
|
3352
|
+
demoted/ (reversible); --contaminated
|
|
3353
|
+
(with --apply) also demotes siblings
|
|
3354
|
+
sharing a poisoned source.
|
|
3165
3355
|
events:emit <kind> --payload <json> [path]
|
|
3166
3356
|
Append an event to _log.md
|
|
3167
3357
|
events:tail [path] Print _log.md
|
|
3168
|
-
discover <topic> [path] [--max N --channels web,youtube,social --lang l --tags t --import]
|
|
3358
|
+
discover <topic> [path] [--max N --channels web,youtube,social --lang l --tags t --import --fresh]
|
|
3169
3359
|
Fan out across channels (web search + YouTube +
|
|
3170
|
-
social), dedup,
|
|
3360
|
+
social), dedup, merge into urls.discovered.txt
|
|
3361
|
+
(union with previous runs; --fresh overwrites).
|
|
3171
3362
|
Web: auto-picks first available key (SERPER /
|
|
3172
3363
|
EXA / TAVILY / GOOGLE_SEARCH). YouTube: yt-dlp
|
|
3173
3364
|
flat-playlist. --import chains import-web.
|
|
@@ -3229,6 +3420,8 @@ async function main(argv2) {
|
|
|
3229
3420
|
return await runValidate(rest);
|
|
3230
3421
|
case "lint":
|
|
3231
3422
|
return await runLint(rest);
|
|
3423
|
+
case "verify":
|
|
3424
|
+
return await runVerify(rest);
|
|
3232
3425
|
case "events:emit":
|
|
3233
3426
|
return await runEventsEmit(rest);
|
|
3234
3427
|
case "events:tail":
|