musubix6 0.0.0-stage → 0.1.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.
Files changed (79) hide show
  1. package/.github/skills/agent-watchdog/SKILL.md +32 -0
  2. package/.github/skills/agent-watchdog/scripts/watchdog.mjs +67 -0
  3. package/.github/skills/agent-watchdog/scripts/watchdog.test.mjs +92 -0
  4. package/.github/skills/japanese-prose/NOTICE.md +17 -0
  5. package/.github/skills/japanese-prose/SKILL.md +111 -0
  6. package/.github/skills/japanese-prose/references/review-workflow.md +50 -0
  7. package/.github/skills/japanese-prose/references/scoring.md +24 -0
  8. package/.github/skills/japanese-prose/references/writing-guidelines.md +60 -0
  9. package/.github/skills/japanese-prose/scripts/core.py +192 -0
  10. package/.github/skills/japanese-prose/scripts/fixtures/natural.md +5 -0
  11. package/.github/skills/japanese-prose/scripts/fixtures/unnatural.md +5 -0
  12. package/.github/skills/japanese-prose/scripts/lint.py +378 -0
  13. package/.github/skills/japanese-prose/scripts/outline.py +68 -0
  14. package/.github/skills/japanese-prose/scripts/terms.py +112 -0
  15. package/.github/skills/japanese-prose/scripts/test_engine.py +117 -0
  16. package/.github/skills/lean-agentic-coding/SKILL.md +54 -0
  17. package/.github/skills/lean-agentic-coding/references/contract-template.md +30 -0
  18. package/.github/skills/lean-agentic-coding/references/ledger-format.md +14 -0
  19. package/.github/skills/lean-agentic-coding/references/t2-protocol.md +15 -0
  20. package/.github/skills/lean-agentic-coding/scripts/gate.sh +21 -0
  21. package/.github/skills/lean-sdd-tdd/SKILL.md +70 -0
  22. package/.github/skills/lean-sdd-tdd/references/config.md +91 -0
  23. package/.github/skills/lean-sdd-tdd/references/enforced-rules.md +230 -0
  24. package/.github/skills/lean-sdd-tdd/references/spec-template.md +36 -0
  25. package/.github/skills/lean-sdd-tdd/references/stubs.md +72 -0
  26. package/.github/skills/lean-sdd-tdd/references/t2.md +12 -0
  27. package/.github/skills/lean-sdd-tdd/scripts/sdd.mjs +7612 -0
  28. package/.github/skills/lean-sdd-tdd/scripts/sdd.test.mjs +14140 -0
  29. package/.github/skills/sdd-reverse-spec/SKILL.md +35 -0
  30. package/.github/skills/sdd-spec-interview/SKILL.md +44 -0
  31. package/.github/skills/tech-writer/SKILL.md +171 -0
  32. package/.github/skills/tech-writer/assets/templates/blueprint.md +187 -0
  33. package/.github/skills/tech-writer/assets/templates/design-doc.md +29 -0
  34. package/.github/skills/tech-writer/assets/templates/migration-plan.md +173 -0
  35. package/.github/skills/tech-writer/assets/templates/operations-runbook.md +202 -0
  36. package/.github/skills/tech-writer/assets/templates/pr-description.md +23 -0
  37. package/.github/skills/tech-writer/assets/templates/qiita.md +44 -0
  38. package/.github/skills/tech-writer/assets/templates/readme.md +38 -0
  39. package/.github/skills/tech-writer/assets/templates/requirements-definition.md +170 -0
  40. package/.github/skills/tech-writer/assets/templates/rfi.md +113 -0
  41. package/.github/skills/tech-writer/assets/templates/rfp.md +180 -0
  42. package/.github/skills/tech-writer/assets/templates/security-design.md +167 -0
  43. package/.github/skills/tech-writer/assets/templates/system-design.md +220 -0
  44. package/.github/skills/tech-writer/assets/templates/technical-proposal.md +112 -0
  45. package/.github/skills/tech-writer/assets/templates/test-plan.md +153 -0
  46. package/.github/skills/tech-writer/assets/templates/user-manual.md +22 -0
  47. package/.github/skills/tech-writer/assets/templates/white-paper.md +192 -0
  48. package/.github/skills/tech-writer/references/doctypes/api-docs.md +33 -0
  49. package/.github/skills/tech-writer/references/doctypes/blueprint.md +81 -0
  50. package/.github/skills/tech-writer/references/doctypes/code-comments.md +39 -0
  51. package/.github/skills/tech-writer/references/doctypes/design-doc.md +42 -0
  52. package/.github/skills/tech-writer/references/doctypes/migration-plan.md +63 -0
  53. package/.github/skills/tech-writer/references/doctypes/operations-runbook.md +63 -0
  54. package/.github/skills/tech-writer/references/doctypes/pr-commit.md +82 -0
  55. package/.github/skills/tech-writer/references/doctypes/qiita.md +75 -0
  56. package/.github/skills/tech-writer/references/doctypes/readme.md +43 -0
  57. package/.github/skills/tech-writer/references/doctypes/release-notes.md +30 -0
  58. package/.github/skills/tech-writer/references/doctypes/requirements-definition.md +61 -0
  59. package/.github/skills/tech-writer/references/doctypes/rfi.md +43 -0
  60. package/.github/skills/tech-writer/references/doctypes/rfp.md +46 -0
  61. package/.github/skills/tech-writer/references/doctypes/security-design.md +71 -0
  62. package/.github/skills/tech-writer/references/doctypes/system-design.md +74 -0
  63. package/.github/skills/tech-writer/references/doctypes/technical-proposal.md +49 -0
  64. package/.github/skills/tech-writer/references/doctypes/test-plan.md +67 -0
  65. package/.github/skills/tech-writer/references/doctypes/user-manual.md +58 -0
  66. package/.github/skills/tech-writer/references/doctypes/white-paper.md +84 -0
  67. package/.github/skills/tech-writer/references/doctypes/zenn.md +66 -0
  68. package/.github/skills/tech-writer/references/intake.md +58 -0
  69. package/.github/skills/tech-writer/references/japanese-prose-optimization.md +110 -0
  70. package/.github/skills/tech-writer/references/review-loop.md +42 -0
  71. package/.github/skills/tech-writer/references/style-constitution.md +104 -0
  72. package/.github/skills/tech-writer/scripts/lint.py +415 -0
  73. package/.github/skills/wslc-containers/SKILL.md +216 -0
  74. package/.github/skills/wslc-containers/scripts/wslc.sh +48 -0
  75. package/LICENSE +21 -0
  76. package/README-ja.md +144 -0
  77. package/README.md +166 -2
  78. package/bin/musubix6.mjs +54 -0
  79. package/package.json +29 -4
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: agent-watchdog
3
+ description: Use when background agents (task with mode background) are running long jobs and may go idle before finishing: detect stalled agents every few minutes and resume them with a continue instruction. バックグラウンドエージェントが未完了のまま停止していないかを定期確認し、停止していれば続行を指示するときに使用。
4
+ ---
5
+
6
+ # agent-watchdog
7
+
8
+ Background agents often end their turn (status `idle`) with work left. Repo code cannot message agents; only the Copilot CLI session can (`write_agent`). So: a read-only detector script plus this procedure.
9
+
10
+ ## Detector
11
+
12
+ ```bash
13
+ node .github/skills/agent-watchdog/scripts/watchdog.mjs <workspace-dir>... [--stale-min 10] [--done-glob 'findings/*.md']
14
+ ```
15
+
16
+ JSON per directory: `idleMinutes` (since the newest file change, ignoring `.git`, `node_modules`, `target`, caches), `done` (a file matching `--done-glob` exists), `stale` (not done and idle > `--stale-min`). Exit 0 = all active/done, 1 = some stale, 2 = bad input or missing dir.
17
+
18
+ ## Procedure
19
+
20
+ 1. Schedule it: `manage_schedule` action `create`, `interval: 5m`, prompt = "run the watchdog on the agents' workspaces, cross-check `list_agents`, resume stalled agents".
21
+ 2. Each tick:
22
+ - `list_agents` (`include_completed: false`, `scope: children`) and note agents in **Idle** that belong to the current batch.
23
+ - Run the detector on their workspaces.
24
+ 3. For each idle agent whose last reply did NOT state that all work is done (`read_agent`, no wait), `write_agent` this message:
25
+ "Continue now and finish ALL remaining work without stopping; list what remains if blocked. Reply (<=8 lines) only when everything is done."
26
+ 4. Leave alone: agents that reported completion, `done: true` workspaces, and agents from earlier batches.
27
+ 5. Cap at 3 continue messages per agent; after that report it to the user instead of looping.
28
+ 6. Stop the schedule (`manage_schedule` action `stop`) when every agent in the batch is done.
29
+
30
+ ## Limits
31
+
32
+ An agent that is running but stuck in a long command still looks active to the agent list; the detector's `idleMinutes` is what exposes it. A workspace edited by something else counts as activity.
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ // Read-only detector for stalled background-agent workspaces.
3
+ // usage: watchdog.mjs <dir>... [--stale-min N] [--done-glob <relative glob>]
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+
7
+ const SKIP = new Set(['.git', 'node_modules', 'target', '__pycache__', '.pytest_cache', '.venv']);
8
+
9
+ const argv = process.argv.slice(2);
10
+ const dirs = [];
11
+ let staleMin = 10;
12
+ let doneGlob = '';
13
+ for (let i = 0; i < argv.length; i++) {
14
+ if (argv[i] === '--stale-min') staleMin = Number(argv[++i]);
15
+ else if (argv[i] === '--done-glob') doneGlob = argv[++i];
16
+ else dirs.push(argv[i]);
17
+ }
18
+ if (!dirs.length || !Number.isFinite(staleMin)) {
19
+ console.error('usage: watchdog.mjs <dir>... [--stale-min N] [--done-glob <relative glob>]');
20
+ process.exit(2);
21
+ }
22
+
23
+ const newestMtime = (root) => {
24
+ let newest = 0;
25
+ const walk = (d) => {
26
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
27
+ if (SKIP.has(e.name)) continue;
28
+ const p = path.join(d, e.name);
29
+ if (e.isDirectory()) walk(p);
30
+ else newest = Math.max(newest, fs.statSync(p).mtimeMs);
31
+ }
32
+ };
33
+ walk(root);
34
+ return newest;
35
+ };
36
+
37
+ const globToRegExp = (glob) =>
38
+ new RegExp('^' + glob.split('*').map((s) => s.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('[^/]*') + '$');
39
+
40
+ const hasDoneMarker = (root, glob) => {
41
+ const re = globToRegExp(glob);
42
+ const walk = (d, rel) => {
43
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
44
+ if (SKIP.has(e.name)) continue;
45
+ const r = rel ? `${rel}/${e.name}` : e.name;
46
+ if (e.isDirectory() ? walk(path.join(d, e.name), r) : re.test(r)) return true;
47
+ }
48
+ return false;
49
+ };
50
+ return walk(root, '');
51
+ };
52
+
53
+ let code = 0;
54
+ const out = dirs.map((dir) => {
55
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
56
+ code = 2;
57
+ return { dir, error: 'directory not found' };
58
+ }
59
+ const newest = newestMtime(dir);
60
+ const idleMinutes = newest ? Math.round(((Date.now() - newest) / 60000) * 10) / 10 : null;
61
+ const done = doneGlob ? hasDoneMarker(dir, doneGlob) : false;
62
+ const stale = !done && (idleMinutes === null || idleMinutes > staleMin);
63
+ if (stale && code === 0) code = 1;
64
+ return { dir, idleMinutes, done, stale };
65
+ });
66
+ console.log(JSON.stringify(out, null, 2));
67
+ process.exit(code);
@@ -0,0 +1,92 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { spawnSync } from 'node:child_process';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const SCRIPT = path.join(path.dirname(fileURLToPath(import.meta.url)), 'watchdog.mjs');
10
+ const mk = () => fs.mkdtempSync(path.join(os.tmpdir(), 'wd-'));
11
+ const age = (file, minutes) => {
12
+ const t = new Date(Date.now() - minutes * 60000);
13
+ fs.utimesSync(file, t, t);
14
+ };
15
+ const run = (...args) => {
16
+ const r = spawnSync('node', [SCRIPT, ...args], { encoding: 'utf8' });
17
+ return { code: r.status, json: r.stdout ? JSON.parse(r.stdout) : null, err: r.stderr };
18
+ };
19
+
20
+ test('TEST-WD-001: recently modified workspace is active', () => {
21
+ const d = mk();
22
+ fs.writeFileSync(path.join(d, 'a.txt'), 'x');
23
+ const r = run(d, '--stale-min', '10');
24
+ assert.equal(r.code, 0);
25
+ assert.equal(r.json[0].stale, false);
26
+ assert.ok(r.json[0].idleMinutes < 1);
27
+ });
28
+
29
+ test('TEST-WD-002: workspace idle longer than the threshold is stale and exits 1', () => {
30
+ const d = mk();
31
+ const f = path.join(d, 'a.txt');
32
+ fs.writeFileSync(f, 'x');
33
+ age(f, 30);
34
+ age(d, 30);
35
+ const r = run(d, '--stale-min', '10');
36
+ assert.equal(r.code, 1);
37
+ assert.equal(r.json[0].stale, true);
38
+ assert.ok(r.json[0].idleMinutes >= 29);
39
+ });
40
+
41
+ test('TEST-WD-003: node_modules, target and .git file changes are ignored', () => {
42
+ const d = mk();
43
+ const f = path.join(d, 'a.txt');
44
+ fs.writeFileSync(f, 'x');
45
+ age(f, 30);
46
+ for (const sub of ['node_modules', 'target', '.git']) {
47
+ fs.mkdirSync(path.join(d, sub));
48
+ fs.writeFileSync(path.join(d, sub, 'fresh.txt'), 'y');
49
+ }
50
+ age(d, 30);
51
+ const r = run(d, '--stale-min', '10');
52
+ assert.equal(r.json[0].stale, true);
53
+ });
54
+
55
+ test('TEST-WD-004: a done marker (findings file) makes a workspace done, never stale', () => {
56
+ const d = mk();
57
+ fs.mkdirSync(path.join(d, 'findings'));
58
+ const f = path.join(d, 'findings', 'app1.md');
59
+ fs.writeFileSync(f, 'done');
60
+ age(f, 60);
61
+ age(path.join(d, 'findings'), 60);
62
+ age(d, 60);
63
+ const r = run(d, '--stale-min', '10', '--done-glob', 'findings/*.md');
64
+ assert.equal(r.code, 0);
65
+ assert.equal(r.json[0].done, true);
66
+ assert.equal(r.json[0].stale, false);
67
+ });
68
+
69
+ test('TEST-WD-005: nested files count toward activity', () => {
70
+ const d = mk();
71
+ fs.mkdirSync(path.join(d, 'src'));
72
+ const old = path.join(d, 'src', 'old.txt');
73
+ fs.writeFileSync(old, 'x');
74
+ age(old, 60);
75
+ fs.writeFileSync(path.join(d, 'src', 'new.txt'), 'y');
76
+ age(path.join(d, 'src'), 60);
77
+ age(d, 60);
78
+ const r = run(d, '--stale-min', '10');
79
+ assert.equal(r.json[0].stale, false);
80
+ });
81
+
82
+ test('TEST-WD-006: a missing directory is reported as an error entry and exits 2', () => {
83
+ const r = run(path.join(os.tmpdir(), 'wd-does-not-exist-xyz'));
84
+ assert.equal(r.code, 2);
85
+ assert.match(r.json[0].error, /not found/);
86
+ });
87
+
88
+ test('TEST-WD-007: no arguments prints usage and exits 2', () => {
89
+ const r = spawnSync('node', [SCRIPT], { encoding: 'utf8' });
90
+ assert.equal(r.status, 2);
91
+ assert.match(r.stderr, /usage/i);
92
+ });
@@ -0,0 +1,17 @@
1
+ # GiNZA dependency notice
2
+
3
+ This skill is an original kotonoha implementation and does not include source
4
+ code from `coji/natural-japanese`.
5
+
6
+ Morphological and syntactic analysis uses
7
+ [GiNZA](https://github.com/megagonlabs/ginza), its `ja_ginza` model, and
8
+ spaCy. These dependencies are resolved by `uv` when a diagnostic script is
9
+ run. GiNZA and its Japanese Universal Dependencies models are distributed
10
+ under the MIT License. spaCy is distributed under the MIT License.
11
+ SudachiPy and SudachiDict are distributed under the Apache License 2.0.
12
+ GiNZA documents the licenses of its remaining dependencies and training
13
+ datasets.
14
+
15
+ The implementation targets GiNZA 5.2.x and uses token surface forms, lemmas,
16
+ universal part-of-speech tags, dependency relationships, sentence boundaries,
17
+ and named-entity labels.
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: japanese-prose
3
+ description: >-
4
+ Improves Japanese prose in technical and business documents using
5
+ kotonoha's original GiNZA-based diagnostics. Use when writing, rewriting,
6
+ reviewing, or scoring Japanese prose; when the user asks for more natural,
7
+ readable, concise, or less AI-like Japanese; or when tech-writer delegates
8
+ its sentence-level quality pass. Owns wording, sentence rhythm, reading
9
+ load, terminology review, and formulaic-expression detection. Does not own
10
+ technical-document structure or presentation storylines.
11
+ license: MIT
12
+ argument-hint: "[write|review|score] [quick|full] <target file or request>"
13
+ ---
14
+
15
+ # japanese-prose
16
+
17
+ Improves Japanese wording without changing approved facts, obligations,
18
+ identifiers, evidence, code, or document structure. This is an original
19
+ kotonoha implementation. It uses
20
+ [GiNZA](https://github.com/megagonlabs/ginza) for tokenization, part-of-speech
21
+ tagging, dependency parsing, lemmatization, and named-entity recognition.
22
+
23
+ ## Responsibility boundary
24
+
25
+ - `tech-writer` owns doctype selection, section structure, completeness,
26
+ traceability, and Markdown organization.
27
+ - `presentation-planner` owns audience strategy, scenarios, slide order, and
28
+ design specifications.
29
+ - `japanese-prose` owns sentence-level Japanese: clarity, rhythm, reading
30
+ load, terminology, repeated patterns, and contextual rewriting.
31
+
32
+ Never move, add, or remove sections unless the calling skill explicitly
33
+ authorizes it. Never alter requirement IDs, risk IDs, control IDs, numbers,
34
+ units, dates, proper nouns, citations, URLs, commands, tables, schemas,
35
+ acceptance criteria, approval states, or normative force.
36
+
37
+ ## Modes
38
+
39
+ - `write`: write or rewrite Japanese prose. Use `quick` unless the document
40
+ is external-facing, high-risk, or longer than roughly 10,000 Japanese
41
+ characters.
42
+ - `review`: report problems and proposed corrections without editing.
43
+ - `score`: run diagnostics and report the 0–100 score without rewriting.
44
+ - `quick`: run prose lint once, review findings in context, edit, and rerun
45
+ until no new actionable findings appear.
46
+ - `full`: run prose lint, reading-load lint, outline extraction, and
47
+ terminology extraction; then review the whole document against
48
+ `references/review-workflow.md`.
49
+
50
+ ## Workflow
51
+
52
+ 1. Read the target, audience, intended outcome, and calling skill's frozen
53
+ invariants.
54
+ 2. Read `references/writing-guidelines.md` before generating or rewriting
55
+ prose.
56
+ 3. Run the GiNZA prose diagnostic:
57
+
58
+ ```bash
59
+ uv run scripts/lint.py <target-file> --genre tech --json > <workdir>/prose-baseline.json
60
+ ```
61
+
62
+ 4. For `full` mode, also run:
63
+
64
+ ```bash
65
+ uv run scripts/lint.py <target-file> --genre tech --reading-load --json
66
+ uv run scripts/outline.py <target-file>
67
+ uv run scripts/terms.py <target-file> --json
68
+ ```
69
+
70
+ 5. Classify each finding as `fix` or `keep`. A detector identifies a review
71
+ target; it does not authorize blind replacement.
72
+ 6. Apply only fixes that improve the intended reader's understanding while
73
+ preserving the frozen invariants.
74
+ 7. Rerun the prose diagnostic with
75
+ `--baseline <workdir>/prose-baseline.json` to classify new, persisting,
76
+ and resolved findings. Replace the baseline only after recording the
77
+ decisions for the current round.
78
+ 8. Stop when every finding has a decision, no new actionable finding appears,
79
+ and the document passes the checks in `references/review-workflow.md`.
80
+
81
+ Allow at most three edit-and-diagnose rounds per invocation. If actionable
82
+ findings or invariant violations remain, report that the optimization did not
83
+ converge and list the unresolved items.
84
+
85
+ ## Markdown emphasis
86
+
87
+ When strong emphasis touches surrounding prose, put half-width spaces outside
88
+ the delimiters: `これは **重要** です`, not `これは**重要**です`. Spaces are
89
+ unnecessary at line boundaries or next to punctuation, and must not be placed
90
+ inside `**`. Use ASCII spaces (`U+0020`), never full-width spaces (`U+3000`),
91
+ tabs, or non-breaking spaces, immediately before and after emphasis embedded
92
+ in prose. Write `これは **「重要」** と説明する`, not
93
+ `これは **「重要」** と説明する`.
94
+
95
+ ## Diagnostic interpretation
96
+
97
+ The score is a triage aid, not a quality certificate. Read
98
+ `references/scoring.md` before presenting it. GiNZA provides linguistic
99
+ observations; the agent remains responsible for deciding whether a change is
100
+ correct in context.
101
+
102
+ ## Completion report
103
+
104
+ Report:
105
+
106
+ - mode and diagnostics executed
107
+ - initial and final scores
108
+ - findings fixed and findings deliberately kept
109
+ - any skipped diagnostic and its reason
110
+ - invariant verification result
111
+ - `completed` or `did not converge`
@@ -0,0 +1,50 @@
1
+ # Japanese prose review workflow
2
+
3
+ ## Review findings in context
4
+
5
+ For every diagnostic finding, record one decision:
6
+
7
+ - `fix`: the expression increases ambiguity, reading load, or mechanical
8
+ repetition for the intended reader
9
+ - `keep`: the expression is required by accuracy, domain convention, quoted
10
+ material, or deliberate rhythm
11
+
12
+ Do not count a finding as resolved merely because the triggering text
13
+ disappeared. Confirm that the replacement preserves meaning.
14
+
15
+ ## Check sentence structure
16
+
17
+ - The subject and predicate can be identified without rereading.
18
+ - Modifiers sit close to the words they modify.
19
+ - A long sentence contains one main relationship.
20
+ - Negation does not require the reader to reverse the meaning twice.
21
+ - Noun chains expose ownership, purpose, and target relationships.
22
+
23
+ ## Check document rhythm
24
+
25
+ - Consecutive sentences do not begin with the same two lemmas without reason.
26
+ - Paragraphs do not all begin with the same connective.
27
+ - Sentence lengths vary with information weight.
28
+ - Nominal endings are not used as the default ending for every sentence.
29
+
30
+ ## Check terminology
31
+
32
+ - Product names, identifiers, values, and citations are unchanged.
33
+ - An unfamiliar term is explained near its first use.
34
+ - One concept uses one preferred term unless a distinction is intentional.
35
+ - An acronym is expanded when the audience cannot be expected to know it.
36
+
37
+ ## Check the whole document
38
+
39
+ Read only headings and paragraph openings. The argument must still be
40
+ predictable. Then read the finished prose continuously and verify that local
41
+ rewrites did not damage transitions or create repeated explanations.
42
+
43
+ ## Convergence
44
+
45
+ The review is complete when:
46
+
47
+ - every finding has a `fix` or `keep` decision
48
+ - rerunning diagnostics produces no new actionable finding
49
+ - frozen invariants match the pre-edit document
50
+ - the reader can reach the requested outcome without an unstated inference
@@ -0,0 +1,24 @@
1
+ # Diagnostic scoring
2
+
3
+ The prose lint starts at 100 and subtracts:
4
+
5
+ - 10 points for each `critical` finding
6
+ - 4 points for each `warning` finding
7
+ - 1 point for each `info` finding
8
+
9
+ The minimum is 0. Scores compare repeated runs of the same document and
10
+ configuration; they do not compare authors, genres, or unrelated documents.
11
+
12
+ Use these bands only as triage:
13
+
14
+ | Score | Interpretation |
15
+ |---|---|
16
+ | 90–100 | Few mechanically detectable review targets |
17
+ | 75–89 | Several passages need contextual review |
18
+ | 50–74 | Reading load or repeated patterns are widespread |
19
+ | 0–49 | Review the document section by section before publication |
20
+
21
+ A high score does not prove that facts are correct, the argument is complete,
22
+ or the prose is natural. A low score does not require every finding to be
23
+ changed. Always report the finding categories and contextual decisions beside
24
+ the score.
@@ -0,0 +1,60 @@
1
+ # Japanese prose writing guidelines
2
+
3
+ Use these rules while drafting or rewriting. They are constraints for
4
+ judgment, not search-and-replace recipes.
5
+
6
+ ## Lead with the reader's required conclusion
7
+
8
+ State the decision, action, result, or answer before the background that
9
+ supports it. An introductory sentence must earn its place by changing what
10
+ the reader understands or does.
11
+
12
+ ## Keep one main relationship per sentence
13
+
14
+ A sentence may contain qualifications, but its subject, predicate, and main
15
+ object must remain visible. Split a sentence when the reader must retain one
16
+ unfinished relationship while parsing another.
17
+
18
+ ## Prefer explicit relationships over compressed noun chains
19
+
20
+ Long noun sequences hide whether terms express ownership, purpose, target, or
21
+ sequence. Add particles or a predicate when GiNZA identifies five or more
22
+ consecutive nouns.
23
+
24
+ ## Use concrete actors and actions
25
+
26
+ Name who checks, changes, approves, records, or observes something. Avoid
27
+ turning actions into abstract nouns when a direct verb is clearer.
28
+
29
+ ## Treat formulaic expressions as review targets
30
+
31
+ Expressions such as 「と言えるでしょう」 or 「以下のとおりです」 are not
32
+ automatically wrong. Keep them only when their rhetorical function is
33
+ necessary. Otherwise state the evidence, conclusion, or preview directly.
34
+
35
+ ## Vary rhythm according to information weight
36
+
37
+ Do not force every sentence or paragraph to the same length. Use a short
38
+ sentence for a decisive result. Use a longer sentence only when its
39
+ relationships remain clear.
40
+
41
+ ## Explain terminology at first use
42
+
43
+ Introduce an unfamiliar term by function before or beside its name. Preserve
44
+ established product names, protocol names, identifiers, and domain terms.
45
+
46
+ ## Preserve uncertainty accurately
47
+
48
+ Keep distinctions among confirmed facts, estimates, assumptions, and
49
+ recommendations. Do not make prose smoother by increasing certainty.
50
+
51
+ ## Use emphasis sparingly and safely
52
+
53
+ Emphasize only the phrase that changes the reader's decision. In Markdown,
54
+ separate `**strong emphasis**` from adjacent prose with half-width spaces.
55
+
56
+ ## End with the consequence
57
+
58
+ Close a section with what the evidence means for the reader's next decision
59
+ or action. Do not append a generic summary when the consequence is already
60
+ clear.
@@ -0,0 +1,192 @@
1
+ """Shared Markdown and GiNZA utilities for kotonoha's Japanese prose tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import re
7
+ from dataclasses import asdict, dataclass
8
+ from functools import lru_cache
9
+ from pathlib import Path
10
+ from typing import Iterable
11
+
12
+
13
+ FENCE_RE = re.compile(r"^\s{0,3}(`{3,}|~{3,})")
14
+ HEADING_RE = re.compile(r"^\s{0,3}#{1,6}\s+")
15
+ LIST_RE = re.compile(r"^\s*(?:[-+*]|\d+[.)])\s+")
16
+ BLOCKQUOTE_RE = re.compile(r"^\s*>\s?")
17
+ INLINE_CODE_RE = re.compile(r"`[^`\n]*`")
18
+ LINK_RE = re.compile(r"!?\[([^\]]*)\]\([^)]+\)")
19
+ URL_RE = re.compile(r"https?://\S+")
20
+ HTML_TAG_RE = re.compile(r"<[^>]+>")
21
+ SENTENCE_END_RE = re.compile(r"[。!?!?]+(?:[」』)】〉》]*)")
22
+ JAPANESE_RE = re.compile(r"[ぁ-んァ-ヶ一-龠々]")
23
+
24
+
25
+ @dataclass
26
+ class Finding:
27
+ line: int
28
+ category: str
29
+ severity: str
30
+ message: str
31
+ excerpt: str
32
+ evidence: dict | None = None
33
+ status: str | None = None
34
+
35
+ def to_dict(self) -> dict:
36
+ value = asdict(self)
37
+ return {key: item for key, item in value.items() if item is not None}
38
+
39
+
40
+ @dataclass
41
+ class ProseBlock:
42
+ text: str
43
+ start_line: int
44
+
45
+
46
+ def read_text(path: Path) -> str:
47
+ if not path.is_file():
48
+ raise FileNotFoundError(f"file not found: {path}")
49
+ return path.read_text(encoding="utf-8")
50
+
51
+
52
+ def strip_markdown_inline(text: str) -> str:
53
+ text = INLINE_CODE_RE.sub(" コード ", text)
54
+ text = LINK_RE.sub(lambda match: match.group(1), text)
55
+ text = URL_RE.sub(" ", text)
56
+ text = HTML_TAG_RE.sub(" ", text)
57
+ text = text.replace("**", "").replace("__", "")
58
+ text = text.replace("~~", "")
59
+ return re.sub(r"[ \t]+", " ", text).strip()
60
+
61
+
62
+ def iter_content_lines(text: str) -> Iterable[tuple[int, str]]:
63
+ """Yield line-numbered Markdown outside metadata, code, and comments."""
64
+ lines = text.splitlines()
65
+ fence_marker = ""
66
+ in_frontmatter = bool(lines and lines[0].strip() == "---")
67
+ in_comment = False
68
+
69
+ for index, raw in enumerate(lines, start=1):
70
+ stripped = raw.strip()
71
+ if in_frontmatter:
72
+ if index > 1 and stripped == "---":
73
+ in_frontmatter = False
74
+ yield index, ""
75
+ continue
76
+
77
+ fence = FENCE_RE.match(raw)
78
+ if fence:
79
+ marker = fence.group(1)[0]
80
+ if not fence_marker:
81
+ fence_marker = marker
82
+ elif marker == fence_marker:
83
+ fence_marker = ""
84
+ yield index, ""
85
+ continue
86
+ if fence_marker:
87
+ yield index, ""
88
+ continue
89
+
90
+ if "<!--" in raw:
91
+ in_comment = True
92
+ if in_comment:
93
+ if "-->" in raw:
94
+ in_comment = False
95
+ yield index, ""
96
+ continue
97
+ yield index, raw
98
+
99
+
100
+ def iter_prose_blocks(text: str) -> list[ProseBlock]:
101
+ """Return Markdown prose blocks while excluding metadata and code."""
102
+ blocks: list[ProseBlock] = []
103
+ buffer: list[str] = []
104
+ buffer_line = 0
105
+
106
+ def flush() -> None:
107
+ nonlocal buffer, buffer_line
108
+ if buffer:
109
+ cleaned = strip_markdown_inline("\n".join(buffer))
110
+ if cleaned and JAPANESE_RE.search(cleaned):
111
+ blocks.append(ProseBlock(cleaned, buffer_line))
112
+ buffer = []
113
+ buffer_line = 0
114
+
115
+ for index, raw in iter_content_lines(text):
116
+ stripped = raw.strip()
117
+ if not stripped or stripped.startswith("|") or HEADING_RE.match(raw):
118
+ flush()
119
+ continue
120
+
121
+ list_item = LIST_RE.match(raw)
122
+ if list_item:
123
+ flush()
124
+ line = LIST_RE.sub("", raw)
125
+ line = BLOCKQUOTE_RE.sub("", line)
126
+ if not buffer:
127
+ buffer_line = index
128
+ buffer.append(line)
129
+
130
+ flush()
131
+ return blocks
132
+
133
+
134
+ def sentence_line(block: ProseBlock, start_char: int) -> int:
135
+ return block.start_line + block.text[:start_char].count("\n")
136
+
137
+
138
+ def split_sentences_fallback(text: str) -> Iterable[tuple[str, int]]:
139
+ start = 0
140
+ for match in SENTENCE_END_RE.finditer(text):
141
+ end = match.end()
142
+ sentence = text[start:end].strip()
143
+ if sentence:
144
+ yield sentence, start
145
+ start = end
146
+ tail = text[start:].strip()
147
+ if tail:
148
+ yield tail, start
149
+
150
+
151
+ @lru_cache(maxsize=2)
152
+ def load_ginza(enable_ner: bool = False):
153
+ import spacy
154
+
155
+ disabled = [] if enable_ner else ["ner"]
156
+ return spacy.load("ja_ginza", disable=disabled)
157
+
158
+
159
+ def content_tokens(span) -> list:
160
+ return [
161
+ token
162
+ for token in span
163
+ if not token.is_space and not token.is_punct and token.pos_ != "SYM"
164
+ ]
165
+
166
+
167
+ def dependency_depth(token) -> int:
168
+ depth = 0
169
+ current = token
170
+ seen: set[int] = set()
171
+ while current.head.i != current.i and current.i not in seen and depth < 40:
172
+ seen.add(current.i)
173
+ current = current.head
174
+ depth += 1
175
+ return depth
176
+
177
+
178
+ def excerpt(text: str, limit: int = 100) -> str:
179
+ value = re.sub(r"\s+", " ", text).strip()
180
+ return value if len(value) <= limit else f"{value[:limit - 1]}…"
181
+
182
+
183
+ def finding_key(finding: Finding) -> str:
184
+ normalized = re.sub(r"\d+", "#", finding.excerpt.lower())
185
+ raw = f"{finding.category}\0{normalized}".encode()
186
+ return hashlib.sha256(raw).hexdigest()[:20]
187
+
188
+
189
+ def score_findings(findings: list[Finding]) -> int:
190
+ weights = {"info": 1, "warning": 4, "critical": 10}
191
+ deduction = sum(weights.get(item.severity, 4) for item in findings)
192
+ return max(0, 100 - deduction)
@@ -0,0 +1,5 @@
1
+ # 日本語診断の確認
2
+
3
+ この機能は、設定ミスを公開前に見つけます。管理者が確認画面を開き、対象ユーザーの権限と申請内容を照合してください。
4
+
5
+ 権限が一致すれば承認できます。一致しない場合は、申請者へ差し戻します。
@@ -0,0 +1,5 @@
1
+ # 日本語診断の確認
2
+
3
+ 結論として、この機能を利用することが可能です。また、この機能は非常に重要なポイントです。また、設定を確認することが可能です。また、結果を確認することが可能です。
4
+
5
+ 利用者権限設定変更承認処理実行結果確認画面を利用する場合においては、設定が正しくないことがないとは言えないため、管理者が確認しなくてはならないということが可能であると言えるでしょう。