mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-07-06 18:52:03 +08:00
refactor(skills): retire nc:env-sync — the data/env/env mirror is dead
Upstream removed every setup-side writer of data/env/env (c82f062d) because
nothing has read it since the container mount was dropped — before this
branch even forked. Our engine codified the dead pattern as a directive:
every apply copied the full .env (live tokens included) into a file nothing
consumes.
- engine: env-sync handler removed from selfStatus + applyOne
- grammar: dropped from KNOWN; a new RETIRED table gives a targeted lint
error ("delete the fence, the adapter reads .env directly") instead of a
generic unknown-directive message
- skills: fence stripped from the 14 converted skills; orphaned "sync to the
container" prose cleaned (incl. add-deltachat/add-wechat old-format prose
upstream's sweep missed); teams troubleshooting entry rewritten
- policy/spec doc lists updated
Suite 827 passed | 1 skipped; all nc: skills lint clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -142,8 +142,6 @@ function selfStatus(d: Directive, root: string): { status: StepStatus; detail: s
|
||||
? { status: 'apply', detail: `set ${missing.join(', ')} in .env` }
|
||||
: { status: 'skip', detail: `${keys.join(', ')} already set` };
|
||||
}
|
||||
case 'env-sync':
|
||||
return { status: 'apply', detail: 'sync .env → data/env/env' };
|
||||
case 'json-merge': {
|
||||
const into = String(d.attrs.into ?? '');
|
||||
const key = String(d.attrs.key ?? '');
|
||||
@@ -668,10 +666,6 @@ async function applyOne(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'env-sync':
|
||||
mkdirSync(join(root, 'data/env'), { recursive: true });
|
||||
copyFileSync(join(root, '.env'), join(root, 'data/env/env'));
|
||||
break;
|
||||
case 'json-merge': {
|
||||
const into = String(d.attrs.into);
|
||||
const key = String(d.attrs.key);
|
||||
|
||||
@@ -19,7 +19,6 @@ describe('skill-directives parser, on the converted add-slack', () => {
|
||||
'prompt', // credentials: capture bot token
|
||||
'prompt', // credentials: capture signing secret
|
||||
'env-set', // credentials: write captured values to .env
|
||||
'env-sync', // credentials: sync to container
|
||||
'operator', // credentials: event-delivery walkthrough
|
||||
'prompt', // resolve: owner member id (owner_handle)
|
||||
'run', // resolve: validate token (auth.test) — fast-fail before the restart
|
||||
@@ -187,6 +186,15 @@ describe('append at:<marker> attribute', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('retired directives', () => {
|
||||
it('flags nc:env-sync with a targeted retirement error, not a generic unknown', () => {
|
||||
const probs = validate(parseDirectives(['```nc:env-sync', '```'].join('\n')));
|
||||
expect(probs).toHaveLength(1);
|
||||
expect(probs[0].message).toMatch(/retired/);
|
||||
expect(probs[0].message).toMatch(/data\/env\/env/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when: guard + multi-field capture', () => {
|
||||
it('parses when: into attrs and lints a guard whose var an earlier prompt defined', () => {
|
||||
const md = ['```nc:prompt mode', 'local or remote', '```', '```nc:prompt server_url when:mode=remote', 'url', '```'].join('\n');
|
||||
|
||||
@@ -68,7 +68,6 @@
|
||||
// pauses for confirmation before the next side effect is derived from
|
||||
// document structure (scripts/skill-policy.ts), never authored here.
|
||||
// env-set body: `KEY=value` ({{var}} allowed) set-if-absent
|
||||
// env-sync (no body) `.env` → data/env/env idempotent copy
|
||||
// json-merge into:<file> key:<field> body: a JSON object push-if-absent
|
||||
//
|
||||
// `append` without `at:` adds to EOF; with `at:<marker>` it inserts before the
|
||||
@@ -113,7 +112,12 @@ export interface Problem {
|
||||
const FENCE = /^```(\S.*)?$/;
|
||||
const EXACT_SEMVER = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
||||
const VAR_REF = /\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
|
||||
const KNOWN = new Set(['copy', 'append', 'dep', 'run', 'prompt', 'operator', 'env-set', 'env-sync', 'json-merge']);
|
||||
const KNOWN = new Set(['copy', 'append', 'dep', 'run', 'prompt', 'operator', 'env-set', 'json-merge']);
|
||||
// Retired directives get a targeted lint error (not just "unknown") so an
|
||||
// author knows the removal was deliberate and what to do instead.
|
||||
const RETIRED: Record<string, string> = {
|
||||
'env-sync': 'nc:env-sync was retired — nothing reads the data/env/env mirror (and it copied live tokens); delete the fence, the adapter reads .env directly',
|
||||
};
|
||||
const PROMPT_FLAGS = new Set(['secret']);
|
||||
|
||||
export function parseDirectives(markdown: string): Directive[] {
|
||||
@@ -203,7 +207,8 @@ export function validate(directives: Directive[], ctx?: { chatVersion?: string }
|
||||
const defined = new Set<string>();
|
||||
const flag = (d: Directive, message: string) => problems.push({ line: d.line, kind: d.kind, message });
|
||||
for (const d of directives) {
|
||||
if (!KNOWN.has(d.kind)) flag(d, `unknown directive nc:${d.kind}`);
|
||||
if (RETIRED[d.kind]) flag(d, RETIRED[d.kind]);
|
||||
else if (!KNOWN.has(d.kind)) flag(d, `unknown directive nc:${d.kind}`);
|
||||
switch (d.kind) {
|
||||
case 'dep':
|
||||
for (const spec of d.body) {
|
||||
|
||||
@@ -65,7 +65,7 @@ function guardOf(d: Directive): { v: string; value: string } | undefined {
|
||||
* 3. Next compatible directive is a `prompt` → no confirm (the prompt is the
|
||||
* barrier — the human can't paste a token before doing the steps).
|
||||
* 4. No such directive (end of document) → no confirm (a final handoff block).
|
||||
* 5. Anything else (`run`, `copy`, `dep`, `append`, `env-set`, `env-sync`,
|
||||
* 5. Anything else (`run`, `copy`, `dep`, `append`, `env-set`,
|
||||
* `json-merge`) → confirm, with the flavor derived from that barrier
|
||||
* directive's effect (`effect:step` → readiness, else completed).
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user