@lmzhen/dsh-evolution-core 0.26.26 → 0.27.1
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 +16 -1
- package/lib/composition/index.js +3 -0
- package/lib/composition-D8Y3-ROd.js +604 -0
- package/lib/ensure-MEYMj9jn.js +222 -0
- package/lib/errors-Br2-uUSK.js +57 -0
- package/lib/http-routes-BP-S3KV-.js +149 -0
- package/lib/http-routes.js +2 -0
- package/lib/index.js +66 -850
- package/lib/restrictor.js +37 -0
- package/lib/subagent/index.js +186 -0
- package/lib/types/citations.js +192 -0
- package/lib/types/{preset-composition.d.ts → composition/composer.d.ts} +1 -1
- package/lib/types/composition/composer.js +681 -0
- package/lib/types/composition/ensure.d.ts +10 -0
- package/lib/types/composition/ensure.js +95 -0
- package/lib/types/composition/index.d.ts +5 -0
- package/lib/types/composition/index.js +7 -0
- package/lib/types/composition/preset-ids.d.ts +17 -0
- package/lib/types/composition/preset-ids.js +21 -0
- package/lib/types/composition/restricted-preset.d.ts +36 -0
- package/lib/types/composition/restricted-preset.js +42 -0
- package/lib/types/constants.js +223 -0
- package/lib/types/cost.js +67 -0
- package/lib/types/curator.js +346 -0
- package/lib/types/diagnostics.d.ts +26 -0
- package/lib/types/diagnostics.js +23 -0
- package/lib/types/document-body.js +91 -0
- package/lib/types/drift-signals.js +319 -0
- package/lib/types/env.js +25 -0
- package/lib/types/errors.d.ts +4 -1
- package/lib/types/errors.js +59 -0
- package/lib/types/events.js +16 -0
- package/lib/types/evidence.js +65 -0
- package/lib/types/evolution-events.js +479 -0
- package/lib/types/fold-untrusted.js +44 -0
- package/lib/types/frontmatter.js +623 -0
- package/lib/types/fuzzy-match.js +135 -0
- package/lib/types/gates.js +51 -0
- package/lib/types/guards.js +27 -0
- package/lib/types/health-baseline.js +63 -0
- package/lib/types/http-routes.d.ts +67 -35
- package/lib/types/http-routes.js +135 -0
- package/lib/types/index.d.ts +7 -2
- package/lib/types/index.js +101 -0
- package/lib/types/instance-scope.js +69 -0
- package/lib/types/io.js +675 -0
- package/lib/types/learn-prompt.js +42 -0
- package/lib/types/limits.js +63 -0
- package/lib/types/live.js +107 -0
- package/lib/types/long-runs.js +96 -0
- package/lib/types/memory-store.d.ts +0 -16
- package/lib/types/memory-store.js +606 -0
- package/lib/types/mutations.js +95 -0
- package/lib/types/namespace-migration.js +156 -0
- package/lib/types/numeric.js +47 -0
- package/lib/types/opt-in.js +101 -0
- package/lib/types/package-identity.js +47 -0
- package/lib/types/params.js +328 -0
- package/lib/types/payload.js +36 -0
- package/lib/types/platform-line.generated.d.ts +10 -0
- package/lib/types/platform-line.generated.js +12 -0
- package/lib/types/platform-services.d.ts +2 -2
- package/lib/types/platform-services.js +52 -0
- package/lib/types/probe.js +122 -0
- package/lib/types/prompts.js +409 -0
- package/lib/types/quality.js +308 -0
- package/lib/types/reachability.js +122 -0
- package/lib/types/redact.js +257 -0
- package/lib/types/reference-rewrite.js +144 -0
- package/lib/types/reports.js +110 -0
- package/lib/types/restrictor.d.ts +11 -0
- package/lib/types/restrictor.js +48 -0
- package/lib/types/review-channel.js +59 -0
- package/lib/types/run-registry.js +345 -0
- package/lib/types/scope.js +7 -0
- package/lib/types/serial.js +17 -0
- package/lib/types/session-fold.d.ts +15 -0
- package/lib/types/session-fold.js +28 -0
- package/lib/types/session-projection.js +295 -0
- package/lib/types/signals.js +209 -0
- package/lib/types/skill-discovery.js +66 -0
- package/lib/types/skill-health.js +134 -0
- package/lib/types/skill-history.js +745 -0
- package/lib/types/skill-lint.js +112 -0
- package/lib/types/skill-reads.js +67 -0
- package/lib/types/skill-row-cells.js +123 -0
- package/lib/types/skill-store.js +3528 -0
- package/lib/types/state-store.js +60 -0
- package/lib/types/store-ledger.js +117 -0
- package/lib/types/subagent/channel.d.ts +4 -0
- package/lib/types/subagent/channel.js +131 -0
- package/lib/types/subagent/index.d.ts +4 -0
- package/lib/types/subagent/index.js +5 -0
- package/lib/types/subagent/readiness.d.ts +6 -0
- package/lib/types/subagent/readiness.js +35 -0
- package/lib/types/subagent/types.d.ts +58 -0
- package/lib/types/subagent/types.js +2 -0
- package/lib/types/threats.js +338 -0
- package/lib/types/tool-dispatch.d.ts +5 -0
- package/lib/types/tool-dispatch.js +535 -0
- package/lib/types/tree-write.js +40 -0
- package/lib/types/unit-members.js +121 -0
- package/lib/types/usage.js +474 -0
- package/lib/types/write-authority.js +23 -0
- package/lib/types/write-gates.js +344 -0
- package/lib/types/write-inventory.js +158 -0
- package/package.json +39 -11
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* skill-lint — the FORM of a judgement, not a second judgement (G1, contract §3/§3b/§4, D1).
|
|
3
|
+
*
|
|
4
|
+
* The engine that DECIDES (token-scale line, /1024 stamp density, soft/2 support threshold) is
|
|
5
|
+
* `skill-health.ts`; this module only re-expresses the facts it already computed as findings, so a
|
|
6
|
+
* conclusion carries a rule id, a severity and a count instead of free text. Severity is declared
|
|
7
|
+
* HERE, per rule — never scored by a model, never derived from statistics — so the same facts always
|
|
8
|
+
* produce the same findings, on any machine, which is what lets CI replay a conclusion.
|
|
9
|
+
*
|
|
10
|
+
* Pure: no IO, no ctx, no thresholds of its own.
|
|
11
|
+
*/
|
|
12
|
+
import { SUPPORT_SPRAWL_FILES } from "./constants.js";
|
|
13
|
+
const msg = (parts) => parts.map(p => (typeof p === 'string' ? p : String(p))).join('');
|
|
14
|
+
/** One rule yields AT MOST one finding (contract §3). `body-oversize-2x` is the only rule whose
|
|
15
|
+
* severity can escalate a verdict to needs-restructure. */
|
|
16
|
+
export const RULES = [
|
|
17
|
+
{
|
|
18
|
+
id: 'unreadable-skill',
|
|
19
|
+
severity: 'error',
|
|
20
|
+
run: facts => facts.readable && facts.dims.bodyChars !== null
|
|
21
|
+
? null
|
|
22
|
+
: { severity: 'error', rule: 'unreadable-skill', message: msg([facts.skillName, ': SKILL.md could not be read — no health claim is possible']), count: 1 },
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'body-oversize-2x',
|
|
26
|
+
severity: 'error',
|
|
27
|
+
run: facts => facts.overTwiceSoftLine
|
|
28
|
+
? { severity: 'error', rule: 'body-oversize-2x', message: msg([facts.skillName, ': body is at or past twice the soft line (', facts.softLineChars, ' chars)']), count: 1 }
|
|
29
|
+
: null,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'body-oversize',
|
|
33
|
+
severity: 'warn',
|
|
34
|
+
run: facts => !facts.overTwiceSoftLine && facts.overSoftLine
|
|
35
|
+
? { severity: 'warn', rule: 'body-oversize', message: msg([facts.skillName, ': body is at or above the soft line (', facts.softLineChars, ' chars)']), count: 1 }
|
|
36
|
+
: null,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'stamp-density',
|
|
40
|
+
severity: 'warn',
|
|
41
|
+
run: (facts) => {
|
|
42
|
+
const density = facts.dims.stampDensityPerKb;
|
|
43
|
+
const hits = facts.dims.stampHits;
|
|
44
|
+
if (density === null || hits === null || density < facts.stampLimitPerKb)
|
|
45
|
+
return null;
|
|
46
|
+
return { severity: 'warn', rule: 'stamp-density', message: msg([facts.skillName, ': stamp density ', density.toFixed(1), '/KB (rc/sha/date lines — log-like content)']), count: hits };
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'body-without-support',
|
|
51
|
+
severity: 'warn',
|
|
52
|
+
run: facts => facts.dims.supportGroups === 0 && facts.dims.bodyChars !== null && facts.dims.bodyChars >= facts.softLineChars / 2
|
|
53
|
+
? { severity: 'warn', rule: 'body-without-support', message: msg([facts.skillName, ': large body (', facts.dims.bodyChars, ' chars) with NO support files']), count: 1 }
|
|
54
|
+
: null,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'references-sprawl',
|
|
58
|
+
severity: 'warn',
|
|
59
|
+
run: (facts) => {
|
|
60
|
+
const files = facts.dims.supportFiles;
|
|
61
|
+
if (files === null || files < SUPPORT_SPRAWL_FILES)
|
|
62
|
+
return null;
|
|
63
|
+
return { severity: 'warn', rule: 'references-sprawl', message: msg([facts.skillName, ': ', files, ' support files']), count: files };
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'write-ghost',
|
|
68
|
+
severity: 'warn',
|
|
69
|
+
run: (facts) => {
|
|
70
|
+
const patches = facts.dims.churnPatches;
|
|
71
|
+
const reads = facts.dims.churnReads;
|
|
72
|
+
if (patches === null || reads === null || patches < facts.churnMinPatches || reads > 0)
|
|
73
|
+
return null;
|
|
74
|
+
return { severity: 'warn', rule: 'write-ghost', message: msg([facts.skillName, ': ', patches, ' patch(es), never read']), count: patches };
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
export function lintSkill(facts) {
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const rule of RULES) {
|
|
81
|
+
const hit = rule.run(facts);
|
|
82
|
+
if (hit !== null)
|
|
83
|
+
out.push(hit);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/** What a write introduced, what got worse, what got fixed. A rule whose count grew (one site to
|
|
88
|
+
* nine) counts as worsened — comparing by rule name alone reported nothing. */
|
|
89
|
+
export function diffFindings(before, after) {
|
|
90
|
+
const beforeByRule = new Map(before.map(f => [f.rule, f]));
|
|
91
|
+
const afterByRule = new Map(after.map(f => [f.rule, f]));
|
|
92
|
+
const introduced = after.filter(f => !beforeByRule.has(f.rule));
|
|
93
|
+
const worsened = after.filter((f) => {
|
|
94
|
+
const old = beforeByRule.get(f.rule);
|
|
95
|
+
return old !== undefined && f.count > old.count;
|
|
96
|
+
});
|
|
97
|
+
const resolved = before.filter(f => !afterByRule.has(f.rule)).map(f => f.rule);
|
|
98
|
+
return { introduced, worsened, resolved };
|
|
99
|
+
}
|
|
100
|
+
/** Support files = markdown files under references/ (contract §4: unit frozen, underscore-prefixed
|
|
101
|
+
* excluded). Paths are supplied by the caller — this module never touches the filesystem. */
|
|
102
|
+
export function countReferenceFiles(paths) {
|
|
103
|
+
return paths.filter((p) => {
|
|
104
|
+
const n = p.replace(/\\/g, '/');
|
|
105
|
+
const inReferences = n.includes('/references/') || n.startsWith('references/');
|
|
106
|
+
if (!inReferences || !n.endsWith('.md'))
|
|
107
|
+
return false;
|
|
108
|
+
const base = n.slice(n.lastIndexOf('/') + 1);
|
|
109
|
+
return !base.startsWith('_');
|
|
110
|
+
}).length;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=skill-lint.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { collectReadSkillNames } from "./tool-dispatch.js";
|
|
2
|
+
import { sessionReadNames } from "./session-projection.js";
|
|
3
|
+
/**
|
|
4
|
+
* The skill names ONE session read, or `undefined` when its log is not readable.
|
|
5
|
+
*
|
|
6
|
+
* Two readers, in this order: the family's `evolutionReads` projection for a platform `Session`
|
|
7
|
+
* (0.2.x's live read — the deprecated synchronous log read is gone from production), then the
|
|
8
|
+
* STRUCTURAL accessor for a view that is not a `Session` at all. The tool path's exec view may be a
|
|
9
|
+
* bare stub, which is what this module exists for; a real Session always takes the first path.
|
|
10
|
+
*
|
|
11
|
+
* `undefined` is NOT an empty set: a caller that must decide (the tool path's read-before-write
|
|
12
|
+
* gate) can then keep its previous behavior instead of refusing every write in a composition whose
|
|
13
|
+
* session objects expose no log at all.
|
|
14
|
+
* @param ctx - a context of the runtime.
|
|
15
|
+
* @param session - the session whose reads are wanted; `undefined` when the call carries none.
|
|
16
|
+
* @returns the names read through a non-failed `skill` dispatch, or `undefined` when unreadable.
|
|
17
|
+
*/
|
|
18
|
+
export function sessionReadSkillNames(ctx, session) {
|
|
19
|
+
if (session === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
const projected = sessionReadNames(ctx, session);
|
|
22
|
+
if (projected !== undefined)
|
|
23
|
+
return projected;
|
|
24
|
+
const events = session.readEvents?.();
|
|
25
|
+
return events === undefined ? undefined : collectReadSkillNames(events);
|
|
26
|
+
}
|
|
27
|
+
/** Actions whose target must have been read first (create is authorship, so it is exempt). */
|
|
28
|
+
export const READ_REQUIRED_ACTIONS = Object.freeze([
|
|
29
|
+
'edit', 'update', 'patch', 'delete', 'write_file', 'remove_file', 'restructure',
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* Does this one op write a skill the caller never read?
|
|
33
|
+
*
|
|
34
|
+
* V6-26 (0.3.37): the validator normalizes a missing action to `patch`, so a missing action is a
|
|
35
|
+
* read-required write, never silently exempt.
|
|
36
|
+
* @param op - one skill operation (from a tool call or a review plan).
|
|
37
|
+
* @param readNames - names read this session, from {@link collectReadSkillNames}.
|
|
38
|
+
* @returns true when the op is a read-required write to an unread target.
|
|
39
|
+
*/
|
|
40
|
+
export function isUnreadWrite(op, readNames) {
|
|
41
|
+
if (!op.name)
|
|
42
|
+
return false;
|
|
43
|
+
return READ_REQUIRED_ACTIONS.includes(op.action ?? 'patch') && !readNames.has(op.name);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Drop mutating ops whose target was not read this session, in place.
|
|
47
|
+
*
|
|
48
|
+
* Covers the same mutating surface Hermes guards (edit/patch/write_file/remove_file), so a
|
|
49
|
+
* background review cannot blind-touch support files or edits of skills it never loaded.
|
|
50
|
+
* @param ops - the plan's ops; dropped entries are removed in place.
|
|
51
|
+
* @param readNames - names read this session.
|
|
52
|
+
* @returns the count of dropped ops, so the plan event can report them as rejected.
|
|
53
|
+
*/
|
|
54
|
+
export function filterUnreadSkillOps(ops, readNames) {
|
|
55
|
+
let dropped = 0;
|
|
56
|
+
for (let index = ops.length - 1; index >= 0; index -= 1) {
|
|
57
|
+
const op = ops[index];
|
|
58
|
+
if (!op)
|
|
59
|
+
continue;
|
|
60
|
+
if (isUnreadWrite(op, readNames)) {
|
|
61
|
+
ops.splice(index, 1);
|
|
62
|
+
dropped += 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return dropped;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=skill-reads.js.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cells one row shows, decided here and rendered by the client.
|
|
3
|
+
*
|
|
4
|
+
* Why it exists: until 0.14.1 a row's information architecture lived inside one JSX expression
|
|
5
|
+
* (description + ' · ' + state + ' · ' + age), so which facts survived the browser's single-line
|
|
6
|
+
* ellipsis was an accident of concatenation order — on a real library the skill row's age and
|
|
7
|
+
* curation state were clipped away on EVERY row (measured 2026-09-28: 6 skills, descriptions
|
|
8
|
+
* 49–61 characters, ~240px of line). A row now arrives as an ORDERED list of cells, each declaring
|
|
9
|
+
* its slot, and the renderer lays them out: `aside` cells are right-aligned and never truncate, so a
|
|
10
|
+
* decision fact cannot be eaten by a long description again.
|
|
11
|
+
*
|
|
12
|
+
* Invariants: no words live here. A cell carries either a locale key (`copy`, with the values its
|
|
13
|
+
* template needs) or a verbatim fact (`literal` — numbers, hashes, recorded paths). Facts come from
|
|
14
|
+
* the host; the dictionary owns every sentence and every number format, including the ones the panel
|
|
15
|
+
* used to build itself (`row.delta.up`, `time.<unit>.one`, `versions.count`).
|
|
16
|
+
* @module @lmzhen/dsh-evolution-core/skill-row-cells
|
|
17
|
+
*/
|
|
18
|
+
/** The actions whose row carries an explanatory hover text. */
|
|
19
|
+
const ACTION_NOTES = {
|
|
20
|
+
delete: 'action.delete.note',
|
|
21
|
+
archive: 'action.archive.note',
|
|
22
|
+
};
|
|
23
|
+
/** The actions whose bytes come from a support file rather than from SKILL.md. */
|
|
24
|
+
const SUPPORT_ACTIONS = ['support-write', 'support-remove'];
|
|
25
|
+
/**
|
|
26
|
+
* The relative-time key for one bucket: a count of one is spelled on its own.
|
|
27
|
+
* @param age - the bucket the host reported.
|
|
28
|
+
* @returns the locale key, singular or plural.
|
|
29
|
+
*/
|
|
30
|
+
export function ageCopyKey(age) {
|
|
31
|
+
// 'now' is its own sentence: there is no count to spell, and no `time.now.one`.
|
|
32
|
+
if (age.unit === 'now')
|
|
33
|
+
return 'time.now';
|
|
34
|
+
return age.n === 1 ? 'time.' + age.unit + '.one' : 'time.' + age.unit;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The state word one skill row carries, from the facts the listing reported.
|
|
38
|
+
* @param skill - the row's facts.
|
|
39
|
+
* @returns the locale key for its state.
|
|
40
|
+
*/
|
|
41
|
+
export function skillStateKey(skill) {
|
|
42
|
+
if (skill.protectionUnknown)
|
|
43
|
+
return 'state.unknown';
|
|
44
|
+
if (skill.protectedBy !== null)
|
|
45
|
+
return 'state.protected';
|
|
46
|
+
return skill.managed ? 'state.managed' : 'state.foreign';
|
|
47
|
+
}
|
|
48
|
+
/** The hover text one action word needs, or nothing when it needs none. */
|
|
49
|
+
function actionNote(actionKind) {
|
|
50
|
+
const note = ACTION_NOTES[actionKind];
|
|
51
|
+
return note === undefined ? {} : { note };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The cells of one version row: the facts on the title line, the summary (if any) under it.
|
|
55
|
+
* @param row - the row's facts.
|
|
56
|
+
* @returns both lines.
|
|
57
|
+
*/
|
|
58
|
+
export function versionRowCells(row) {
|
|
59
|
+
const title = [
|
|
60
|
+
{ key: 'v', copy: 'row.version', values: { n: row.v }, tone: 'tertiary', slot: 'meta' },
|
|
61
|
+
{ key: 'action', copy: 'action.' + row.actionKind, tone: 'primary', slot: 'lead', ...actionNote(row.actionKind) },
|
|
62
|
+
{ key: 'time', copy: ageCopyKey(row.age), values: { n: row.age.n }, tone: 'tertiary', slot: 'meta' },
|
|
63
|
+
];
|
|
64
|
+
if (row.path !== undefined) {
|
|
65
|
+
title.push({ key: 'path', literal: row.path, tone: 'tertiary', slot: 'meta', note: row.path });
|
|
66
|
+
}
|
|
67
|
+
else if (SUPPORT_ACTIONS.includes(row.actionKind)) {
|
|
68
|
+
title.push({ key: 'path', copy: 'path.unrecorded', tone: 'tertiary', slot: 'meta' });
|
|
69
|
+
}
|
|
70
|
+
const delta = row.charsDelta;
|
|
71
|
+
if (delta !== undefined && delta !== 0) {
|
|
72
|
+
title.push({
|
|
73
|
+
key: 'delta',
|
|
74
|
+
copy: delta > 0 ? 'row.delta.up' : 'row.delta.down',
|
|
75
|
+
values: { n: Math.abs(delta) },
|
|
76
|
+
tone: 'tertiary',
|
|
77
|
+
slot: 'meta',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// U-5: the recorded hash, as a SHORT code (seven chars) whose hover carries the full value — the
|
|
81
|
+
// platform already puts `hash` on the row; nothing rendered it, so the fact was declared and unused.
|
|
82
|
+
if (row.hash !== undefined && row.hash !== '') {
|
|
83
|
+
title.push({ key: 'hash', literal: row.hash.slice(0, 7), tone: 'tertiary', slot: 'meta', note: row.hash });
|
|
84
|
+
}
|
|
85
|
+
const meta = [];
|
|
86
|
+
// The summary is the one line a deployment may or may not generate; the host sends it already
|
|
87
|
+
// verbatim, so it needs no dictionary key.
|
|
88
|
+
if (row.summary !== undefined && row.summary !== '') {
|
|
89
|
+
meta.push({ key: 'summary', literal: row.summary, tone: 'secondary', slot: 'lead' });
|
|
90
|
+
}
|
|
91
|
+
return { title, meta };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The cells of one skill row: the name line, then the facts that describe it.
|
|
95
|
+
*
|
|
96
|
+
* The description is a `lead` cell (it is the one allowed to truncate, with the full text on hover),
|
|
97
|
+
* while the version count and the last-changed time are `aside` cells — they survive any description.
|
|
98
|
+
* The description is NOT pre-truncated in JavaScript: the browser's ellipsis already clips it at the
|
|
99
|
+
* layout's real width, and a character cut on top of that only hides where the fact ends.
|
|
100
|
+
* @param skill - the row's facts.
|
|
101
|
+
* @returns both lines.
|
|
102
|
+
*/
|
|
103
|
+
export function skillRowCells(skill) {
|
|
104
|
+
const title = [
|
|
105
|
+
{ key: 'name', literal: skill.name, tone: 'primary', slot: 'lead', note: skill.name },
|
|
106
|
+
{ key: 'count', copy: 'versions.count', values: { n: skill.versions }, tone: 'tertiary', slot: 'aside' },
|
|
107
|
+
];
|
|
108
|
+
const meta = [
|
|
109
|
+
{ key: 'description', literal: skill.description, tone: 'tertiary', slot: 'lead', note: skill.description },
|
|
110
|
+
{ key: 'state', copy: skillStateKey(skill), tone: 'tertiary', slot: 'meta' },
|
|
111
|
+
];
|
|
112
|
+
// U-5 (history half): a protected skill says WHO holds it, not only that it is held. The state cell
|
|
113
|
+
// above answers 'protected or not' from the same field; this cell carries the guard's own name, a
|
|
114
|
+
// verbatim fact (no dictionary key — literals are values the host owns).
|
|
115
|
+
if (skill.protectedBy !== null) {
|
|
116
|
+
meta.push({ key: 'protectedBy', literal: skill.protectedBy, tone: 'tertiary', slot: 'meta', note: skill.protectedBy });
|
|
117
|
+
}
|
|
118
|
+
if (skill.age !== null) {
|
|
119
|
+
meta.push({ key: 'age', copy: ageCopyKey(skill.age), values: { n: skill.age.n }, tone: 'tertiary', slot: 'aside' });
|
|
120
|
+
}
|
|
121
|
+
return { title, meta };
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=skill-row-cells.js.map
|