@logicseek/hello-cc 0.1.3 → 0.1.5

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.
@@ -0,0 +1,123 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export function guidanceMarkdown() {
5
+ return `# hello-cc Coordination Rules
6
+
7
+ This project may be edited by multiple Claude Code and Codex CLI sessions.
8
+
9
+ Use \`hcc\` as the source of truth for cross-session coordination. If the user
10
+ asks what other sessions are doing, do not answer from generic model knowledge.
11
+ Run the project-local commands below and summarize the result.
12
+
13
+ Each terminal session must use a stable peer id, for example codex-a,
14
+ codex-b, claude-a, claude-b, or gpu-runner-a.
15
+
16
+ Status checks:
17
+ - \`hcc peers\` shows known Claude/Codex/shell sessions.
18
+ - \`hcc task list\` shows all open tasks. Tasks stay visible until \`done\` or
19
+ \`abandoned\`; they are not read/unread items.
20
+ - \`hcc msg inbox\` shows unread messages for this session.
21
+ - \`hcc lock list\` shows active advisory locks.
22
+ - \`hcc status\` summarizes peers, tasks, locks, inbox, and recent events.
23
+ - \`hcc team status --task N\` summarizes explicit subtasks for a parent task.
24
+
25
+ Read-only work:
26
+ - Reading files, searching, inspecting \`git diff\` / \`git status\`, reviewing
27
+ uncommitted changes, and running non-mutating checks such as \`node --check\`
28
+ do not require locks.
29
+ - For read-only review, do not acquire file locks. If another live peer already
30
+ holds write locks on changed files, report that the review is only a current
31
+ snapshot and not a final commit-ready verdict.
32
+ - Use tasks, messages, events, or handoffs to make read-only review visible;
33
+ do not use advisory locks to reserve files you are only reading.
34
+
35
+ Review and monitoring:
36
+ - Reviewing another peer's work is a read-only activity. You may inspect current
37
+ diffs, task state, messages, handoffs, and non-mutating checks while that peer
38
+ keeps its write locks.
39
+ - If you find a concrete issue in another peer's task, proactively send that
40
+ peer an \`hcc msg\` with the task id, affected file or behavior, why it is a
41
+ problem, and a suggested fix or verification step.
42
+ - Continue monitoring until the peer fixes the issue, records a follow-up task,
43
+ or hands off. Do not silently treat a snapshot review as final approval.
44
+
45
+ Before mutating work:
46
+ - Register with hcc.
47
+ - Read current status with \`hcc status\`, \`hcc state\`, \`hcc task list\`, and \`hcc msg inbox\`.
48
+ - If \`hcc state\` shows a current task for this peer, continue that task until
49
+ handoff, done, or blocked instead of claiming a new task.
50
+ - Claim one task before editing or mutating shared state when this peer has no
51
+ current task.
52
+ - If work needs multiple peers, use \`hcc team plan\` and \`hcc team start\`
53
+ explicitly; do not create or claim hidden extra tasks.
54
+
55
+ Before editing or mutating shared resources:
56
+ - Acquire an advisory lock for the file, directory, module scope, or shared
57
+ resource.
58
+ - Locks are coordination signals, not task ownership and not read barriers.
59
+ A broad lock such as \`--resource bin/hcc.mjs\` protects the whole file, while
60
+ scoped locks such as \`--resource bin/hcc.mjs --scope db-schema\` and
61
+ \`--scope web-ui\` can be held by different peers at the same time.
62
+ - If another live peer holds a conflicting lock, message that peer before
63
+ editing. When the work is in separate regions of the same file, agree on
64
+ narrower scoped locks and wait for all related scopes to hand off or release
65
+ before final tests, formatting, staging, or commit.
66
+ - Acquire locks only before writes or shared-resource mutation, including file
67
+ edits, formatting, staging/committing through \`.git/index\`, intentional DB
68
+ data/schema changes, starting/stopping/restarting shared runtimes, or taking
69
+ over tmux/session/port resources.
70
+ - Commit-readiness checks are read-only until staging begins. Do not lock source
71
+ files for review. Before committing, ensure no live peer holds write locks on
72
+ the files to be committed, then lock \`.git/index\` only while staging and
73
+ committing.
74
+
75
+ During work:
76
+ - Keep changes scoped to the claimed task.
77
+ - Send progress messages when another session needs context.
78
+
79
+ Before stopping:
80
+ - Mark the task done or blocked.
81
+ - Create a handoff with changed files, tests, and remaining risks.
82
+ - Release locks you no longer need.
83
+ `;
84
+ }
85
+
86
+ export function writeGuidance(root) {
87
+ const guidePath = path.join(root, '.hello-cc', 'HCC.md');
88
+ fs.mkdirSync(path.dirname(guidePath), { recursive: true });
89
+ const content = guidanceMarkdown();
90
+ fs.writeFileSync(guidePath, content);
91
+ const clause = `\n<!-- hello-cc:start -->\n\n${content}\n<!-- hello-cc:end -->\n`;
92
+ for (const file of ['CLAUDE.md', 'AGENTS.md']) {
93
+ const target = path.join(root, file);
94
+ if (!fs.existsSync(target)) {
95
+ fs.writeFileSync(target, clause.trimStart());
96
+ continue;
97
+ }
98
+ const existing = fs.readFileSync(target, 'utf8');
99
+ const updated = existing.includes('<!-- hello-cc:start -->')
100
+ ? existing.replace(/<!-- hello-cc:start -->[\s\S]*?<!-- hello-cc:end -->/, clause.trim())
101
+ : `${existing.trimEnd()}\n${clause}`;
102
+ fs.writeFileSync(target, updated);
103
+ }
104
+ return guidePath;
105
+ }
106
+
107
+ export function removeGuidanceBlocks(root) {
108
+ const changed = [];
109
+ for (const file of ['CLAUDE.md', 'AGENTS.md']) {
110
+ const target = path.join(root, file);
111
+ if (!fs.existsSync(target)) continue;
112
+ const existing = fs.readFileSync(target, 'utf8');
113
+ const updated = existing
114
+ .replace(/\n?<!-- hello-cc:start -->[\s\S]*?<!-- hello-cc:end -->\n?/g, '\n')
115
+ .replace(/\n{3,}/g, '\n\n')
116
+ .trimEnd();
117
+ if (updated !== existing) {
118
+ fs.writeFileSync(target, updated ? `${updated}\n` : '');
119
+ changed.push(target);
120
+ }
121
+ }
122
+ return changed;
123
+ }
@@ -0,0 +1,14 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export function readJsonSafe(filePath) {
5
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
6
+ catch { return null; }
7
+ }
8
+
9
+ export function writeJsonSafe(filePath, data) {
10
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
11
+ const tmpPath = filePath + '.tmp.' + process.pid;
12
+ fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n');
13
+ fs.renameSync(tmpPath, filePath);
14
+ }
@@ -0,0 +1,24 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ export function packageRoot(fromUrl = import.meta.url) {
6
+ return path.resolve(fileURLToPath(fromUrl), '..', '..');
7
+ }
8
+
9
+ export function readJson(file) {
10
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
11
+ }
12
+
13
+ export function readPackageJson(root) {
14
+ return readJson(path.join(root, 'package.json'));
15
+ }
16
+
17
+ export function readPackageMeta(root) {
18
+ const pkg = readPackageJson(root);
19
+ return {
20
+ name: pkg.name,
21
+ version: pkg.version,
22
+ packageJson: pkg
23
+ };
24
+ }
@@ -0,0 +1,40 @@
1
+ export function normalizeVersion(version) {
2
+ return String(version || '').replace(/^v/, '');
3
+ }
4
+
5
+ export function releaseSection(markdown, version) {
6
+ const lines = markdown.split(/\r?\n/);
7
+ const heading = `## ${version}`;
8
+ const start = lines.findIndex((line) => line.trim() === heading);
9
+ if (start < 0) return null;
10
+
11
+ let end = lines.length;
12
+ for (let i = start + 1; i < lines.length; i += 1) {
13
+ if (/^##\s+/.test(lines[i])) {
14
+ end = i;
15
+ break;
16
+ }
17
+ }
18
+ return lines.slice(start, end).join('\n').trimEnd();
19
+ }
20
+
21
+ export function validateReleaseSection(section, version) {
22
+ const problems = [];
23
+ if (!section) {
24
+ problems.push(`CHANGELOG.md is missing section ## ${version}`);
25
+ return problems;
26
+ }
27
+ if (!section.includes('### Summary')) problems.push(`## ${version} is missing ### Summary`);
28
+ if (!section.includes('### Highlights')) problems.push(`## ${version} is missing ### Highlights`);
29
+ if (section.includes('TODO') || section.includes('TBD')) problems.push(`## ${version} still contains TODO/TBD text`);
30
+ const bulletCount = (section.match(/^- /gm) || []).length;
31
+ if (bulletCount < 2) problems.push(`## ${version} should include at least two release-note bullets`);
32
+ return problems;
33
+ }
34
+
35
+ export function repoFromPackage(pkg) {
36
+ const repo = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url;
37
+ if (!repo) return null;
38
+ const match = repo.match(/github\.com[:/](.+?\/.+?)(?:\.git)?$/);
39
+ return match ? match[1] : null;
40
+ }
@@ -0,0 +1,45 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+
4
+ export function projectStateDir(root) {
5
+ return path.join(root, '.hello-cc');
6
+ }
7
+
8
+ export function projectDbPath(root) {
9
+ return path.join(projectStateDir(root), 'mesh.db');
10
+ }
11
+
12
+ export function runtimePath(ctx) {
13
+ return path.join(projectStateDir(ctx.root), 'runtime.json');
14
+ }
15
+
16
+ export function webLogPath(ctx) {
17
+ return path.join(projectStateDir(ctx.root), 'web.log');
18
+ }
19
+
20
+ export function globalStateDir() {
21
+ return path.join(os.homedir(), '.hello-cc');
22
+ }
23
+
24
+ export function globalRuntimePath() {
25
+ return path.join(globalStateDir(), 'runtime.json');
26
+ }
27
+
28
+ export function globalWebTokenPath() {
29
+ return path.join(globalStateDir(), 'web-token');
30
+ }
31
+
32
+ export function projectRegistryPath() {
33
+ return path.join(globalStateDir(), 'projects.json');
34
+ }
35
+
36
+ export function contextForProject(root, dbPath = null, base = {}) {
37
+ const resolvedRoot = path.resolve(root);
38
+ return {
39
+ cwd: base.cwd || resolvedRoot,
40
+ root: resolvedRoot,
41
+ dbPath: path.resolve(dbPath || projectDbPath(resolvedRoot)),
42
+ json: Boolean(base.json),
43
+ explicitRoot: true
44
+ };
45
+ }