@awebai/oats 0.27.2 → 0.29.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/bin/oats.mjs +445 -96
- package/capabilities/oats-okf/bin/oats-okf.mjs +55 -30
- package/capabilities/oats-okf/injects/okf.md +36 -28
- package/capabilities/oats-okf/lib/binding-wire.mjs +4 -1
- package/capabilities/oats-okf/lib/config.mjs +6 -1
- package/capabilities/oats-okf/lib/consult.mjs +496 -0
- package/capabilities/oats-okf/lib/harvest-status.mjs +88 -0
- package/capabilities/oats-okf/lib/harvest-switch.mjs +81 -0
- package/capabilities/oats-okf/lib/inspection.mjs +11 -3
- package/capabilities/oats-okf/lib/io.mjs +9 -2
- package/capabilities/oats-okf/lib/okf-validate.mjs +123 -0
- package/capabilities/oats-okf/lib/sources.mjs +42 -55
- package/capabilities/oats-okf/lib/stores.mjs +19 -11
- package/capabilities/oats-okf/lib/worker.mjs +90 -8
- package/capabilities/oats-okf/oats.json +24 -9
- package/capabilities/oats-okf/skills/okf-consultation/SKILL.md +144 -0
- package/capabilities/oats-okf/skills/okf-consultation/references/consult.md +86 -0
- package/capabilities/oats-okf/skills/okf-instance-knowledge/SKILL.md +104 -0
- package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +140 -0
- package/capabilities/oats-okf-harvest/injects/harvester.md +12 -0
- package/capabilities/oats-okf-harvest/oats.json +26 -0
- package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +168 -0
- package/capabilities/oats-okf-harvest/skills/knowledge-theory/SKILL.md +192 -0
- package/capabilities/{oats-okf/skills/okf → oats-okf-harvest/skills/okf-authoring}/SKILL.md +15 -22
- package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +149 -0
- package/capabilities/oats-okf-maintenance/injects/maintainer.md +12 -0
- package/capabilities/oats-okf-maintenance/lib/provenance.mjs +45 -0
- package/capabilities/oats-okf-maintenance/oats.json +21 -0
- package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +144 -0
- package/capabilities/oats-okf-maintenance/skills/knowledge-theory/SKILL.md +192 -0
- package/capabilities/oats-okf-maintenance/skills/okf-authoring/SKILL.md +151 -0
- package/capabilities/oats-okf-maintenance/skills/okf-authoring/scripts/okf-validate.mjs +123 -0
- package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +146 -0
- package/capabilities/oats-review/injects/review.md +3 -2
- package/capabilities/oats-review/oats.json +3 -4
- package/docs/capabilities.md +41 -9
- package/docs/capability-manifest.schema.json +0 -7
- package/docs/design/2026-09-24-phase-d-plan.md +11 -0
- package/docs/design/2026-09-26-desktop-design-brief-architecture.md +241 -0
- package/docs/design/2026-09-26-okf-knowledge-operations.md +389 -0
- package/docs/desktop-cli-api.md +342 -10
- package/docs/implementation.md +1 -1
- package/docs/knowledge-capability-authoring.md +8 -2
- package/docs/knowledge-reference/package-craft.md +8 -5
- package/docs/knowledge.md +101 -0
- package/docs/oats-local.schema.json +33 -2
- package/docs/oats-package.schema.json +39 -0
- package/docs/official-catalog.md +7 -4
- package/docs/packages.md +76 -6
- package/docs/release-lane.md +1 -1
- package/docs/release-notes/v0.28.0.md +144 -0
- package/docs/release-notes/v0.29.0.md +240 -0
- package/docs/schedules.md +230 -4
- package/docs/souls-and-instances.md +11 -9
- package/docs/workspaces.md +18 -3
- package/lib/automations.mjs +369 -0
- package/lib/core.mjs +87 -158
- package/lib/instance-inspect.mjs +16 -8
- package/lib/instance-resolution.mjs +90 -197
- package/lib/materialize.mjs +18 -7
- package/lib/operator-dispatch.mjs +1 -2
- package/lib/packages.mjs +107 -6
- package/lib/remote.mjs +21 -1
- package/lib/resolve.mjs +71 -9
- package/lib/schedule.mjs +228 -45
- package/lib/triggers.mjs +678 -0
- package/lib/workspace.mjs +81 -4
- package/package-catalog.json +6 -4
- package/package.json +1 -1
- package/capabilities/oats-okf/agents/memory-harvest/AGENTS.md +0 -21
- package/capabilities/oats-okf/agents/memory-harvest/soul.yaml +0 -5
- package/capabilities/oats-okf/skills/memory-harvest/SKILL.md +0 -285
- package/capabilities/oats-review/agents/reviewer/AGENTS.md +0 -53
- package/capabilities/oats-review/agents/reviewer/soul.yaml +0 -6
- /package/capabilities/{oats-okf/skills/okf → oats-okf-harvest/skills/okf-authoring}/scripts/okf-validate.mjs +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { fs, join, resolve, readJSON, safePath, oats, fail, unlock } from '../lib/io.mjs';
|
|
3
|
+
import { loadBindings } from '../lib/config.mjs';
|
|
4
|
+
import { register, registerCaptured, loadInvocationSourceReceipt, homeSource, loadSource, loadStatus, saveStatus, updateStatus, capture, scheduleSource, settleRetiredSchedule, service, markerPath, harvestOffRecord } from '../lib/sources.mjs';
|
|
5
|
+
import { harvestStatus, setupHarvest } from '../lib/harvest-status.mjs';
|
|
6
|
+
import { settings } from '../lib/config.mjs';
|
|
7
|
+
import { CONSULT } from '../lib/consult.mjs';
|
|
6
8
|
import { runSource, complete, retry, readRun, requireQualifiedHelper } from '../lib/worker.mjs';
|
|
7
9
|
import { initBase, migrate, deliverMigration, cutoverMigration, migrateSource, forgetMigration } from '../lib/migration.mjs';
|
|
8
10
|
import { inspect } from '../lib/inspection.mjs';
|
|
@@ -13,9 +15,18 @@ oats okf harvest [--home PATH] [--no-launch] [--json]
|
|
|
13
15
|
oats okf run-source --source FILE [--manual] [--no-launch] [--json]
|
|
14
16
|
oats okf complete --source FILE --run ID --judgment FILE [--json]
|
|
15
17
|
oats okf retry --source FILE [--run ID --rejudge | --rejudge | --launch | --adopt-home PATH] [--json]
|
|
16
|
-
oats okf
|
|
17
|
-
oats okf
|
|
18
|
+
oats okf bases [--fresh] [--json]
|
|
19
|
+
oats okf index [--base ALIAS] [NODE | ALIAS/NODE] [--fresh] [--json]
|
|
20
|
+
oats okf cat --base ALIAS PATH [--from PATH] [--fresh] [--json]
|
|
21
|
+
oats okf ls --base ALIAS [DIR] [--fresh] [--json]
|
|
22
|
+
oats okf links --base ALIAS PATH [--fresh] [--json]
|
|
23
|
+
oats okf search [--base ALIAS | --all] [--node NODE] [--regex] [--case-sensitive] TEXT [--fresh] [--json]
|
|
24
|
+
Consult commands read the accepted state remotely (host cache, no local copy);
|
|
25
|
+
also accept --home PATH | --source FILE. PATH is /node/x.md from the base root,
|
|
26
|
+
relative to --from's directory, or bare node/x.md from the root.
|
|
18
27
|
oats okf setup --source FILE [--enable | --disable] [--install-host] [--json]
|
|
28
|
+
oats okf setup --harvest on|off [--json] (writes oats-local.yaml settings.oats.okf.harvest)
|
|
29
|
+
oats okf harvest-status [--soul NAME] [--json] (the effective harvest switch, why, and the registered sources)
|
|
19
30
|
oats okf init --base ALIAS --nodes FILE [--output PATH | --confirm] [--json]
|
|
20
31
|
oats okf migrate --legacy PATH --base ALIAS --node NODE --output PATH [--json]
|
|
21
32
|
oats okf migrate --deliver FILE | --cutover FILE --soul-dir PATH [--json]
|
|
@@ -36,11 +47,13 @@ if(args.includes('--help') || args.includes('-h')) {process.stdout.write(HELP);}
|
|
|
36
47
|
else {
|
|
37
48
|
const event=process.env.OATS_EVENT || args[0];
|
|
38
49
|
const hook=['spawn','retire','soul-scaffold'].includes(event);
|
|
39
|
-
|
|
50
|
+
const consult=Object.hasOwn(CONSULT,event);
|
|
51
|
+
let exit=0,answer,text,textMode=consult && !args.includes('--json');
|
|
40
52
|
try {
|
|
41
|
-
const flags={}; const boolean=new Set(['json','no-launch','manual','rejudge','launch','enable','disable','install-host','confirm']);
|
|
53
|
+
const flags={},positionals=[]; const boolean=new Set(['json','no-launch','manual','rejudge','launch','enable','disable','install-host','confirm','fresh','all','regex','case-sensitive']);
|
|
42
54
|
for(let i=1;i<args.length;i++) {
|
|
43
|
-
if(
|
|
55
|
+
if(consult && args[i]==='--') {positionals.push(...args.slice(i+1));break;}
|
|
56
|
+
if(!args[i].startsWith('--')) {if(!consult) fail('E_USAGE',`unexpected argument ${args[i]}`);positionals.push(args[i]);continue;}
|
|
44
57
|
const k=args[i].slice(2);if(k in flags) fail('E_USAGE',`duplicate --${k}`);
|
|
45
58
|
if(boolean.has(k)) flags[k]=true;
|
|
46
59
|
else {if(!args[i+1] || args[i+1].startsWith('--')) fail('E_USAGE',`--${k} needs a value`);flags[k]=args[++i];}
|
|
@@ -49,8 +62,10 @@ else {
|
|
|
49
62
|
spawn:[],retire:['home'], 'soul-scaffold':[],
|
|
50
63
|
harvest:['home','no-launch','native-request','worker-mode'],inspect:['home','source'],
|
|
51
64
|
'run-source':['source','manual','no-launch'],complete:['source','run','judgment'],
|
|
52
|
-
retry:['source','run','rejudge','launch','adopt-home'],read:['home','source','base','path'],refresh:['home','source'],
|
|
53
|
-
|
|
65
|
+
retry:['source','run','rejudge','launch','adopt-home'],read:['home','source','base','path','fresh'],refresh:['home','source'],'harvest-status':['home'],
|
|
66
|
+
bases:['home','source','fresh'],index:['home','source','base','fresh'],cat:['home','source','base','from','fresh'],ls:['home','source','base','fresh'],
|
|
67
|
+
links:['home','source','base','fresh'],search:['home','source','base','all','node','regex','case-sensitive','fresh'],
|
|
68
|
+
setup:['source','enable','disable','install-host','harvest'],init:['base','nodes','output','confirm'],
|
|
54
69
|
migrate:['source-home','legacy','base','node','output','deliver','cutover','soul-dir'],unlock:['lock','token']
|
|
55
70
|
};
|
|
56
71
|
for(const k of Object.keys(flags)) if(!['json','soul',...(accepted[event] || [])].includes(k)) fail('E_USAGE',`unknown flag --${k} for ${event}`);
|
|
@@ -107,15 +122,22 @@ else {
|
|
|
107
122
|
readRun(s,id);return s;
|
|
108
123
|
};
|
|
109
124
|
let result;
|
|
110
|
-
if(event==='
|
|
125
|
+
if(event==='read') fail('E_REMOVED','okf 4.0.0 removed read: use `oats okf cat --base ALIAS PATH` (same path, text and receipt)');
|
|
126
|
+
if(consult) {const answer=CONSULT[event](src(),flags,positionals);result=answer.result;text=answer.text;}
|
|
127
|
+
else if(event==='refresh') fail('E_REMOVED','okf 3.0.0 has no per-instance views; index/cat always read the accepted state: run `oats okf index`, then `oats okf cat --base ALIAS PATH`');
|
|
128
|
+
else if(event==='harvest-status') result=harvestStatus({home,flags});
|
|
129
|
+
else if(event==='soul-scaffold') {
|
|
111
130
|
// Souls are portable declarations, never an implicit knowledge store.
|
|
112
131
|
result={meta:{scaffolded:false},brief:'OKF requires explicit external bindings and soul/okf.json before a working instance can spawn. Use init or migrate; no knowledge was created in this soul.'};
|
|
113
132
|
} else if(event==='spawn') {
|
|
114
133
|
const s=sourceReceipt.mode==='captured'?registerCaptured(home,sourceReceipt.receipt):register(home);
|
|
134
|
+
const nodes=(list)=>list.join(', ') || 'none';
|
|
135
|
+
const brief=decl=>`Your soul knowledge is read remotely at its accepted state; there is no local copy. Start every task with your instance knowledge (STATE.md, log.md, notes/), then \`oats okf index\` (owns: ${nodes(decl.owns)}; reads: ${nodes(decl.reads.filter(r=>!decl.owns.includes(r)))}) and \`oats okf cat --base ALIAS PATH\` for the concepts the task needs; \`oats okf search\` before re-deriving a decision. Load okf-consultation and okf-instance-knowledge. Never edit accepted knowledge.`;
|
|
115
136
|
if(s.skipped) result={meta:{memory:'none'},brief:'Service agent: follow your own task; no working-memory upkeep.'};
|
|
137
|
+
else if(s.harvestOff) result={meta:{memory:'okf-v2',harvest:'off',reason:s.switch.reason},brief:`${brief(s.decl)} Harvest is off for this instance: nothing of this session is captured.`,...(s.switch.warnings.length?{warning:`oats-okf: ${s.switch.warnings.join('; ')}`}:{})};
|
|
116
138
|
else {
|
|
117
139
|
const schedule=loadStatus(s).schedule.result;
|
|
118
|
-
result={meta:{memory:'okf-v2',source:s.file,schedule},brief
|
|
140
|
+
result={meta:{memory:'okf-v2',harvest:'on',source:s.file,schedule},brief:`${brief(s.decl)} Harvest is on: your notes and session are captured for the knowledge harvester.`};
|
|
119
141
|
}
|
|
120
142
|
} else if(event==='retire') {
|
|
121
143
|
if(captured) {
|
|
@@ -124,6 +146,7 @@ else {
|
|
|
124
146
|
else {if(!fs.existsSync(markerPath(home))) fail('E_MIGRATION','captured retire requires a durable registered source or explicit helper receipt');s=src();}
|
|
125
147
|
if(s) {scheduleSource(s);const r=capture(s,{final:true});const schedule=settleRetiredSchedule(s);result={meta:{retired:r.complete===true,source:s.file,capture:r,schedule},brief:'Final input is in durable custody. Delivery remains asynchronous.'};}
|
|
126
148
|
} else if(service(home)) result={meta:{retired:true}};
|
|
149
|
+
else if(!fs.existsSync(markerPath(home)) && harvestOffRecord(home)) result={meta:{retired:true,reason:'harvest-off'}};
|
|
127
150
|
else if(!fs.existsSync(markerPath(home))) {
|
|
128
151
|
if(['STATE.md','log.md','notes','.okf-harvest-record.json','.okf-harvest-record.next.json'].some(p=>fs.existsSync(join(home,p)))) fail('E_MIGRATION','unregistered/legacy source has memory; explicitly migrate/register before retirement');
|
|
129
152
|
result={meta:{retired:true,reason:'nothing-to-delete'}};
|
|
@@ -139,32 +162,30 @@ else {
|
|
|
139
162
|
// Snapshot absence does not turn a persisted captured source into legacy.
|
|
140
163
|
if(fs.existsSync(markerPath(home))) requireQualifiedHelper(src());
|
|
141
164
|
const s=register(home);
|
|
165
|
+
if(s.harvestOff) fail('E_HARVEST_OFF',`harvest is off for this instance: ${s.switch.reason}. Nothing was captured.`);
|
|
142
166
|
result=s.skipped?{status:'skipped',reason:'service'}:runSource(s,{manual:true,noLaunch:!!flags['no-launch']});
|
|
143
167
|
}
|
|
144
|
-
} else if(event==='run-source')
|
|
168
|
+
} else if(event==='run-source') {
|
|
169
|
+
// The deployment can switch harvest off after a source registered: the
|
|
170
|
+
// job then captures and processes nothing (the soul's opt-out was already
|
|
171
|
+
// applied at registration). Nothing drains when it is switched back on.
|
|
172
|
+
const source=src();
|
|
173
|
+
result=settings().harvest!=='on'?{status:'harvest-off',source:source.file,reason:'the deployment does not switch harvest on (settings.oats.okf.harvest); nothing was captured'}
|
|
174
|
+
:runSource(source,{manual:!!flags.manual,noLaunch:!!flags['no-launch']});
|
|
175
|
+
}
|
|
145
176
|
else if(event==='complete') {const s=src();if(captured) retainedRun(s);result=complete(s,flags.run,flags.judgment && resolve(flags.judgment));}
|
|
146
177
|
else if(event==='retry') {const s=src();if(captured && !flags.run && !flags.rejudge && !flags.launch && !flags['adopt-home']) retainedRun(s);result=retry(s,{run:flags.run,rejudge:!!flags.rejudge,launch:!!flags.launch,adoptHome:flags['adopt-home']});}
|
|
147
178
|
else if(event==='inspect') result=inspect(src());
|
|
179
|
+
else if(event==='setup' && flags.harvest!==undefined) {
|
|
180
|
+
if(flags.source || flags.enable || flags.disable || flags['install-host']) fail('E_USAGE','setup --harvest takes no other setup flag');
|
|
181
|
+
result=setupHarvest(flags.harvest);
|
|
182
|
+
}
|
|
148
183
|
else if(event==='setup') {
|
|
149
184
|
const s=src();if(flags.enable && flags.disable) fail('E_USAGE','choose enable or disable');
|
|
150
185
|
scheduleSource(s);
|
|
151
186
|
if(flags.enable || flags.disable) {oats(['schedule',flags.enable?'enable':'disable',`okf-${s.id}`,'--dir',s.context,'--json'],s.context);updateStatus(s,current=>{current.auto=!!flags.enable;});}
|
|
152
187
|
if(flags['install-host']) oats(['schedule','host','install','--dir',s.context,'--json'],s.context);
|
|
153
188
|
result={source:s.file,scheduler:oats(['schedule','list','--dir',s.context,'--json'],s.context).scheduler};
|
|
154
|
-
} else if(event==='read' || event==='refresh') {
|
|
155
|
-
const s=src();
|
|
156
|
-
// A descriptor-selected read is independent of any invoking/source home.
|
|
157
|
-
// In particular, retired sources must not leave caches in context/repo.
|
|
158
|
-
const target=join(flags.source?join(dirname(s.file),'views'):home,`knowledge-view-${randomUUID()}`);
|
|
159
|
-
const receipts=views(s.bindings,s.decl,target);
|
|
160
|
-
if(event==='refresh') result={path:target,receipts};
|
|
161
|
-
else {
|
|
162
|
-
if(!Object.hasOwn(s.bindings.bases,flags.base || '')) fail('E_CONFIG','unknown --base');
|
|
163
|
-
const basePath=join(target,receipts[flags.base].path);
|
|
164
|
-
const p=safePath(join(basePath,flags.path || 'index.md'));
|
|
165
|
-
if(!p.startsWith(basePath+'/') || !p.endsWith('.md')) fail('E_PATH','read only contained Markdown');
|
|
166
|
-
result={path:p,text:fs.readFileSync(p,'utf8'),receipt:receipts[flags.base]};
|
|
167
|
-
}
|
|
168
189
|
} else if(event==='init') result=initBase(loadBindings(),flags.base,flags.nodes,flags.output,{confirm:!!flags.confirm});
|
|
169
190
|
else if(event==='migrate') {
|
|
170
191
|
if(flags['source-home']) result=migrateSource(loadBindings(),flags['source-home']);
|
|
@@ -176,6 +197,10 @@ else {
|
|
|
176
197
|
else fail('E_USAGE',`unknown command ${event}; see --help`);
|
|
177
198
|
answer=hook?result:{schemaVersion:1,ok:true,result};
|
|
178
199
|
} catch(e) {const code=e.code || 'E_OKF';exit=1;answer=hook?{meta:{...(event==='retire'?{retired:false,reason:e.message}:{})},warning:`oats-okf ${code}: ${e.message}`}:{schemaVersion:1,ok:false,error:{code,message:e.message}};}
|
|
179
|
-
//
|
|
180
|
-
process.
|
|
200
|
+
// Consult commands print text unless --json; every other answer is JSON.
|
|
201
|
+
// Let Node drain the pipe; no process.exit after a possibly large answer.
|
|
202
|
+
if(textMode && exit) process.stderr.write(`oats okf ${event}: ${answer.error.code}: ${answer.error.message}\n`);
|
|
203
|
+
else if(textMode) process.stdout.write(text+'\n');
|
|
204
|
+
else process.stdout.write(JSON.stringify(answer)+'\n');
|
|
205
|
+
process.exitCode=exit;
|
|
181
206
|
}
|
|
@@ -1,34 +1,42 @@
|
|
|
1
1
|
## Knowledge: OKF
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
when resuming. Follow only links relevant to the task: do not bulk-load bases.
|
|
7
|
-
Each base is one link namespace: `/node/concept.md` resolves from that base's
|
|
8
|
-
root, not the filesystem root. All configured bases are discoverable; owns
|
|
9
|
-
means responsibility and reads means starting context, neither is an ACL.
|
|
3
|
+
You have two kinds of knowledge. Work WITH both: consult them at the start of
|
|
4
|
+
every task, after compaction, and every so often while you work, to decide,
|
|
5
|
+
to situate the task and to stay coherent with what your soul already knows.
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
reads; do not create an empty substitute.
|
|
7
|
+
- **Soul knowledge**: your soul's accepted OKF bases, external to the soul and
|
|
8
|
+
read remotely at their accepted state (there is no local copy).
|
|
9
|
+
**Consultation** reads them with the `oats okf` CLI from your instance home.
|
|
10
|
+
Load the **okf-consultation** skill at the start of every task.
|
|
11
|
+
- **Instance knowledge**: this instance's own STATE.md, log.md and notes/ in
|
|
12
|
+
instance home. Load the **okf-instance-knowledge** skill: it teaches what is
|
|
13
|
+
worth capturing, not only where to put it.
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
The work mode:
|
|
16
|
+
- **At task start and after compaction:** read STATE.md, recent log.md
|
|
17
|
+
entries and the relevant notes/, then `oats okf index` and
|
|
18
|
+
`oats okf cat --base ALIAS PATH` for the concepts the task needs. Follow
|
|
19
|
+
links; do not bulk-load.
|
|
20
|
+
- **Before compaction and before a task boundary:** update STATE.md, log.md
|
|
21
|
+
and notes/ first, so your future self can continue.
|
|
22
|
+
- **Every so often while working, and always before a design decision or
|
|
23
|
+
before re-deriving something:** `oats okf search` / `cat` the relevant
|
|
24
|
+
concepts and re-read your own notes. Consult prior decisions before
|
|
25
|
+
re-deriving them.
|
|
26
|
+
- Cite what you relied on as `alias/node/concept.md@<short-oid>`. A Git PR is
|
|
27
|
+
not accepted knowledge until it is merged. Report missing configuration or
|
|
28
|
+
blocked reads; do not create a substitute.
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
**Capture with judgment**, as it happens: decisions and why, rejected
|
|
31
|
+
alternatives, costly discoveries, limitations and the workaround that worked,
|
|
32
|
+
conclusions, blockers, human direction and corrections, and surprises that
|
|
33
|
+
contradict soul knowledge (flag those as candidate supersessions). Not code
|
|
34
|
+
descriptions, command logs, retries, secrets, verbatim third-party messages,
|
|
35
|
+
or what the tracker and docs already hold. One concept per note in notes/;
|
|
36
|
+
STATE.md is the current picture (rewritten); log.md is dated events
|
|
37
|
+
(append-only). The knowledge harvester, not you, decides what is promoted.
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
**Never write accepted knowledge or soul knowledge.** This is an instruction
|
|
40
|
+
boundary, not a filesystem sandbox. Skills remain curated soul artifacts.
|
|
41
|
+
Instance knowledge lives in instance home, not ./work; code and repository
|
|
42
|
+
documentation remain the truth about code.
|
|
@@ -115,7 +115,10 @@ function contract(value,{required=false}={}) {
|
|
|
115
115
|
return value;
|
|
116
116
|
}
|
|
117
117
|
function runtimeSettings(settings) {
|
|
118
|
-
|
|
118
|
+
// `harvest` (on|off) is read by the lifecycle hooks, not bound: accept it here
|
|
119
|
+
// so a deployment can set it, and refuse any other value.
|
|
120
|
+
keys(settings,['bindings-file','state-dir','harvest-runtime','harvest-model','harvest'],[], 'OKF settings');
|
|
121
|
+
if(settings.harvest!==undefined && !['on','off'].includes(settings.harvest)) wireError('invalid-binding');
|
|
119
122
|
const descriptorFile=settings['bindings-file'],stateDir=settings['state-dir'],runtime=settings['harvest-runtime'],model=settings['harvest-model'] ?? null;
|
|
120
123
|
for(const name of ['bindings-file','state-dir']) {
|
|
121
124
|
if(settings[name]===undefined) settingError(`${name}:missing`);
|
|
@@ -7,8 +7,10 @@ function keys(value, allowed, label, code='E_CONFIG') {
|
|
|
7
7
|
}
|
|
8
8
|
export function settings() {
|
|
9
9
|
const s = JSON.parse(process.env.OATS_SETTINGS || '{}');
|
|
10
|
-
keys(s,['bindings-file','state-dir','harvest-runtime','harvest-model','git-timeout'],'OATS_SETTINGS');
|
|
10
|
+
keys(s,['bindings-file','state-dir','harvest-runtime','harvest-model','git-timeout','consult-max-age','harvest'],'OATS_SETTINGS');
|
|
11
|
+
if(s.harvest!==undefined && !['on','off'].includes(s.harvest)) fail('E_CONFIG','harvest must be on or off');
|
|
11
12
|
if(s['git-timeout']!==undefined && (!Number.isInteger(s['git-timeout']) || s['git-timeout']<1)) fail('E_CONFIG','git-timeout must be a positive integer number of seconds');
|
|
13
|
+
if(s['consult-max-age']!==undefined && (!Number.isInteger(s['consult-max-age']) || s['consult-max-age']<0)) fail('E_CONFIG','consult-max-age must be a non-negative integer number of seconds');
|
|
12
14
|
if(s['state-dir']!==undefined && (typeof s['state-dir']!=='string' || !isAbsolute(s['state-dir']) || resolve(s['state-dir'])!==s['state-dir'])) fail('E_CONFIG','state-dir must be a normalized absolute path');
|
|
13
15
|
if(s['harvest-runtime']!==undefined && !['pi','claude','codex'].includes(s['harvest-runtime'])) fail('E_CONFIG','invalid harvest-runtime');
|
|
14
16
|
if(s['harvest-model']!==undefined && (typeof s['harvest-model']!=='string' || !s['harvest-model'].trim())) fail('E_CONFIG','harvest-model must be a nonempty string');
|
|
@@ -17,6 +19,9 @@ export function settings() {
|
|
|
17
19
|
/** Time budget for Git operations that talk to a remote (clone, fetch, push,
|
|
18
20
|
* ls-remote). Local object reads keep the short exec default. */
|
|
19
21
|
export function gitTimeoutMs() { return (settings()['git-timeout'] ?? 600)*1000; }
|
|
22
|
+
/** How old (seconds) the host cache's accepted commit may be before a consult
|
|
23
|
+
* read refetches the accepted branch; 0 fetches on every read. */
|
|
24
|
+
export function consultMaxAgeMs() { return (settings()['consult-max-age'] ?? 300)*1000; }
|
|
20
25
|
export function noGit(path) {
|
|
21
26
|
for (let p = safePath(path); ; p = dirname(p)) {
|
|
22
27
|
if (fs.existsSync(join(p, '.git')) || (fs.existsSync(join(p, 'HEAD')) && fs.existsSync(join(p, 'objects')) && fs.existsSync(join(p, 'refs')))) fail('E_DIRECTORY_GIT', `directory store is in Git custody: ${p}; use kind git`);
|