atris 3.1.0 → 3.2.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 +11 -2
- package/atris/skills/improve/SKILL.md +2 -2
- package/bin/atris.js +7 -1
- package/commands/autopilot.js +562 -39
- package/commands/business.js +14 -9
- package/commands/experiments.js +1 -1
- package/commands/release.js +183 -0
- package/commands/research.js +52 -0
- package/commands/sync.js +102 -13
- package/commands/verify.js +3 -3
- package/commands/wiki.js +45 -25
- package/lib/reward-config.js +24 -0
- package/lib/scorecard.js +16 -2
- package/lib/wiki.js +87 -56
- package/package.json +3 -2
package/commands/wiki.js
CHANGED
|
@@ -5,6 +5,8 @@ const { apiRequestJson } = require('../utils/api');
|
|
|
5
5
|
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
6
6
|
const {
|
|
7
7
|
WIKI_ROOT,
|
|
8
|
+
PRIVATE_WIKI_ROOT,
|
|
9
|
+
getWikiRoot,
|
|
8
10
|
ensureWikiScaffold,
|
|
9
11
|
findLocalWikiDir,
|
|
10
12
|
buildIngestPrompt,
|
|
@@ -31,9 +33,14 @@ function parseCloudArgs(args) {
|
|
|
31
33
|
|
|
32
34
|
function parseModeArgs(args) {
|
|
33
35
|
const cloud = args.includes('--cloud');
|
|
36
|
+
const privateMode = args.includes('--private');
|
|
37
|
+
if (cloud && privateMode) {
|
|
38
|
+
console.error('Use either --cloud or --private, not both.');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
34
41
|
return {
|
|
35
|
-
mode: cloud ? 'cloud' : 'local',
|
|
36
|
-
args: args.filter((arg) => arg !== '--cloud' && arg !== '--local'),
|
|
42
|
+
mode: cloud ? 'cloud' : (privateMode ? 'private' : 'local'),
|
|
43
|
+
args: args.filter((arg) => arg !== '--cloud' && arg !== '--local' && arg !== '--private'),
|
|
37
44
|
};
|
|
38
45
|
}
|
|
39
46
|
|
|
@@ -142,10 +149,10 @@ async function runChat(business, prompt, token) {
|
|
|
142
149
|
}
|
|
143
150
|
}
|
|
144
151
|
|
|
145
|
-
function printLocalPrompt(title, prompt, details = []) {
|
|
152
|
+
function printLocalPrompt(title, prompt, wikiRoot, details = []) {
|
|
146
153
|
console.log('');
|
|
147
154
|
console.log(title);
|
|
148
|
-
console.log(`Target: ${
|
|
155
|
+
console.log(`Target: ${wikiRoot}`);
|
|
149
156
|
details.forEach((detail) => console.log(detail));
|
|
150
157
|
console.log('');
|
|
151
158
|
console.log('Prompt for the current coding agent:');
|
|
@@ -160,9 +167,10 @@ async function wikiIngest(mode, slug, sourceValue) {
|
|
|
160
167
|
process.exit(1);
|
|
161
168
|
}
|
|
162
169
|
|
|
163
|
-
if (mode === 'local') {
|
|
164
|
-
const
|
|
165
|
-
|
|
170
|
+
if (mode === 'local' || mode === 'private') {
|
|
171
|
+
const wikiMode = mode === 'private' ? 'private' : 'public';
|
|
172
|
+
const wikiDir = ensureWikiScaffold(process.cwd(), wikiMode);
|
|
173
|
+
printLocalPrompt(mode === 'private' ? 'Private wiki ingest' : 'Local wiki ingest', buildIngestPrompt(sourceValue, wikiMode), getWikiRoot(wikiMode), [
|
|
166
174
|
`Wiki dir: ${wikiDir}`,
|
|
167
175
|
`Sources: ${sourceValue}`,
|
|
168
176
|
]);
|
|
@@ -182,13 +190,14 @@ async function wikiQuery(mode, slug, question) {
|
|
|
182
190
|
process.exit(1);
|
|
183
191
|
}
|
|
184
192
|
|
|
185
|
-
if (mode
|
|
186
|
-
const
|
|
193
|
+
if (mode !== 'cloud') {
|
|
194
|
+
const wikiMode = mode === 'private' ? 'private' : 'public';
|
|
195
|
+
const wikiDir = findLocalWikiDir(process.cwd(), slug, wikiMode);
|
|
187
196
|
if (!wikiDir) {
|
|
188
|
-
console.error(
|
|
197
|
+
console.error(`No local wiki found at ${getWikiRoot(wikiMode)}. Run: atris wiki ingest${wikiMode === 'private' ? ' --private' : ''} <path>`);
|
|
189
198
|
process.exit(1);
|
|
190
199
|
}
|
|
191
|
-
printLocalPrompt('Local wiki query', buildQueryPrompt(question), [
|
|
200
|
+
printLocalPrompt(mode === 'private' ? 'Private wiki query' : 'Local wiki query', buildQueryPrompt(question, wikiMode), getWikiRoot(wikiMode), [
|
|
192
201
|
`Wiki dir: ${wikiDir}`,
|
|
193
202
|
`Question: ${question}`,
|
|
194
203
|
]);
|
|
@@ -201,13 +210,14 @@ async function wikiQuery(mode, slug, question) {
|
|
|
201
210
|
}
|
|
202
211
|
|
|
203
212
|
async function wikiLint(mode, slug) {
|
|
204
|
-
if (mode
|
|
205
|
-
const
|
|
213
|
+
if (mode !== 'cloud') {
|
|
214
|
+
const wikiMode = mode === 'private' ? 'private' : 'public';
|
|
215
|
+
const wikiDir = findLocalWikiDir(process.cwd(), slug, wikiMode);
|
|
206
216
|
if (!wikiDir) {
|
|
207
|
-
console.error(
|
|
217
|
+
console.error(`No local wiki found at ${getWikiRoot(wikiMode)}. Run: atris wiki ingest${wikiMode === 'private' ? ' --private' : ''} <path>`);
|
|
208
218
|
process.exit(1);
|
|
209
219
|
}
|
|
210
|
-
printLocalPrompt('Local wiki lint', buildLintPrompt(), [`Wiki dir: ${wikiDir}`]);
|
|
220
|
+
printLocalPrompt(mode === 'private' ? 'Private wiki lint' : 'Local wiki lint', buildLintPrompt(wikiMode), getWikiRoot(wikiMode), [`Wiki dir: ${wikiDir}`]);
|
|
211
221
|
return;
|
|
212
222
|
}
|
|
213
223
|
|
|
@@ -217,15 +227,16 @@ async function wikiLint(mode, slug) {
|
|
|
217
227
|
await runChat(business, buildLintPrompt(), creds.token);
|
|
218
228
|
}
|
|
219
229
|
|
|
220
|
-
function wikiSearch(slug, query) {
|
|
230
|
+
function wikiSearch(mode, slug, query) {
|
|
221
231
|
if (!query) {
|
|
222
232
|
console.error('Usage: atris wiki search [business] <term>');
|
|
223
233
|
process.exit(1);
|
|
224
234
|
}
|
|
225
235
|
|
|
226
|
-
const
|
|
236
|
+
const wikiMode = mode === 'private' ? 'private' : 'public';
|
|
237
|
+
const wikiDir = findLocalWikiDir(process.cwd(), slug, wikiMode);
|
|
227
238
|
if (!wikiDir) {
|
|
228
|
-
console.error(`No local wiki found
|
|
239
|
+
console.error(`No local wiki found at ${getWikiRoot(wikiMode)}.`);
|
|
229
240
|
process.exit(1);
|
|
230
241
|
}
|
|
231
242
|
|
|
@@ -250,10 +261,11 @@ function wikiSearch(slug, query) {
|
|
|
250
261
|
console.log('');
|
|
251
262
|
}
|
|
252
263
|
|
|
253
|
-
function wikiLog(slug, limit) {
|
|
254
|
-
const
|
|
264
|
+
function wikiLog(mode, slug, limit) {
|
|
265
|
+
const wikiMode = mode === 'private' ? 'private' : 'public';
|
|
266
|
+
const wikiDir = findLocalWikiDir(process.cwd(), slug, wikiMode);
|
|
255
267
|
if (!wikiDir) {
|
|
256
|
-
console.error(`No local wiki found
|
|
268
|
+
console.error(`No local wiki found at ${getWikiRoot(wikiMode)}.`);
|
|
257
269
|
process.exit(1);
|
|
258
270
|
}
|
|
259
271
|
|
|
@@ -314,14 +326,21 @@ async function wikiCommand(subcommand, ...args) {
|
|
|
314
326
|
break;
|
|
315
327
|
}
|
|
316
328
|
case 'search': {
|
|
317
|
-
|
|
318
|
-
|
|
329
|
+
if (mode === 'private') {
|
|
330
|
+
wikiSearch(mode, null, cleanArgs.join(' '));
|
|
331
|
+
} else {
|
|
332
|
+
const [slug, query] = parseCloudArgs(cleanArgs);
|
|
333
|
+
wikiSearch(mode, slug, query);
|
|
334
|
+
}
|
|
319
335
|
break;
|
|
320
336
|
}
|
|
321
337
|
case 'log': {
|
|
322
338
|
let slug;
|
|
323
339
|
let limit;
|
|
324
|
-
if (
|
|
340
|
+
if (mode === 'private') {
|
|
341
|
+
slug = null;
|
|
342
|
+
limit = parseInt(cleanArgs[0], 10) || 20;
|
|
343
|
+
} else if (cleanArgs.length === 0) {
|
|
325
344
|
slug = autoDetectSlug();
|
|
326
345
|
limit = 20;
|
|
327
346
|
} else if (cleanArgs.length === 1) {
|
|
@@ -336,7 +355,7 @@ async function wikiCommand(subcommand, ...args) {
|
|
|
336
355
|
slug = cleanArgs[0];
|
|
337
356
|
limit = parseInt(cleanArgs[1], 10) || 20;
|
|
338
357
|
}
|
|
339
|
-
wikiLog(slug, limit);
|
|
358
|
+
wikiLog(mode, slug, limit);
|
|
340
359
|
break;
|
|
341
360
|
}
|
|
342
361
|
case 'loop': {
|
|
@@ -361,6 +380,7 @@ async function wikiCommand(subcommand, ...args) {
|
|
|
361
380
|
console.log('Flags:');
|
|
362
381
|
console.log(' --cloud Route ingest/query/lint to the cloud workspace');
|
|
363
382
|
console.log(' --local Be explicit about local mode');
|
|
383
|
+
console.log(` --private Use local private wiki at ${PRIVATE_WIKI_ROOT}/`);
|
|
364
384
|
console.log('');
|
|
365
385
|
console.log('Business is auto-detected from .atris/business.json for cloud mode if omitted.');
|
|
366
386
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frozen reward constants for the RL loop.
|
|
3
|
+
*
|
|
4
|
+
* These live outside mutable repo state so the loop cannot edit its own
|
|
5
|
+
* judge. REWARD_CHECKSUM is the SHA-256 of JSON.stringify(REWARD_CONFIG)
|
|
6
|
+
* + computeTickReward.toString() at ship time — if the config values or
|
|
7
|
+
* function body change, verifyJudgeIntegrity() halts the next tick.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
|
|
12
|
+
const REWARD_CONFIG = Object.freeze({
|
|
13
|
+
REVIEW_CLEAN: 1,
|
|
14
|
+
VERIFY_PASS: 3,
|
|
15
|
+
NPM_TEST_BONUS: 2,
|
|
16
|
+
COMMIT_LANDED: 1,
|
|
17
|
+
HALT_PENALTY: -3,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// SHA-256 of JSON.stringify(REWARD_CONFIG) + computeTickReward.toString() at ship time.
|
|
21
|
+
// Regenerate: node -e "const c=require('./lib/reward-config');const h=require('crypto').createHash('sha256');h.update(JSON.stringify(c.REWARD_CONFIG));h.update(require('./commands/autopilot').computeTickReward.toString());console.log(h.digest('hex'))"
|
|
22
|
+
const REWARD_CHECKSUM = '5a84be0f7f392d6ef05337be0776f864852e94d6391da0b41486298555595a40';
|
|
23
|
+
|
|
24
|
+
module.exports = { REWARD_CONFIG, REWARD_CHECKSUM };
|
package/lib/scorecard.js
CHANGED
|
@@ -2,6 +2,18 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { parseTodo } = require('./todo');
|
|
4
4
|
|
|
5
|
+
const PRIVATE_MEMORY_ROOT = '.atris/presidio';
|
|
6
|
+
|
|
7
|
+
function ensurePrivateMemoryDir(atrisDir) {
|
|
8
|
+
const privateDir = path.join(path.dirname(atrisDir), PRIVATE_MEMORY_ROOT);
|
|
9
|
+
fs.mkdirSync(privateDir, { recursive: true });
|
|
10
|
+
return privateDir;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getScorecardsPath(atrisDir) {
|
|
14
|
+
return path.join(ensurePrivateMemoryDir(atrisDir), 'scorecards.md');
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
function parsePickedAt(value) {
|
|
6
18
|
if (!value) return null;
|
|
7
19
|
const match = String(value).trim().match(/^(\d{4}-\d{2}-\d{2})(?:\s+(\d{2}:\d{2}))?/);
|
|
@@ -191,7 +203,7 @@ function writeScorecard(atrisDir, data) {
|
|
|
191
203
|
throw new Error('Scorecard: slug is required');
|
|
192
204
|
}
|
|
193
205
|
|
|
194
|
-
const scorecardsPath =
|
|
206
|
+
const scorecardsPath = getScorecardsPath(atrisDir);
|
|
195
207
|
|
|
196
208
|
// Ensure scorecards.md exists
|
|
197
209
|
if (!fs.existsSync(scorecardsPath)) {
|
|
@@ -244,7 +256,7 @@ function detectEndgameCompletion(atrisDir) {
|
|
|
244
256
|
* Parse scorecards.md and return array of scorecard objects.
|
|
245
257
|
*/
|
|
246
258
|
function readScorecards(atrisDir) {
|
|
247
|
-
const scorecardsPath =
|
|
259
|
+
const scorecardsPath = getScorecardsPath(atrisDir);
|
|
248
260
|
if (!fs.existsSync(scorecardsPath)) return [];
|
|
249
261
|
|
|
250
262
|
const content = fs.readFileSync(scorecardsPath, 'utf8');
|
|
@@ -280,6 +292,8 @@ function readScorecards(atrisDir) {
|
|
|
280
292
|
}
|
|
281
293
|
|
|
282
294
|
module.exports = {
|
|
295
|
+
PRIVATE_MEMORY_ROOT,
|
|
296
|
+
getScorecardsPath,
|
|
283
297
|
buildScorecardData,
|
|
284
298
|
writeScorecard,
|
|
285
299
|
readScorecards,
|
package/lib/wiki.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
4
|
const WIKI_ROOT = 'atris/wiki';
|
|
5
|
+
const PRIVATE_WIKI_ROOT = '.atris/presidio';
|
|
5
6
|
const LEGACY_WIKI_ROOT = 'wiki';
|
|
6
7
|
const WIKI_BRIEFS_SUBDIR = 'briefs';
|
|
7
8
|
const LEGACY_WIKI_BRIEFS_SUBDIR = 'syntheses';
|
|
@@ -9,6 +10,14 @@ const WIKI_SUBDIRS = ['people', 'systems', 'concepts', WIKI_BRIEFS_SUBDIR];
|
|
|
9
10
|
const WIKI_STATUS_FILE = 'STATUS.md';
|
|
10
11
|
const WIKI_CONTENT_SUBDIRS = WIKI_SUBDIRS.map((subdir) => path.join(WIKI_ROOT, subdir));
|
|
11
12
|
|
|
13
|
+
function getWikiRoot(mode = 'public') {
|
|
14
|
+
return mode === 'private' ? PRIVATE_WIKI_ROOT : WIKI_ROOT;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getWikiLinkRoot(mode = 'public') {
|
|
18
|
+
return mode === 'private' ? PRIVATE_WIKI_ROOT : 'atris/wiki';
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
function today() {
|
|
13
22
|
return new Date().toISOString().slice(0, 10);
|
|
14
23
|
}
|
|
@@ -17,10 +26,12 @@ function nowTime() {
|
|
|
17
26
|
return new Date().toTimeString().slice(0, 5);
|
|
18
27
|
}
|
|
19
28
|
|
|
20
|
-
function protocolMarkdown() {
|
|
29
|
+
function protocolMarkdown(mode = 'public') {
|
|
30
|
+
const wikiRoot = getWikiRoot(mode);
|
|
31
|
+
const wikiLinkRoot = getWikiLinkRoot(mode);
|
|
21
32
|
return `# Atris Wiki Protocol
|
|
22
33
|
|
|
23
|
-
This wiki lives in \`${
|
|
34
|
+
This wiki lives in \`${wikiRoot}/\`.
|
|
24
35
|
|
|
25
36
|
## Purpose
|
|
26
37
|
|
|
@@ -28,20 +39,20 @@ Turn raw project context into a living memory the next agent can pick up cold.
|
|
|
28
39
|
|
|
29
40
|
## Shape
|
|
30
41
|
|
|
31
|
-
- \`${
|
|
32
|
-
- \`${
|
|
33
|
-
- \`${
|
|
34
|
-
- \`${
|
|
35
|
-
- \`${
|
|
36
|
-
- \`${
|
|
37
|
-
- \`${
|
|
38
|
-
- \`${
|
|
42
|
+
- \`${wikiRoot}/wiki.md\` - this protocol
|
|
43
|
+
- \`${wikiRoot}/index.md\` - catalog grouped by page type
|
|
44
|
+
- \`${wikiRoot}/log.md\` - append-only ingest and lint history
|
|
45
|
+
- \`${wikiRoot}/STATUS.md\` - plain-English health summary
|
|
46
|
+
- \`${wikiRoot}/people/\` - humans (employees, contacts, stakeholders)
|
|
47
|
+
- \`${wikiRoot}/systems/\` - tools, tables, dashboards, services, products
|
|
48
|
+
- \`${wikiRoot}/concepts/\` - patterns, frameworks, recurring ideas
|
|
49
|
+
- \`${wikiRoot}/${WIKI_BRIEFS_SUBDIR}/\` - multi-page briefs and cross-cutting analysis
|
|
39
50
|
|
|
40
51
|
## Rules
|
|
41
52
|
|
|
42
53
|
- Read the full source before writing.
|
|
43
54
|
- Merge new facts into existing pages. Do not overwrite history blindly.
|
|
44
|
-
- Add cross-references with \`[[
|
|
55
|
+
- Add cross-references with \`[[${wikiLinkRoot}/...]]\` links.
|
|
45
56
|
- Keep \`index.md\`, \`log.md\`, and \`STATUS.md\` in sync with page changes.
|
|
46
57
|
- If something is unclear or contradictory, say so directly.
|
|
47
58
|
`;
|
|
@@ -155,15 +166,18 @@ function migrateLegacyBriefsDir(wikiDir) {
|
|
|
155
166
|
rewriteLegacyWikiReferences(wikiDir);
|
|
156
167
|
}
|
|
157
168
|
|
|
158
|
-
function ensureWikiScaffold(projectRoot = process.cwd()) {
|
|
159
|
-
const
|
|
169
|
+
function ensureWikiScaffold(projectRoot = process.cwd(), mode = 'public') {
|
|
170
|
+
const wikiRoot = getWikiRoot(mode);
|
|
171
|
+
const wikiDir = path.join(projectRoot, wikiRoot);
|
|
160
172
|
fs.mkdirSync(wikiDir, { recursive: true });
|
|
161
|
-
|
|
173
|
+
if (mode === 'public') {
|
|
174
|
+
migrateLegacyBriefsDir(wikiDir);
|
|
175
|
+
}
|
|
162
176
|
for (const subdir of WIKI_SUBDIRS) {
|
|
163
177
|
fs.mkdirSync(path.join(wikiDir, subdir), { recursive: true });
|
|
164
178
|
}
|
|
165
179
|
|
|
166
|
-
ensureFile(path.join(wikiDir, 'wiki.md'), protocolMarkdown());
|
|
180
|
+
ensureFile(path.join(wikiDir, 'wiki.md'), protocolMarkdown(mode));
|
|
167
181
|
ensureFile(path.join(wikiDir, 'index.md'), indexMarkdown());
|
|
168
182
|
ensureFile(path.join(wikiDir, 'log.md'), logMarkdown());
|
|
169
183
|
ensureFile(path.join(wikiDir, WIKI_STATUS_FILE), statusMarkdown());
|
|
@@ -171,7 +185,12 @@ function ensureWikiScaffold(projectRoot = process.cwd()) {
|
|
|
171
185
|
return wikiDir;
|
|
172
186
|
}
|
|
173
187
|
|
|
174
|
-
function findLocalWikiDir(projectRoot = process.cwd(), slug = null) {
|
|
188
|
+
function findLocalWikiDir(projectRoot = process.cwd(), slug = null, mode = 'public') {
|
|
189
|
+
if (mode === 'private') {
|
|
190
|
+
const privateDir = path.join(projectRoot, PRIVATE_WIKI_ROOT);
|
|
191
|
+
return fs.existsSync(privateDir) ? privateDir : null;
|
|
192
|
+
}
|
|
193
|
+
|
|
175
194
|
const tries = [
|
|
176
195
|
path.join(projectRoot, WIKI_ROOT),
|
|
177
196
|
path.join(projectRoot, LEGACY_WIKI_ROOT),
|
|
@@ -190,8 +209,8 @@ function normalizeWikiOnlyPrefix(prefix) {
|
|
|
190
209
|
return null;
|
|
191
210
|
}
|
|
192
211
|
|
|
193
|
-
function readWikiStatus(projectRoot = process.cwd(), slug = null) {
|
|
194
|
-
const wikiDir = findLocalWikiDir(projectRoot, slug);
|
|
212
|
+
function readWikiStatus(projectRoot = process.cwd(), slug = null, mode = 'public') {
|
|
213
|
+
const wikiDir = findLocalWikiDir(projectRoot, slug, mode);
|
|
195
214
|
if (!wikiDir) return null;
|
|
196
215
|
|
|
197
216
|
const statusPath = path.join(wikiDir, WIKI_STATUS_FILE);
|
|
@@ -275,8 +294,9 @@ function parseFrontmatter(content) {
|
|
|
275
294
|
return frontmatter;
|
|
276
295
|
}
|
|
277
296
|
|
|
278
|
-
function readWikiPages(projectRoot = process.cwd()) {
|
|
279
|
-
const
|
|
297
|
+
function readWikiPages(projectRoot = process.cwd(), mode = 'public') {
|
|
298
|
+
const wikiRoot = getWikiRoot(mode);
|
|
299
|
+
const wikiDir = path.join(projectRoot, wikiRoot);
|
|
280
300
|
const pages = [];
|
|
281
301
|
|
|
282
302
|
for (const subdir of WIKI_SUBDIRS) {
|
|
@@ -302,8 +322,8 @@ function normalizeSourcePath(projectRoot, source) {
|
|
|
302
322
|
return path.normalize(path.join(projectRoot, source));
|
|
303
323
|
}
|
|
304
324
|
|
|
305
|
-
function findStaleWikiPages(projectRoot = process.cwd()) {
|
|
306
|
-
return readWikiPages(projectRoot)
|
|
325
|
+
function findStaleWikiPages(projectRoot = process.cwd(), mode = 'public') {
|
|
326
|
+
return readWikiPages(projectRoot, mode)
|
|
307
327
|
.map((page) => {
|
|
308
328
|
const sources = Array.isArray(page.frontmatter.sources) ? page.frontmatter.sources : [];
|
|
309
329
|
if (sources.length === 0) return null;
|
|
@@ -345,13 +365,14 @@ function findStaleWikiPages(projectRoot = process.cwd()) {
|
|
|
345
365
|
}
|
|
346
366
|
|
|
347
367
|
function extractWikiLinks(content) {
|
|
348
|
-
const matches = content.match(/\[\[(atris\/wiki\/[^\]]+?)\]\]/g) || [];
|
|
368
|
+
const matches = content.match(/\[\[((?:atris\/wiki|\.atris\/presidio)\/[^\]]+?)\]\]/g) || [];
|
|
349
369
|
return matches.map((match) => match.slice(2, -2));
|
|
350
370
|
}
|
|
351
371
|
|
|
352
|
-
function findWikiOrphans(projectRoot = process.cwd()) {
|
|
353
|
-
const
|
|
354
|
-
const
|
|
372
|
+
function findWikiOrphans(projectRoot = process.cwd(), mode = 'public') {
|
|
373
|
+
const wikiRoot = getWikiRoot(mode);
|
|
374
|
+
const pages = readWikiPages(projectRoot, mode);
|
|
375
|
+
const indexPath = path.join(projectRoot, wikiRoot, 'index.md');
|
|
355
376
|
const indexContent = fs.existsSync(indexPath) ? fs.readFileSync(indexPath, 'utf8') : '';
|
|
356
377
|
|
|
357
378
|
const inboundLinks = new Map();
|
|
@@ -423,8 +444,8 @@ function parseStatusBullets(content) {
|
|
|
423
444
|
return bullets;
|
|
424
445
|
}
|
|
425
446
|
|
|
426
|
-
function writeWikiStatus(projectRoot = process.cwd(), report) {
|
|
427
|
-
const wikiDir = ensureWikiScaffold(projectRoot);
|
|
447
|
+
function writeWikiStatus(projectRoot = process.cwd(), report, mode = 'public') {
|
|
448
|
+
const wikiDir = ensureWikiScaffold(projectRoot, mode);
|
|
428
449
|
const statusPath = path.join(wikiDir, WIKI_STATUS_FILE);
|
|
429
450
|
const existing = fs.existsSync(statusPath) ? fs.readFileSync(statusPath, 'utf8') : '';
|
|
430
451
|
const bullets = parseStatusBullets(existing);
|
|
@@ -444,8 +465,8 @@ function writeWikiStatus(projectRoot = process.cwd(), report) {
|
|
|
444
465
|
return statusPath;
|
|
445
466
|
}
|
|
446
467
|
|
|
447
|
-
function appendWikiLog(projectRoot = process.cwd(), summary, details = []) {
|
|
448
|
-
const wikiDir = ensureWikiScaffold(projectRoot);
|
|
468
|
+
function appendWikiLog(projectRoot = process.cwd(), summary, details = [], mode = 'public') {
|
|
469
|
+
const wikiDir = ensureWikiScaffold(projectRoot, mode);
|
|
449
470
|
const logPath = path.join(wikiDir, 'log.md');
|
|
450
471
|
let content = fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf8') : '# Atris Wiki Log\n';
|
|
451
472
|
const dateHeader = `## ${today()}`;
|
|
@@ -471,17 +492,20 @@ function formatSourceList(sourceValue) {
|
|
|
471
492
|
.join(', ');
|
|
472
493
|
}
|
|
473
494
|
|
|
474
|
-
|
|
495
|
+
function buildWikiSchema(mode = 'public') {
|
|
496
|
+
const wikiRoot = getWikiRoot(mode);
|
|
497
|
+
const wikiLinkRoot = getWikiLinkRoot(mode);
|
|
498
|
+
return `The wiki lives in ${wikiRoot}/.
|
|
475
499
|
|
|
476
500
|
Structure:
|
|
477
|
-
- ${
|
|
478
|
-
- ${
|
|
479
|
-
- ${
|
|
480
|
-
- ${
|
|
481
|
-
- ${
|
|
482
|
-
- ${
|
|
483
|
-
- ${
|
|
484
|
-
- ${
|
|
501
|
+
- ${wikiRoot}/wiki.md - protocol for future agents
|
|
502
|
+
- ${wikiRoot}/index.md - catalog grouped by type
|
|
503
|
+
- ${wikiRoot}/log.md - append-only activity log
|
|
504
|
+
- ${wikiRoot}/STATUS.md - plain-English health summary
|
|
505
|
+
- ${wikiRoot}/people/ - one page per human
|
|
506
|
+
- ${wikiRoot}/systems/ - one page per tool, table, dashboard, service, or product
|
|
507
|
+
- ${wikiRoot}/concepts/ - pattern and framework pages
|
|
508
|
+
- ${wikiRoot}/${WIKI_BRIEFS_SUBDIR}/ - cross-cutting briefs referencing 3+ pages
|
|
485
509
|
|
|
486
510
|
Page format:
|
|
487
511
|
---
|
|
@@ -497,7 +521,7 @@ tags: [tag1, tag2]
|
|
|
497
521
|
# Title
|
|
498
522
|
Body in markdown.
|
|
499
523
|
## Cross-References
|
|
500
|
-
- [[
|
|
524
|
+
- [[${wikiLinkRoot}/people/related.md]] - why related
|
|
501
525
|
|
|
502
526
|
Rules:
|
|
503
527
|
- Read every listed source fully before writing
|
|
@@ -505,20 +529,23 @@ Rules:
|
|
|
505
529
|
- Keep index.md, log.md, and STATUS.md current
|
|
506
530
|
- Flag contradictions directly instead of smoothing them over
|
|
507
531
|
- Never modify the raw source documents you ingested`;
|
|
532
|
+
}
|
|
508
533
|
|
|
509
|
-
function buildIngestPrompt(sourceValue) {
|
|
534
|
+
function buildIngestPrompt(sourceValue, mode = 'public') {
|
|
535
|
+
const wikiRoot = getWikiRoot(mode);
|
|
536
|
+
const wikiLinkRoot = getWikiLinkRoot(mode);
|
|
510
537
|
return `Atris wiki ingest: ${formatSourceList(sourceValue)}
|
|
511
|
-
${
|
|
538
|
+
${buildWikiSchema(mode)}
|
|
512
539
|
|
|
513
540
|
Workflow:
|
|
514
541
|
1. Read every source in: ${sourceValue}
|
|
515
|
-
2. Ensure ${
|
|
542
|
+
2. Ensure ${wikiRoot}/ exists with wiki.md, index.md, log.md, STATUS.md, and the 3 page subfolders
|
|
516
543
|
3. Extract people, systems, and concepts worth preserving
|
|
517
|
-
4. Create or update pages under ${
|
|
518
|
-
5. Add cross-references using [[
|
|
519
|
-
6. Update ${
|
|
520
|
-
7. Append an INGEST entry to ${
|
|
521
|
-
8. Refresh ${
|
|
544
|
+
4. Create or update pages under ${wikiRoot}/, merging with existing facts instead of replacing them
|
|
545
|
+
5. Add cross-references using [[${wikiLinkRoot}/...]] links
|
|
546
|
+
6. Update ${wikiRoot}/index.md with one-line descriptions of touched pages
|
|
547
|
+
7. Append an INGEST entry to ${wikiRoot}/log.md under today's date
|
|
548
|
+
8. Refresh ${wikiRoot}/STATUS.md in plain English for a non-technical reader
|
|
522
549
|
|
|
523
550
|
Quality bar:
|
|
524
551
|
- Ask clarifying questions if the source is ambiguous
|
|
@@ -527,18 +554,20 @@ Quality bar:
|
|
|
527
554
|
- Leave the wiki sharper than you found it`;
|
|
528
555
|
}
|
|
529
556
|
|
|
530
|
-
function buildQueryPrompt(question) {
|
|
557
|
+
function buildQueryPrompt(question, mode = 'public') {
|
|
558
|
+
const wikiRoot = getWikiRoot(mode);
|
|
531
559
|
return `Atris wiki query: ${question}
|
|
532
560
|
|
|
533
|
-
Read ${
|
|
534
|
-
Answer from the wiki with direct references to page paths under ${
|
|
561
|
+
Read ${wikiRoot}/index.md first, then the most relevant pages.
|
|
562
|
+
Answer from the wiki with direct references to page paths under ${wikiRoot}/.
|
|
535
563
|
If the answer reveals a reusable insight, offer to save it as a brief page.`;
|
|
536
564
|
}
|
|
537
565
|
|
|
538
|
-
function buildLintPrompt() {
|
|
566
|
+
function buildLintPrompt(mode = 'public') {
|
|
567
|
+
const wikiRoot = getWikiRoot(mode);
|
|
539
568
|
return `Atris wiki lint pass
|
|
540
569
|
|
|
541
|
-
Read ${
|
|
570
|
+
Read ${wikiRoot}/index.md, crawl the referenced pages, and inspect the local wiki.
|
|
542
571
|
|
|
543
572
|
Checks:
|
|
544
573
|
1. Every page referenced by index.md exists
|
|
@@ -546,8 +575,8 @@ Checks:
|
|
|
546
575
|
3. Orphan pages are listed
|
|
547
576
|
4. Contradictions are called out plainly
|
|
548
577
|
5. Gaps worth ingesting next are listed concretely
|
|
549
|
-
6. ${
|
|
550
|
-
7. ${
|
|
578
|
+
6. ${wikiRoot}/STATUS.md is rewritten in plain English
|
|
579
|
+
7. ${wikiRoot}/log.md gets a LINT entry under today's date
|
|
551
580
|
|
|
552
581
|
Output:
|
|
553
582
|
- Clear summary for a non-technical reader
|
|
@@ -557,11 +586,13 @@ Output:
|
|
|
557
586
|
|
|
558
587
|
module.exports = {
|
|
559
588
|
WIKI_ROOT,
|
|
589
|
+
PRIVATE_WIKI_ROOT,
|
|
560
590
|
LEGACY_WIKI_ROOT,
|
|
561
591
|
WIKI_SUBDIRS,
|
|
562
592
|
WIKI_CONTENT_SUBDIRS,
|
|
563
|
-
WIKI_SCHEMA,
|
|
593
|
+
WIKI_SCHEMA: buildWikiSchema(),
|
|
564
594
|
WIKI_STATUS_FILE,
|
|
595
|
+
getWikiRoot,
|
|
565
596
|
ensureWikiScaffold,
|
|
566
597
|
findLocalWikiDir,
|
|
567
598
|
normalizeWikiOnlyPrefix,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atris",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Atris — an operating system for intelligence. Integrates with any agent.",
|
|
5
5
|
"main": "bin/atris.js",
|
|
6
6
|
"bin": {
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"atris/skills/"
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
|
-
"test": "node --test"
|
|
35
|
+
"test": "node --test",
|
|
36
|
+
"prepare": "cp scripts/pre-commit .git/hooks/pre-commit 2>/dev/null && chmod +x .git/hooks/pre-commit || true"
|
|
36
37
|
},
|
|
37
38
|
"keywords": [
|
|
38
39
|
"atrisdev",
|