owfadev 0.4.1 → 0.4.2
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/.owfa/VERSION +1 -1
- package/.owfa/protocol/actions.md +4 -4
- package/.owfa/scripts/pipeline.py +7 -1
- package/package.json +1 -1
- package/payloads/personal/PKG.yaml +2 -2
- package/payloads/personal/calendar/PLUGIN.md +2 -1
- package/payloads/personal/command/owfa-map/SKILL.md +4 -4
- package/payloads/personal/command/owfa-profile/SKILL.md +4 -4
- package/payloads/personal/command/owfa-save/SKILL.md +5 -5
- package/payloads/personal/device/PLUGIN.md +2 -1
- package/payloads/personal/hot/PLUGIN.md +1 -0
- package/payloads/personal/hot/PLUGIN.yaml +3 -3
- package/payloads/personal/index/PLUGIN.md +1 -0
- package/payloads/personal/index/PLUGIN.yaml +3 -3
- package/payloads/personal/log/PLUGIN.md +1 -0
- package/payloads/personal/log/PLUGIN.yaml +5 -3
- package/payloads/personal/pipeline.py +441 -0
- package/payloads/personal/sessions/PLUGIN.md +1 -0
- package/payloads/personal/sessions/PLUGIN.yaml +3 -3
- package/payloads/personal/tmp/PLUGIN.md +2 -1
- package/payloads/personal/user-profile/PLUGIN.md +2 -1
package/.owfa/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.4.
|
|
1
|
+
0.4.2
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Action discipline: path zones × action tiers
|
|
2
2
|
|
|
3
|
-
> The semantic shape of permissions: this table does not rely on code for "enforcement", but deterministic structural operations are entrusted to scripts (`.owfa/scripts/owfa_kernel.py`: compliance / dependencies / injection / registry / copy sync; `.owfa/scripts/pipeline.py
|
|
4
|
-
> Four execution principles: deterministic structural operations go through scripts, semantic judgment through the LLM; rules spell out in plain text "how to do" rather than "what it is"; if one read suffices, there is no second scan (call frugality); write commands must close with
|
|
3
|
+
> The semantic shape of permissions: this table does not rely on code for "enforcement", but deterministic structural operations are entrusted to scripts (`.owfa/scripts/owfa_kernel.py`: compliance / dependencies / injection / registry / copy sync; the suite-provided pipeline, `.owfa/plugins/domains/personal/pipeline.py` (deprecated host copy `.owfa/scripts/pipeline.py` until 0.5.0): index / tags rebuild, hot / log rolling, post-write self-attestation), while semantic judgment is entrusted to the LLM; the agent consults this table before acting, and check audits afterwards from script output and git traces.
|
|
4
|
+
> Four execution principles: deterministic structural operations go through scripts, semantic judgment through the LLM; rules spell out in plain text "how to do" rather than "what it is"; if one read suffices, there is no second scan (call frugality); write commands must close with the pipeline's `verify` self-attestation (the minimal attester form: the LLM executes, the script attests; the pipeline is suite machinery — with the personal suite since 0.1.1).
|
|
5
5
|
|
|
6
6
|
## Path zones
|
|
7
7
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
| Tier | Typical actions | How executed |
|
|
19
19
|
|---|---|---|
|
|
20
|
-
| Mechanical-auto | index / tags / registry rebuild, injection-region rebuild, hot window eviction, log archival routing, hash recalculation, query log lines, command copy sync, post-write self-attestation | Execute directly (whatever scripts can do goes through scripts: `owfa_kernel.py` and
|
|
20
|
+
| Mechanical-auto | index / tags / registry rebuild, injection-region rebuild, hot window eviction, log archival routing, hash recalculation, query log lines, command copy sync, post-write self-attestation | Execute directly (whatever scripts can do goes through scripts: `owfa_kernel.py` (framework) and the suite pipeline), no asking |
|
|
21
21
|
| Mechanical-confirm | tag merges (batch edits to plugin fields), reserved-field migration, tmp over-age cleanup (present list, delete after confirmation) | Present the list, execute after confirmation |
|
|
22
22
|
| Semantic-report | note-merge suggestions, near-duplicate alerts, staleness judgments on proxy descriptions | Report the differences; the human decides |
|
|
23
23
|
| Always-human | deletion, history rewriting, changes to existing `vault/` files, rewriting note bodies | Commands never execute these; ghost-writing on explicit user instruction must leave a log trace |
|
|
@@ -39,4 +39,4 @@ The criterion: touching "plugin-owned fields or regenerable pages" → mechanica
|
|
|
39
39
|
- **hot**: ≤25 entries, <5 days, ≤200 characters per entry. Write hot entries via `pipeline.py hot <type> "<one-liner>"`—out-of-window entries are evicted and over-long entries truncated automatically before writing
|
|
40
40
|
- **Index / registry**: on deviation from reality → rebuild directly inside check (`pipeline.py index` / `tags`; `owfa_kernel.py all`), no asking
|
|
41
41
|
- **Vocabulary**: before writing tags, map / save first reads `wiki/tags.md` (the derived zone is the vocabulary), preferring to reuse existing words; near-duplicate merging is a mechanical-confirm item
|
|
42
|
-
- **Parameter ownership**: mechanical parameters such as rolling windows defer to the
|
|
42
|
+
- **Parameter ownership**: mechanical parameters such as rolling windows defer to the pipeline source (suite package root) (the script source is the rule list), while plugin PLUGIN.md files keep the semantic explanations—commands and docs do not re-copy the numbers
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
3
|
+
"""DEPRECATED HOST COPY — retires in 0.5.0. Since suite 0.1.1 the pipeline ships with
|
|
4
|
+
the personal suite at the package root (instance path:
|
|
5
|
+
.owfa/plugins/domains/personal/pipeline.py); the kernel never calls it. This
|
|
6
|
+
framework-tree copy is a one-version compatibility shim for reverse-order upgrades
|
|
7
|
+
(framework before suite). Do not edit here — source of truth: owfadev/owfa-personal.
|
|
8
|
+
|
|
9
|
+
Derived-layer post-write pipeline: index / tags / hot / log / verify.
|
|
4
10
|
|
|
5
11
|
Positioning (.owfa/protocol/actions.md execution principles): deterministic structural
|
|
6
12
|
operations go to this script; semantic judgment goes to the LLM. Rebuild and rolling
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
schema_version: 1
|
|
2
2
|
id: personal
|
|
3
3
|
kind: suite
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
compat: ">= 0.4"
|
|
6
6
|
members: [calendar, cron, device, domain, hot, index, instance-git, language, link, log, mapping, notes, sessions, structure, tag, tmp, todo, trust, user-profile, vault, wiki]
|
|
7
7
|
depends: [framework]
|
|
8
8
|
skeleton: [vault/, wiki/notes/, wiki/sessions/, wiki/vault/]
|
|
9
9
|
uninstall:
|
|
10
|
-
anchors: [wiki/profile.md, wiki/language.md, wiki/calendar.md, wiki/todo.md, wiki/cron/]
|
|
10
|
+
anchors: [wiki/profile.md, wiki/language.md, wiki/structure.md, wiki/calendar.md, wiki/todo.md, wiki/cron/]
|
|
11
11
|
retains: [vault/, wiki/]
|
|
12
12
|
credentials: []
|
|
13
13
|
bridges: []
|
|
@@ -32,5 +32,6 @@ A derivation-destination bridge governing schedules: domain plugins one-way deri
|
|
|
32
32
|
## Changelog
|
|
33
33
|
|
|
34
34
|
- 0.5 2026-11-09: inject line rewritten to routing-pointer density (issue #49 phase 1, relieves the #48 budget): path + read/write trigger + the one easy-to-break invariant + pointer here; the dropped detail already lives in this file / the manifest
|
|
35
|
-
- 0.4 (2026-10-06) inject line: sections lost to the kernel comment-strip clip restored (quoted values are now protected), tightened for budget headroom — issue #12 post-migration nit
|
|
35
|
+
- 0.4 (2026-10-06) inject line: sections lost to the kernel comment-strip clip restored (quoted values are now protected), tightened for budget headroom — issue #12 post-migration nit
|
|
36
|
+
- 0.3 (2026-10-06) inject line compressed to pointer density (issue #12 layer discipline) — procedural detail lives in usage / PLUGIN.md / skills- 0.2 2026-10-02: global-domain batch two — established the on-demand bridge as the derivation destination; domain-plugin self-descriptions retained with a pointer
|
|
36
37
|
- 0.1 2026-09-19: established — month-page two-section scheme, the open source model, the freezing regime; lark-calendar as the first source adapter
|
|
@@ -20,7 +20,7 @@ Read: registry (`.owfa/protocol/registry.yaml`, field and value-set anchor), tag
|
|
|
20
20
|
2. Read the target asset in vault/
|
|
21
21
|
3. Assemble the proxy-page draft per the write-side contracts in the injection region (mapping mirror and registration fields, trust generated, tag labeling); **present the mapping preview** (path / hash / description / tags) and wait for user confirmation
|
|
22
22
|
4. After confirmation, write the proxy page
|
|
23
|
-
5. **Post-write pipeline** (deterministic, mechanical and automatic, no prompting): execute each plugin's write calls in injection-region order, then finish with `python .owfa/
|
|
23
|
+
5. **Post-write pipeline** (deterministic, mechanical and automatic, no prompting): execute each plugin's write calls in injection-region order, then finish with `python .owfa/plugins/domains/personal/pipeline.py verify` (post-write self-verification; on failure, go back and fix); then commit per the commit discipline (`映射: <asset name>`, see the vocabulary in `.owfa/protocol/actions.md`)
|
|
24
24
|
6. Report: path / hash / description / tags
|
|
25
25
|
|
|
26
26
|
## Prohibitions
|
|
@@ -59,14 +59,14 @@ Output language takes the default key of the language declaration page `wiki/lan
|
|
|
59
59
|
<!-- /usage:tag -->
|
|
60
60
|
|
|
61
61
|
<!-- usage:index -->
|
|
62
|
-
- Post-write rebuild (mechanical, automatic): `python .owfa/
|
|
62
|
+
- Post-write rebuild (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py index` (index — overflow-offloading scheme, including deletion of obsolete old indexes after merge-back) and the same script's `tags` (tag reverse index); LLMs never hand-write indexes
|
|
63
63
|
<!-- /usage:index -->
|
|
64
64
|
|
|
65
65
|
<!-- usage:hot -->
|
|
66
|
-
- Writing entries (mechanical, automatic): `python .owfa/
|
|
66
|
+
- Writing entries (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py hot <type> "<wikilink + one-sentence essence>"` (type value set same as log, see the log block of the AGENTS injection region); window eviction and truncation are performed by the script
|
|
67
67
|
<!-- /usage:hot -->
|
|
68
68
|
|
|
69
69
|
<!-- usage:log -->
|
|
70
|
-
- Writing lines (mechanical, automatic): `python .owfa/
|
|
70
|
+
- Writing lines (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py log <type> "<one sentence>" [--domain <domain>]` (type value set in the log block of the AGENTS injection region; domain tag = domain-plugin name such as bb/lark/vault, mandatory for in-domain transactions, omitted for framework and native transactions); rolling window and archiving are performed by the script
|
|
71
71
|
<!-- /usage:log -->
|
|
72
72
|
<!-- cmd-inject:end -->
|
|
@@ -20,7 +20,7 @@ Read: registry (`.owfa/protocol/registry.yaml`, field and value-set anchor), tru
|
|
|
20
20
|
2. **Judge signals** (criteria in the user-profile usage block below): if this session's and recent observations do not qualify, report and exit — never force a write
|
|
21
21
|
3. Assemble the update: incremental assertions (new value replaces old + body trace) or construction / consolidation (whole-page rewrite) — construction and consolidation **present a profile preview** (assertions / evidence / layers) and wait for user confirmation
|
|
22
22
|
4. Write the page (layered placement and stale_after renewal per the usage block below)
|
|
23
|
-
5. **Post-write pipeline** (deterministic, mechanical and automatic, no prompting): execute each plugin's write calls in injection-region order, then finish with `python .owfa/
|
|
23
|
+
5. **Post-write pipeline** (deterministic, mechanical and automatic, no prompting): execute each plugin's write calls in injection-region order, then finish with `python .owfa/plugins/domains/personal/pipeline.py verify` (post-write self-verification; on failure, go back and fix); then commit per the commit discipline (`画像: <one line>`, see the vocabulary in `.owfa/protocol/actions.md`)
|
|
24
24
|
6. Report: what was updated / the evidence chain / which layer it landed in
|
|
25
25
|
|
|
26
26
|
## Prohibitions
|
|
@@ -62,14 +62,14 @@ Output language takes the default key of the language declaration page `wiki/lan
|
|
|
62
62
|
<!-- /usage:trust -->
|
|
63
63
|
|
|
64
64
|
<!-- usage:index -->
|
|
65
|
-
- Post-write rebuild (mechanical, automatic): `python .owfa/
|
|
65
|
+
- Post-write rebuild (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py index` (index — overflow-offloading scheme, including deletion of obsolete old indexes after merge-back) and the same script's `tags` (tag reverse index); LLMs never hand-write indexes
|
|
66
66
|
<!-- /usage:index -->
|
|
67
67
|
|
|
68
68
|
<!-- usage:hot -->
|
|
69
|
-
- Writing entries (mechanical, automatic): `python .owfa/
|
|
69
|
+
- Writing entries (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py hot <type> "<wikilink + one-sentence essence>"` (type value set same as log, see the log block of the AGENTS injection region); window eviction and truncation are performed by the script
|
|
70
70
|
<!-- /usage:hot -->
|
|
71
71
|
|
|
72
72
|
<!-- usage:log -->
|
|
73
|
-
- Writing lines (mechanical, automatic): `python .owfa/
|
|
73
|
+
- Writing lines (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py log <type> "<one sentence>" [--domain <domain>]` (type value set in the log block of the AGENTS injection region; domain tag = domain-plugin name such as bb/lark/vault, mandatory for in-domain transactions, omitted for framework and native transactions); rolling window and archiving are performed by the script
|
|
74
74
|
<!-- /usage:log -->
|
|
75
75
|
<!-- cmd-inject:end -->
|
|
@@ -33,7 +33,7 @@ type / status value sets defer to the registry (a one-time read anchor; do not r
|
|
|
33
33
|
4. Rewrite in declarative present tense: write knowledge, not dialogue
|
|
34
34
|
5. Create the page per the write-side contracts in the injection region (notes / sessions destinations and shapes, trust generated, tag labeling); the session type goes to the long-sessions section
|
|
35
35
|
6. Write wiki pages mentioned in the conversation into related with wikilinks
|
|
36
|
-
7. **Post-write pipeline** (deterministic, mechanical, automatic): execute each plugin's write calls in injection-region order, then finish with `python .owfa/
|
|
36
|
+
7. **Post-write pipeline** (deterministic, mechanical, automatic): execute each plugin's write calls in injection-region order, then finish with `python .owfa/plugins/domains/personal/pipeline.py verify` (post-write self-verification; on failure, go back and fix); then commit per the commit discipline (`保存: <page title>`, see `.owfa/protocol/actions.md`)
|
|
37
37
|
8. Report: `Saved as [[title]] in wiki/notes/` (session type: `... in wiki/sessions/`)
|
|
38
38
|
|
|
39
39
|
## Long Sessions (session backbone)
|
|
@@ -62,7 +62,7 @@ Declarative present tense; output language takes the default key of the language
|
|
|
62
62
|
|
|
63
63
|
<!-- usage:sessions -->
|
|
64
64
|
- Landing spot `wiki/sessions/YYYY-MM-DD-<topic>.md` (default naming), type: session, participants required (actor list)
|
|
65
|
-
- Backbone page five-section shape: Core Conclusions / Decisions & Rationale / Non-obvious Insights / Open Questions / Related Pages (full-shape sample in the Example section of `.owfa/plugins/sessions/PLUGIN.md`)
|
|
65
|
+
- Backbone page five-section shape: Core Conclusions / Decisions & Rationale / Non-obvious Insights / Open Questions / Related Pages (full-shape sample in the Example section of `.owfa/plugins/domains/personal/sessions/PLUGIN.md`)
|
|
66
66
|
- Standalone high-value topics promoted to `wiki/notes/` pages, the backbone page keeping a wikilink; dedupe before promotion
|
|
67
67
|
<!-- /usage:sessions -->
|
|
68
68
|
|
|
@@ -81,14 +81,14 @@ Declarative present tense; output language takes the default key of the language
|
|
|
81
81
|
<!-- /usage:trust -->
|
|
82
82
|
|
|
83
83
|
<!-- usage:index -->
|
|
84
|
-
- Post-write rebuild (mechanical, automatic): `python .owfa/
|
|
84
|
+
- Post-write rebuild (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py index` (index — overflow-offloading scheme, including deletion of obsolete old indexes after merge-back) and the same script's `tags` (tag reverse index); LLMs never hand-write indexes
|
|
85
85
|
<!-- /usage:index -->
|
|
86
86
|
|
|
87
87
|
<!-- usage:hot -->
|
|
88
|
-
- Writing entries (mechanical, automatic): `python .owfa/
|
|
88
|
+
- Writing entries (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py hot <type> "<wikilink + one-sentence essence>"` (type value set same as log, see the log block of the AGENTS injection region); window eviction and truncation are performed by the script
|
|
89
89
|
<!-- /usage:hot -->
|
|
90
90
|
|
|
91
91
|
<!-- usage:log -->
|
|
92
|
-
- Writing lines (mechanical, automatic): `python .owfa/
|
|
92
|
+
- Writing lines (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py log <type> "<one sentence>" [--domain <domain>]` (type value set in the log block of the AGENTS injection region; domain tag = domain-plugin name such as bb/lark/vault, mandatory for in-domain transactions, omitted for framework and native transactions); rolling window and archiving are performed by the script
|
|
93
93
|
<!-- /usage:log -->
|
|
94
94
|
<!-- cmd-inject:end -->
|
|
@@ -35,5 +35,6 @@
|
|
|
35
35
|
## Changelog
|
|
36
36
|
|
|
37
37
|
- 0.5 2026-11-09: inject line rewritten to routing-pointer density (issue #49 phase 1, relieves the #48 budget): path + read/write trigger + the one easy-to-break invariant + pointer here; the dropped detail already lives in this file / the manifest
|
|
38
|
-
- 0.4 (2026-10-06) inject line: sections lost to the kernel comment-strip clip restored (quoted values are now protected), tightened for budget headroom — issue #12 post-migration nit
|
|
38
|
+
- 0.4 (2026-10-06) inject line: sections lost to the kernel comment-strip clip restored (quoted values are now protected), tightened for budget headroom — issue #12 post-migration nit
|
|
39
|
+
- 0.3 (2026-10-06) inject line compressed to pointer density (issue #12 layer discipline) — procedural detail lives in usage / PLUGIN.md / skills- 0.2 2026-10-04: added the tool-environment summary section — the body's `## Tool Environment` records presence-level info on connectors/runtimes/channels, a cross-device lookup registry, the AGENTS shell pointing to the pages; presence-level granularity, (un)install syncing, no full inventory
|
|
39
40
|
- 0.1 2026-10-04: established — reusing the notes territory (type: entity suggested), the device block mapping minimal key set, the attached-audit expiry scan (30-day window), expiry entering todo via delegation and confirmation; zero commands; depends wiki
|
|
@@ -28,6 +28,7 @@ hot.max_entry_chars: 200 # parameters calibrated against the original reposito
|
|
|
28
28
|
|
|
29
29
|
## Changelog
|
|
30
30
|
|
|
31
|
+
- 0.11 2026-11-15: pipeline call path relocated to the suite package root (suite 0.1.1)
|
|
31
32
|
- 0.10 2026-09-23: added the wiki dependency edge — inner-side plugins attach to wiki, aligning with the domain 0.1 declaration; omitted from the 2026-09-22 domain-ization batch
|
|
32
33
|
- 0.9 2026-09-13: usage's type parameter gained its value-set source — same as log, pointing to the log block of the AGENTS injection region
|
|
33
34
|
- 0.8 2026-09-13: injection source moved to the manifest — removed Checks, Usage, Inject, Attachments sections, md returned to pure documentation
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
id: hot
|
|
2
2
|
schema_version: 1
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.11
|
|
4
4
|
depends:
|
|
5
5
|
- wiki
|
|
6
|
-
updated: 2026-
|
|
6
|
+
updated: 2026-11-15
|
|
7
7
|
attachment:
|
|
8
8
|
- "wiki/hot.md — single-file hot cache"
|
|
9
9
|
fields: {}
|
|
10
10
|
inject: "Hot cache `wiki/hot.md`: recent-change digest (≤25 entries, <5 days, ≤200 chars per entry); agents read this page first on entering the repository; evict out-of-window entries before writing"
|
|
11
11
|
usage:
|
|
12
|
-
- Writing entries (mechanical, automatic): `python .owfa/
|
|
12
|
+
- Writing entries (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py hot <type> "<wikilink + one-sentence essence>"` (type value set same as log, see the log block of the AGENTS injection region); window eviction and truncation are performed by the script
|
|
13
13
|
checks:
|
|
14
14
|
- Window out of bounds (>25 entries / >5 days / >200 chars per entry) → run `pipeline.py hot <type> "<backfill>"` once, or wait for the next write to converge naturally then re-check
|
|
15
15
|
- Contradiction with log (log has records while hot shows no trace) → warning
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
## Changelog
|
|
27
27
|
|
|
28
|
+
- 0.15 2026-11-15: pipeline call path relocated to the suite package root (`.owfa/plugins/domains/personal/pipeline.py`) — derived-layer machinery travels with the suite (suite 0.1.1)
|
|
28
29
|
- 0.14 2026-11-09: inject line rewritten to routing-pointer density (issue #49 phase 1, relieves the #48 budget): path + read/write trigger + the one easy-to-break invariant + pointer here; the dropped detail already lives in this file / the manifest
|
|
29
30
|
- 0.13 (2026-10-06) inject line compressed to pointer density (issue #12 layer discipline) — procedural detail lives in usage / PLUGIN.md / skills- 0.12 2026-09-23: added the wiki dependency edge — inner-side plugins attach to wiki, aligning with the domain 0.1 declaration; omitted from the 2026-09-22 domain-ization batch
|
|
30
31
|
- 0.11 2026-09-19: concept-page detection excluded wiki/tmp/ — the temporary zone stays out of indexes and tags, in step with the tmp plugin's establishment
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
id: index
|
|
2
2
|
schema_version: 1
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.15
|
|
4
4
|
depends:
|
|
5
5
|
- tag
|
|
6
6
|
- wiki
|
|
7
|
-
updated: 2026-11-
|
|
7
|
+
updated: 2026-11-15
|
|
8
8
|
attachment:
|
|
9
9
|
- "wiki/index.md — root index (always present; carries format_version; directly lists reachable pages)"
|
|
10
10
|
- "Per-directory index.md — born of the overflow-offloading scheme (split out when a list overflows the window, disappearing on rebuild once merged back into the parent)"
|
|
@@ -12,7 +12,7 @@ attachment:
|
|
|
12
12
|
fields: {}
|
|
13
13
|
inject: "Index `wiki/index.md` (+ subtree index.md on overflow): derived, never hand-edited — rebuild via `pipeline.py index`; lists every reachable page by full path; the overflow cure is subdirectories; details PLUGIN.md"
|
|
14
14
|
usage:
|
|
15
|
-
- Post-write rebuild (mechanical, automatic): `python .owfa/
|
|
15
|
+
- Post-write rebuild (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py index` (index — overflow-offloading scheme, including deletion of obsolete old indexes after merge-back) and the same script's `tags` (tag reverse index); LLMs never hand-write indexes
|
|
16
16
|
checks:
|
|
17
17
|
- Deviation between indexes and the actual page set (including merged-back indexes pending deletion) → fixed by running `pipeline.py index` to rebuild (idempotent; no diff means consistent); same for tags (`pipeline.py tags`)
|
|
18
18
|
- Traces of hand editing → warning
|
|
@@ -42,6 +42,7 @@ log.max_chars: 14000 # window character cap, approximate
|
|
|
42
42
|
|
|
43
43
|
## Changelog
|
|
44
44
|
|
|
45
|
+
- 0.17 2026-11-15: pipeline call path relocated (suite 0.1.1); `usage_routes: [owfa-query]` added — the log projection in owfa-query's usage block is now manifest-backed
|
|
45
46
|
- 0.16 2026-11-09: inject line rewritten to routing-pointer density (issue #49 phase 1, relieves the #48 budget): path + read/write trigger + the one easy-to-break invariant + pointer here; the dropped detail already lives in this file / the manifest
|
|
46
47
|
- 0.15 2026-10-02: global-domain batch one — established the mandatory bridge, i.e. the recording bridge; domain bases must depend on it, kernel verifies completeness; entries gained the omissible `[domain]` tag; pipeline log gained the `--domain` parameter
|
|
47
48
|
- 0.13 2026-09-23: added the wiki dependency edge — inner-side plugins attach to wiki, aligning with the domain 0.1 declaration; omitted from the 2026-09-22 domain-ization batch
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
id: log
|
|
2
2
|
schema_version: 1
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.17
|
|
4
4
|
bridge: 必依
|
|
5
5
|
depends:
|
|
6
6
|
- wiki
|
|
7
|
-
updated: 2026-11-
|
|
7
|
+
updated: 2026-11-15
|
|
8
8
|
attachment:
|
|
9
9
|
- "wiki/log.md — single-file operation journal (rolling window)"
|
|
10
10
|
- "wiki/archive/YYYY-MM/log.md — archive spillover (immutable zone, grouped by entry month)"
|
|
11
11
|
- "scripts/check.py — attached audit (entry date contract)"
|
|
12
12
|
fields: {}
|
|
13
|
+
usage_routes:
|
|
14
|
+
- owfa-query
|
|
13
15
|
inject: "Run log `wiki/log.md`: prepend, never rewrite; entry = date + type (map/save/query/check/plugin/todo/profile/other) + optional [domain] + one sentence, written via `pipeline.py log`; >100 entries auto-archived; details PLUGIN.md"
|
|
14
16
|
usage:
|
|
15
|
-
- Writing lines (mechanical, automatic): `python .owfa/
|
|
17
|
+
- Writing lines (mechanical, automatic): `python .owfa/plugins/domains/personal/pipeline.py log <type> "<one sentence>" [--domain <domain>]` (type value set in the log block of the AGENTS injection region; domain tag = domain-plugin name such as bb/lark/vault, mandatory for in-domain transactions, omitted for framework and native transactions); rolling window and archiving are performed by the script
|
|
16
18
|
checks:
|
|
17
19
|
- Mechanical items (attached-audit script `scripts/check.py`, executed on audit discovery): undated entries (starting with `- ` yet not matching the date format, main file and archive checked alike) → error
|
|
18
20
|
- Semantic items (check command): historical entries modified (verifiable via git) → error; capacity over the limit → re-check after mechanical archiving
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Derived-layer post-write pipeline: index / tags / hot / log / verify.
|
|
4
|
+
|
|
5
|
+
Lives at the personal-suite package root since 0.1.1 (protocol machinery travels with
|
|
6
|
+
the suite; the kernel never calls it). Instance path: .owfa/plugins/domains/personal/pipeline.py.
|
|
7
|
+
The framework tree keeps a deprecated host copy until 0.5.0.
|
|
8
|
+
|
|
9
|
+
Positioning (.owfa/protocol/actions.md execution principles): deterministic structural
|
|
10
|
+
operations go to this script; semantic judgment goes to the LLM. Rebuild and rolling
|
|
11
|
+
rules and window parameters are governed by this file's source (the script source is
|
|
12
|
+
the rule list); PLUGIN.md keeps the semantic explanation; commands uniformly end by
|
|
13
|
+
invoking this pipeline instead of hand-writing derived pages themselves. verify is
|
|
14
|
+
post-write self-attestation (the minimal attester form): the LLM executes, the script
|
|
15
|
+
certifies.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
python .owfa/plugins/domains/personal/pipeline.py index # canonical suite location (host shim: .owfa/scripts/pipeline.py, deprecated)
|
|
19
|
+
python .owfa/scripts/pipeline.py index # deprecated host shim, retires in 0.5.0 (overflow-offloading scheme, idempotent; includes deleting surplus indexes after merging back)
|
|
20
|
+
python .owfa/scripts/pipeline.py tags # rebuild wiki/tags.md (idempotent)
|
|
21
|
+
python .owfa/scripts/pipeline.py hot <type> <one-liner> # evict out-of-window entries + prepend a hot-cache entry
|
|
22
|
+
python .owfa/scripts/pipeline.py log <type> <one-liner> [--domain domain] # capacity archive + prepend a log line (domain tag optional — domainless transactions)
|
|
23
|
+
python .owfa/scripts/pipeline.py verify # post-write self-attestation: attached audits + derived-area drift check
|
|
24
|
+
|
|
25
|
+
Type enumeration: map / save / query / check / plugin / todo / profile / other (the log plugin).
|
|
26
|
+
Concept-page determination (what enters the index): all .md under wiki/, excluding —
|
|
27
|
+
reserved names (index.md, log.md), wiki-root derived pages (hot.md, tags.md), the
|
|
28
|
+
archive/ subtree (immutable region, never rewritten by this script), and the tmp/
|
|
29
|
+
subtree (temporary zone: invisible to the derived layer, no retention promise, see the
|
|
30
|
+
tmp plugin). Index overflow-offloading scheme: a single index list holds at most
|
|
31
|
+
INDEX_MAX_ENTRIES entries (page entries + directory entries); the root index always
|
|
32
|
+
exists; on overflow, split off the subdirectories with the most pages (ties by name)
|
|
33
|
+
into their own indexes until it fits; pure-function rebuild (same structure, same result).
|
|
34
|
+
"""
|
|
35
|
+
import datetime
|
|
36
|
+
import os
|
|
37
|
+
import re
|
|
38
|
+
import subprocess
|
|
39
|
+
import sys
|
|
40
|
+
|
|
41
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
42
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
43
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
44
|
+
|
|
45
|
+
# Location-adaptive bootstrap. Two legal hosts:
|
|
46
|
+
# suite install: <root>/.owfa/plugins/domains/personal/pipeline.py (canonical since 0.1.1)
|
|
47
|
+
# framework host shim: <root>/.owfa/scripts/pipeline.py (deprecated, retires in 0.5.0)
|
|
48
|
+
_here = os.path.dirname(os.path.abspath(__file__))
|
|
49
|
+
if os.path.basename(_here) == "scripts":
|
|
50
|
+
ROOT = os.path.dirname(os.path.dirname(_here)) # <root>/.owfa/scripts
|
|
51
|
+
else:
|
|
52
|
+
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(_here))) # .../personal -> domains -> plugins -> .owfa -> root is one more up
|
|
53
|
+
ROOT = os.path.dirname(ROOT)
|
|
54
|
+
sys.path.insert(0, os.path.join(ROOT, ".owfa", "scripts")) # wikilib is a framework-level shared library
|
|
55
|
+
sys.path.insert(0, _here)
|
|
56
|
+
import wikilib
|
|
57
|
+
WIKI = os.path.join(ROOT, "wiki")
|
|
58
|
+
FORMAT_VERSION = "0.2"
|
|
59
|
+
DESC_MAX = 80
|
|
60
|
+
ROOT_RESERVED = {"hot.md", "tags.md"} # wiki-root derived pages (log.md/index.md are reserved names, excluded uniformly)
|
|
61
|
+
|
|
62
|
+
# rolling-window parameters (mechanical authoritative source for the hot / log plugins; PLUGIN.md keeps the semantic explanation)
|
|
63
|
+
HOT_MAX_ENTRIES = 25
|
|
64
|
+
HOT_MAX_DAYS = 5
|
|
65
|
+
HOT_MAX_CHARS = 200
|
|
66
|
+
LOG_MAX_ENTRIES = 100
|
|
67
|
+
INDEX_MAX_ENTRIES = 25 # single index-list window (page entries + directory entries; overflow-offloading split basis)
|
|
68
|
+
TYPE_ORDER = ["map", "save", "query", "check", "plugin", "todo", "profile", "other"]
|
|
69
|
+
HOT_HEADER = ("# 热缓存\n\n> 最近变更摘要;≤25 条且 <5 日,窗外即删;可整体再生。"
|
|
70
|
+
"规则见 `.owfa/plugins/domains/personal/hot/`,写走 `pipeline.py hot`。\n")
|
|
71
|
+
LOG_HEADER = ("# 运行日志\n\n> 置顶追加、只增不删;条目 = `- YYYY-MM-DD <类型> [<域>]:一句话`(域标可缺省——无域事务,log 桥)。"
|
|
72
|
+
"规则见 `.owfa/plugins/domains/personal/log/`,写走 `pipeline.py log`。\n")
|
|
73
|
+
DATE_RE = re.compile(r"^- (\d{4}-\d{2}-\d{2}) ")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _today():
|
|
77
|
+
return datetime.date.today().isoformat()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------- concept pages and index / tags ----------
|
|
81
|
+
|
|
82
|
+
def _cut(text):
|
|
83
|
+
return text if len(text) <= DESC_MAX else text[: DESC_MAX - 1] + "…"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _desc(fm, body):
|
|
87
|
+
d = fm.get("description")
|
|
88
|
+
if d:
|
|
89
|
+
return _cut(str(d))
|
|
90
|
+
for line in body.splitlines():
|
|
91
|
+
t = line.strip()
|
|
92
|
+
if t and not t.startswith(("#", ">", "|", "```")):
|
|
93
|
+
return _cut(t)
|
|
94
|
+
return "(无描述)"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def concept_pages():
|
|
98
|
+
"""[(page full name = path minus .md, containing dir, frontmatter, description)], sorted by full name."""
|
|
99
|
+
out = []
|
|
100
|
+
for rel, fm, body in wikilib.walk_pages(ROOT):
|
|
101
|
+
d, _, fn = rel.rpartition("/")
|
|
102
|
+
if fn in ("index.md", "log.md"):
|
|
103
|
+
continue # reserved names: not concept pages
|
|
104
|
+
if d == "" and fn in ROOT_RESERVED:
|
|
105
|
+
continue
|
|
106
|
+
if rel.startswith("archive/") or d.startswith("archive/") or d == "archive":
|
|
107
|
+
continue
|
|
108
|
+
if rel.startswith("tmp/") or d.startswith("tmp/") or d == "tmp":
|
|
109
|
+
continue # temporary zone: invisible to the derived layer
|
|
110
|
+
out.append((rel[:-3], d, fm, _desc(fm, body)))
|
|
111
|
+
return sorted(out)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def all_dirs(pages):
|
|
115
|
+
"""The set of directories under wiki/ that should get an index (including empty ones; excluding the archive/ subtree)."""
|
|
116
|
+
dirs = {""}
|
|
117
|
+
for _, d, _, _ in pages:
|
|
118
|
+
parts = d.split("/") if d else []
|
|
119
|
+
for i in range(len(parts) + 1):
|
|
120
|
+
dirs.add("/".join(parts[:i]))
|
|
121
|
+
for dirpath, subdirs, _files in os.walk(WIKI):
|
|
122
|
+
if "archive" in subdirs:
|
|
123
|
+
subdirs.remove("archive") # do not descend into the immutable region
|
|
124
|
+
if "tmp" in subdirs:
|
|
125
|
+
subdirs.remove("tmp") # no index for the temporary zone
|
|
126
|
+
rel = os.path.relpath(dirpath, WIKI).replace(os.sep, "/")
|
|
127
|
+
dirs.add("" if rel == "." else rel)
|
|
128
|
+
return dirs
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def subdirs_of(d, dirs):
|
|
132
|
+
"""Direct subdirectory names of d (sorted)."""
|
|
133
|
+
prefix = d + "/" if d else ""
|
|
134
|
+
depth = d.count("/") + 1 if d else 0
|
|
135
|
+
return sorted(x[len(prefix):] for x in dirs
|
|
136
|
+
if x != d and x.startswith(prefix) and x.count("/") == depth)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _count_subtree(full, pages):
|
|
140
|
+
return sum(1 for name, d, _, _ in pages if d == full or d.startswith(full + "/"))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def plan_index_dirs(pages, dirs):
|
|
144
|
+
"""Overflow-offloading plan: returns the set of directories that should get an index.md (the root always present).
|
|
145
|
+
|
|
146
|
+
A directory's list = its own concept pages + pages of un-split subtrees (transparent
|
|
147
|
+
inlining) + directory entry lines; on overflow, split off the subdirectories with the
|
|
148
|
+
most pages (ties by name) into their own indexes until it fits. A split saves at least
|
|
149
|
+
S-1 lines, so a subdirectory with S<2 is never split; an empty subtree (S=0) costs
|
|
150
|
+
one inline line (the visibility line). Pure function: same structure guarantees the
|
|
151
|
+
same result, no historical state.
|
|
152
|
+
"""
|
|
153
|
+
own = {}
|
|
154
|
+
for _, d, _, _ in pages:
|
|
155
|
+
own[d] = own.get(d, 0) + 1
|
|
156
|
+
|
|
157
|
+
planned = set()
|
|
158
|
+
|
|
159
|
+
def plan(d):
|
|
160
|
+
planned.add(d)
|
|
161
|
+
kids = subdirs_of(d, dirs)
|
|
162
|
+
sizes = {c: _count_subtree((d + "/" + c) if d else c, pages) for c in kids}
|
|
163
|
+
total = own.get(d, 0) + sum(s if s > 0 else 1 for s in sizes.values())
|
|
164
|
+
if total <= INDEX_MAX_ENTRIES:
|
|
165
|
+
return
|
|
166
|
+
for c in sorted((k for k in kids if sizes[k] >= 2),
|
|
167
|
+
key=lambda c: (-sizes[c], c)):
|
|
168
|
+
if total <= INDEX_MAX_ENTRIES:
|
|
169
|
+
break
|
|
170
|
+
plan((d + "/" + c) if d else c)
|
|
171
|
+
total -= sizes[c] - 1
|
|
172
|
+
|
|
173
|
+
plan("")
|
|
174
|
+
return planned
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def visible_pages(d, pages, planned):
|
|
178
|
+
"""Pages listed directly in d's index: this directory's pages + all descendant pages whose path crosses no split directory (transparent inlining)."""
|
|
179
|
+
pre = d + "/" if d else ""
|
|
180
|
+
out = []
|
|
181
|
+
for name, dd, fm, desc in pages:
|
|
182
|
+
if dd == d:
|
|
183
|
+
out.append((name, fm, desc))
|
|
184
|
+
elif dd.startswith(pre):
|
|
185
|
+
parts = dd[len(pre):].split("/")
|
|
186
|
+
if all((pre + "/".join(parts[:i])) not in planned for i in range(1, len(parts) + 1)):
|
|
187
|
+
out.append((name, fm, desc))
|
|
188
|
+
return out
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def render_index(d, pages, dirs, planned):
|
|
192
|
+
"""Full text of directory d's index.md (overflow-offloading scheme: reachable pages listed directly; split subdirectories as entry lines)."""
|
|
193
|
+
is_root = d == ""
|
|
194
|
+
lines = []
|
|
195
|
+
if is_root:
|
|
196
|
+
lines += ["---", f"format_version: {FORMAT_VERSION}", "---", ""]
|
|
197
|
+
lines.append("# 索引" if is_root else f"# {d.rsplit('/', 1)[-1]} 索引")
|
|
198
|
+
lines.append("")
|
|
199
|
+
tip = ("溢出减负:本页直列可达页面;清单超窗时子目录自立索引(入口行带页数),下钻读之。"
|
|
200
|
+
if is_root else "本目录索引:直列未切子树页面,已切子目录见入口行。")
|
|
201
|
+
lines.append(f"> {tip}只聚合、永不手编,由 index 插件经 `pipeline.py index` 重建。")
|
|
202
|
+
lines.append("")
|
|
203
|
+
entries = []
|
|
204
|
+
for s in subdirs_of(d, dirs):
|
|
205
|
+
full = (d + "/" + s) if d else s
|
|
206
|
+
n = _count_subtree(full, pages)
|
|
207
|
+
if full in planned:
|
|
208
|
+
entries.append(f"- [[{full}/index|{s}/]]({n} 页)")
|
|
209
|
+
elif n == 0:
|
|
210
|
+
entries.append(f"- {s}/(0 页)") # empty-subtree visibility line: no index to link, excluded from the broken-link graph
|
|
211
|
+
if entries:
|
|
212
|
+
lines += ["## 子目录", ""] + entries + [""]
|
|
213
|
+
groups = {}
|
|
214
|
+
for name, fm, desc in visible_pages(d, pages, planned):
|
|
215
|
+
groups.setdefault(str(fm.get("type") or "未分类"), []).append((name, desc))
|
|
216
|
+
for t in sorted(groups):
|
|
217
|
+
lines.append(f"## {t}")
|
|
218
|
+
lines.append("")
|
|
219
|
+
for name, desc in sorted(groups[t]):
|
|
220
|
+
lines.append(f"- [[{name}]] —— {desc}")
|
|
221
|
+
lines.append("")
|
|
222
|
+
if not entries and not groups:
|
|
223
|
+
lines.append("(暂无页面)")
|
|
224
|
+
lines.append("")
|
|
225
|
+
return "\n".join(lines).rstrip() + "\n"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def render_tags(pages):
|
|
229
|
+
tag_map = {}
|
|
230
|
+
for name, d, fm, _ in pages:
|
|
231
|
+
tags = fm.get("tags")
|
|
232
|
+
if tags is None:
|
|
233
|
+
continue
|
|
234
|
+
if not isinstance(tags, list):
|
|
235
|
+
tags = [tags]
|
|
236
|
+
for t in tags:
|
|
237
|
+
tag_map.setdefault(str(t), []).append(name)
|
|
238
|
+
lines = ["# tag 索引", "",
|
|
239
|
+
"> tag → 页面反向索引;只聚合、永不手编,由 index 插件经 `pipeline.py tags` 重建。", ""]
|
|
240
|
+
if not tag_map:
|
|
241
|
+
lines.append("(暂无)")
|
|
242
|
+
lines.append("")
|
|
243
|
+
for t in sorted(tag_map):
|
|
244
|
+
lines.append(f"## {t}")
|
|
245
|
+
lines.append("")
|
|
246
|
+
for name in sorted(tag_map[t]):
|
|
247
|
+
lines.append(f"- [[{name}]]")
|
|
248
|
+
lines.append("")
|
|
249
|
+
return "\n".join(lines).rstrip() + "\n"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def index_targets():
|
|
253
|
+
pages = concept_pages()
|
|
254
|
+
dirs = all_dirs(pages)
|
|
255
|
+
planned = plan_index_dirs(pages, dirs)
|
|
256
|
+
targets = {(d + "/" if d else "") + "index.md": render_index(d, pages, dirs, planned)
|
|
257
|
+
for d in planned}
|
|
258
|
+
return planned, targets
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def write_changed(path, content):
|
|
262
|
+
if os.path.exists(path) and open(path, encoding="utf-8").read() == content:
|
|
263
|
+
return False
|
|
264
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
265
|
+
open(path, "w", encoding="utf-8", newline="\n").write(content)
|
|
266
|
+
return True
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def do_index(check_only=False):
|
|
270
|
+
_, targets = index_targets()
|
|
271
|
+
drift, changed = [], 0
|
|
272
|
+
for rel, content in sorted(targets.items()):
|
|
273
|
+
path = os.path.join(WIKI, rel)
|
|
274
|
+
cur = open(path, encoding="utf-8").read() if os.path.exists(path) else None
|
|
275
|
+
if cur == content:
|
|
276
|
+
continue
|
|
277
|
+
if check_only:
|
|
278
|
+
drift.append(rel)
|
|
279
|
+
elif write_changed(path, content):
|
|
280
|
+
changed += 1
|
|
281
|
+
print(f"[index] rebuilt wiki/{rel}")
|
|
282
|
+
# unplanned index.md files (old indexes after offloading merges back): delete / report drift; archive/ and tmp/ subtrees untouched
|
|
283
|
+
for dirpath, subdirs, files in os.walk(WIKI):
|
|
284
|
+
if "archive" in subdirs:
|
|
285
|
+
subdirs.remove("archive") # do not descend into the immutable region
|
|
286
|
+
if "tmp" in subdirs:
|
|
287
|
+
subdirs.remove("tmp") # the temporary zone is outside this pipeline's jurisdiction
|
|
288
|
+
if "index.md" not in files:
|
|
289
|
+
continue
|
|
290
|
+
rel = os.path.relpath(os.path.join(dirpath, "index.md"), WIKI).replace(os.sep, "/")
|
|
291
|
+
if rel not in targets:
|
|
292
|
+
if check_only:
|
|
293
|
+
drift.append(rel)
|
|
294
|
+
else:
|
|
295
|
+
os.remove(os.path.join(WIKI, rel))
|
|
296
|
+
changed += 1
|
|
297
|
+
print(f"[index] removed wiki/{rel} (merged back into parent)")
|
|
298
|
+
if not check_only:
|
|
299
|
+
print(f"[index] done, {changed} file(s) changed (idempotent)")
|
|
300
|
+
return drift
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def do_tags(check_only=False):
|
|
304
|
+
path = os.path.join(WIKI, "tags.md")
|
|
305
|
+
content = render_tags(concept_pages())
|
|
306
|
+
cur = open(path, encoding="utf-8").read() if os.path.exists(path) else None
|
|
307
|
+
if cur == content:
|
|
308
|
+
if not check_only:
|
|
309
|
+
print("[tags] unchanged")
|
|
310
|
+
return []
|
|
311
|
+
if check_only:
|
|
312
|
+
return ["tags.md"]
|
|
313
|
+
write_changed(path, content)
|
|
314
|
+
print("[tags] rebuilt wiki/tags.md")
|
|
315
|
+
return []
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ---------- hot / log rolling ----------
|
|
319
|
+
|
|
320
|
+
def do_hot(kind, text):
|
|
321
|
+
if kind not in TYPE_ORDER:
|
|
322
|
+
print(f"[hot] type must be one of {'/'.join(TYPE_ORDER)} (got {kind})")
|
|
323
|
+
return False
|
|
324
|
+
entry = f"- {_today()} {text}"[:HOT_MAX_CHARS]
|
|
325
|
+
# collect existing entries (with section membership), mechanically evict out-of-window ones
|
|
326
|
+
sections = {t: [] for t in TYPE_ORDER}
|
|
327
|
+
hot_path = os.path.join(WIKI, "hot.md")
|
|
328
|
+
if os.path.exists(hot_path):
|
|
329
|
+
cur = None
|
|
330
|
+
for raw in open(hot_path, encoding="utf-8").read().splitlines():
|
|
331
|
+
m = re.match(r"^##\s*Recent (\S+)", raw)
|
|
332
|
+
if m and m.group(1) in TYPE_ORDER:
|
|
333
|
+
cur = m.group(1)
|
|
334
|
+
continue
|
|
335
|
+
dm = DATE_RE.match(raw)
|
|
336
|
+
if dm and cur:
|
|
337
|
+
if (datetime.date.today() - datetime.date.fromisoformat(dm.group(1))).days < HOT_MAX_DAYS:
|
|
338
|
+
sections[cur].append(raw[:HOT_MAX_CHARS])
|
|
339
|
+
sections[kind].insert(0, entry) # prepend
|
|
340
|
+
flat = [e for t in TYPE_ORDER for e in sections[t]]
|
|
341
|
+
if len(flat) > HOT_MAX_ENTRIES: # overall overflow: keep the 25 most recent by date uniformly (stable sort, same-day keeps section order)
|
|
342
|
+
keep = set(sorted(flat, key=lambda e: e[2:12], reverse=True)[:HOT_MAX_ENTRIES])
|
|
343
|
+
for t in TYPE_ORDER:
|
|
344
|
+
sections[t] = [e for e in sections[t] if e in keep]
|
|
345
|
+
body = [HOT_HEADER.rstrip()]
|
|
346
|
+
any_entry = False
|
|
347
|
+
for t in TYPE_ORDER:
|
|
348
|
+
if not sections[t]:
|
|
349
|
+
continue
|
|
350
|
+
any_entry = True
|
|
351
|
+
body += ["", f"## Recent {t}", ""] + sections[t]
|
|
352
|
+
if not any_entry:
|
|
353
|
+
body += ["", "(暂无)"]
|
|
354
|
+
write_changed(hot_path, "\n".join(body) + "\n")
|
|
355
|
+
print(f"[hot] written ({kind}), {min(len(flat), HOT_MAX_ENTRIES)} entries")
|
|
356
|
+
return True
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _split_domain(args):
|
|
360
|
+
"""Extract --domain <domain> / --domain=<domain> from the argument list (the log bridge's domain tag); the remaining args are returned in order."""
|
|
361
|
+
dom, rest, i = None, [], 0
|
|
362
|
+
while i < len(args):
|
|
363
|
+
if args[i] == "--domain" and i + 1 < len(args):
|
|
364
|
+
dom = args[i + 1]
|
|
365
|
+
i += 2
|
|
366
|
+
continue
|
|
367
|
+
if args[i].startswith("--domain="):
|
|
368
|
+
dom = args[i].split("=", 1)[1]
|
|
369
|
+
i += 1
|
|
370
|
+
continue
|
|
371
|
+
rest.append(args[i])
|
|
372
|
+
i += 1
|
|
373
|
+
return dom, rest
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def do_log(kind, text, domain=None):
|
|
377
|
+
if kind not in TYPE_ORDER:
|
|
378
|
+
print(f"[log] type must be one of {'/'.join(TYPE_ORDER)} (got {kind})")
|
|
379
|
+
return False
|
|
380
|
+
log_path = os.path.join(WIKI, "log.md")
|
|
381
|
+
entries = []
|
|
382
|
+
if os.path.exists(log_path):
|
|
383
|
+
for raw in open(log_path, encoding="utf-8").read().splitlines():
|
|
384
|
+
if DATE_RE.match(raw):
|
|
385
|
+
entries.append(raw)
|
|
386
|
+
tag = f" [{domain}]" if domain else ""
|
|
387
|
+
entries.insert(0, f"- {_today()} {kind}{tag}:{text}") # prepend; existing entries are never rewritten
|
|
388
|
+
overflow = entries[LOG_MAX_ENTRIES:]
|
|
389
|
+
if overflow: # window overflow: the oldest stretch is grouped by entry month into archive/<month>/log.md (position move only)
|
|
390
|
+
for month in sorted({e[2:9] for e in overflow}):
|
|
391
|
+
moved = [e for e in overflow if e[2:9] == month]
|
|
392
|
+
adir = os.path.join(WIKI, "archive", month)
|
|
393
|
+
apath = os.path.join(adir, "log.md")
|
|
394
|
+
old = open(apath, encoding="utf-8").read() if os.path.exists(apath) else f"# 归档 {month}\n"
|
|
395
|
+
write_changed(apath, old.rstrip() + "\n" + "\n".join(moved) + "\n")
|
|
396
|
+
print(f"[log] archived {len(moved)} entries to wiki/archive/{month}/log.md")
|
|
397
|
+
write_changed(log_path, LOG_HEADER + "\n" + "\n".join(entries[:LOG_MAX_ENTRIES]) + "\n")
|
|
398
|
+
print(f"[log] written ({kind}), main file {min(len(entries), LOG_MAX_ENTRIES)} entries")
|
|
399
|
+
return True
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# ---------- post-write self-attestation ----------
|
|
403
|
+
|
|
404
|
+
def do_verify():
|
|
405
|
+
ok = True
|
|
406
|
+
r = subprocess.run(
|
|
407
|
+
[sys.executable, os.path.join(ROOT, ".owfa", "scripts", "owfa_kernel.py"), "audit"])
|
|
408
|
+
if r.returncode != 0:
|
|
409
|
+
ok = False
|
|
410
|
+
print("[verify] plugin audit has errors")
|
|
411
|
+
drift = do_index(check_only=True) + do_tags(check_only=True)
|
|
412
|
+
if drift:
|
|
413
|
+
ok = False
|
|
414
|
+
print("[verify] derived drift: " + ", ".join("wiki/" + d for d in drift) + " (run pipeline.py index / tags to fix)")
|
|
415
|
+
else:
|
|
416
|
+
print("[verify] derived area consistent")
|
|
417
|
+
print("[verify] " + ("passed" if ok else "failed"))
|
|
418
|
+
return 0 if ok else 1
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def main():
|
|
422
|
+
cmd = sys.argv[1] if len(sys.argv) > 1 else ""
|
|
423
|
+
if cmd == "index":
|
|
424
|
+
do_index()
|
|
425
|
+
return 0
|
|
426
|
+
if cmd == "tags":
|
|
427
|
+
do_tags()
|
|
428
|
+
return 0
|
|
429
|
+
if cmd == "hot":
|
|
430
|
+
return 0 if do_hot(sys.argv[2], " ".join(sys.argv[3:])) else 1
|
|
431
|
+
if cmd == "log":
|
|
432
|
+
dom, rest = _split_domain(sys.argv[3:])
|
|
433
|
+
return 0 if do_log(sys.argv[2], " ".join(rest), domain=dom) else 1
|
|
434
|
+
if cmd == "verify":
|
|
435
|
+
return do_verify()
|
|
436
|
+
print(__doc__)
|
|
437
|
+
return 2
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
if __name__ == "__main__":
|
|
441
|
+
sys.exit(main())
|
|
@@ -52,6 +52,7 @@ The performance budget for volumetric fog on low-end machines?
|
|
|
52
52
|
|
|
53
53
|
## Changelog
|
|
54
54
|
|
|
55
|
+
- 0.11 2026-11-15: manifest path fix — `.owfa/plugins/sessions/` → `.owfa/plugins/domains/personal/sessions/` (stale pre-split deployment path)
|
|
55
56
|
- 0.10 2026-11-09: inject line rewritten to routing-pointer density (issue #49 phase 1, relieves the #48 budget): path + read/write trigger + the one easy-to-break invariant + pointer here; the dropped detail already lives in this file / the manifest
|
|
56
57
|
- 0.9 2026-09-23: added the wiki dependency edge — inner plugins attach to wiki, aligning with the domain 0.1 declaration; missed in the 2026-09-22 domainization batch
|
|
57
58
|
- 0.8 2026-09-13: made the usage sample pointer explicit — added the `.owfa/plugins/sessions/PLUGIN.md` path, zero priors
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
id: sessions
|
|
2
2
|
schema_version: 1
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.11
|
|
4
4
|
depends:
|
|
5
5
|
- wiki
|
|
6
|
-
updated: 2026-11-
|
|
6
|
+
updated: 2026-11-15
|
|
7
7
|
attachment:
|
|
8
8
|
- "wiki/sessions/ — native sessions zone, session-type backbone pages"
|
|
9
9
|
fields:
|
|
@@ -13,7 +13,7 @@ commands:
|
|
|
13
13
|
inject: "Native sessions `wiki/sessions/`: session backbone pages (`type: session`, `participants` required, named YYYY-MM-DD-<topic>); high-value topics promoted to `wiki/notes/` with a back-link; commands append, never modify"
|
|
14
14
|
usage:
|
|
15
15
|
- Landing spot `wiki/sessions/YYYY-MM-DD-<topic>.md` (default naming), type: session, participants required (actor list)
|
|
16
|
-
- Backbone page five-section shape: Core Conclusions / Decisions & Rationale / Non-obvious Insights / Open Questions / Related Pages (full-shape sample in the Example section of `.owfa/plugins/sessions/PLUGIN.md`)
|
|
16
|
+
- Backbone page five-section shape: Core Conclusions / Decisions & Rationale / Non-obvious Insights / Open Questions / Related Pages (full-shape sample in the Example section of `.owfa/plugins/domains/personal/sessions/PLUGIN.md`)
|
|
17
17
|
- Standalone high-value topics promoted to `wiki/notes/` pages, the backbone page keeping a wikilink; dedupe before promotion
|
|
18
18
|
checks:
|
|
19
19
|
- Mechanical (covered by audit): session-type pages outside `wiki/sessions/` (or the reverse) → warning; participants missing or entries violating the actor convention → warning
|
|
@@ -24,4 +24,5 @@
|
|
|
24
24
|
## Changelog
|
|
25
25
|
|
|
26
26
|
- 0.4 2026-11-09: inject line rewritten to routing-pointer density (issue #49 phase 1, relieves the #48 budget): path + read/write trigger + the one easy-to-break invariant + pointer here; the dropped detail already lives in this file / the manifest
|
|
27
|
-
- 0.3 (2026-10-06) inject line: code formatting restored on the territory path (issue #12 post-migration nit)
|
|
27
|
+
- 0.3 (2026-10-06) inject line: code formatting restored on the territory path (issue #12 post-migration nit)
|
|
28
|
+
- 0.2 (2026-10-06) inject line compressed to pointer density (issue #12 layer discipline) — procedural detail lives in usage / PLUGIN.md / skills- 0.1 2026-09-19: established — path-as-territory, invisible to the derived layer, stale_after cleanup prompts, delete upon promotion
|
|
@@ -35,7 +35,8 @@ The cognition bridge: the global profile's extension point toward domain-level c
|
|
|
35
35
|
|
|
36
36
|
- 0.8 2026-11-12: contract-had-no-enforcement fix — mechanical checks (wrong-territory error / page-absent info) landed as the attached-audit script scripts/check.py, closing the gap between the declared checks and execution
|
|
37
37
|
- 0.7 2026-11-09: inject line rewritten to routing-pointer density (issue #49 phase 1, relieves the #48 budget): path + read/write trigger + the one easy-to-break invariant + pointer here; the dropped detail already lives in this file / the manifest
|
|
38
|
-
- 0.6 (2026-10-06) inject line: sections lost to the kernel comment-strip clip restored (quoted values are now protected), tightened for budget headroom — issue #12 post-migration nit
|
|
38
|
+
- 0.6 (2026-10-06) inject line: sections lost to the kernel comment-strip clip restored (quoted values are now protected), tightened for budget headroom — issue #12 post-migration nit
|
|
39
|
+
- 0.5 (2026-10-06) inject line compressed to pointer density (issue #12 layer discipline) — procedural detail lives in usage / PLUGIN.md / skills- 0.4 2026-10-02: global-domain batch three — established the cognition bridge, on-demand: the profile body `## Domain Cognition` registration section, aggregate-don't-copy, read-first discipline; bb-track user.md the first registration; the mapping edge removed — under the bridge law it reached domain via the vault chain, causing a global plugin to be misjudged, caught by an actual kernel test; the 'hanging edges are harmless' premise failed
|
|
39
40
|
- 0.3 2026-09-29: profile command established — the write side shed its parasitism on the map and save dual entry points, both commands' consumes entries removed; the evidence domain opened to any page inside wiki, dissolving the cross-domain ownership suspension; first creation self-builds on trigger, absorbing the profile part of the pending initialization items; tags explicitly not applied; depends kept, ruling: reference-level edges retained, not cut
|
|
40
41
|
- 0.2 2026-09-14: usage added the profile distillation methodology — signal criteria, layered placement, assertions concrete and verifiable; the executable part of the distillation entered the write-side contract, closing the SASU-L disclosure loop: an agent running map or save knows how to distill, without relying on model priors; the full text remains in `docs/research-user-profile.md`
|
|
41
42
|
- 0.1 2026-09-13: established — `wiki/profile.md` convergence-style cognition profile, type: profile entered the registry value set; depends were wiki, trust, mapping, sessions; dual signal channels hung on save and map, consumes inserted after trust and before derivation; three checks — the assertion evidence gate as warning, wrong territory as error, page absence as info-level
|