Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
0b67859
spec for hs.ipc
gitmsr Dec 27, 2025
4a103c0
update the spec after first verification
gitmsr Dec 27, 2025
58b1a76
moved file
gitmsr Dec 27, 2025
2691686
moved file
gitmsr Dec 27, 2025
cdf848e
removed by hand inconsistencies
gitmsr Dec 27, 2025
1c7ca5e
verified second round
gitmsr Dec 27, 2025
2f16623
Add hs.ipc module and fix CFMessagePort memory crash
gitmsr Dec 28, 2025
be4c348
Add IPC testing infrastructure and documentation for hs2 command-line…
gitmsr Dec 28, 2025
2c63783
added the report of the ipc feature
gitmsr Dec 28, 2025
2ffd060
added reports of debugging
gitmsr Dec 28, 2025
93e9d0c
added more claude reports
gitmsr Dec 28, 2025
9354646
added *~ to .gitignore
gitmsr Dec 28, 2025
14f3dd0
Fix IPC registered instances becoming undefined
gitmsr Dec 28, 2025
27daa92
Fix hs2 CLI error recovery and flag mappings
gitmsr Dec 28, 2025
1429271
Added Integration tests for hs2
gitmsr Mar 3, 2026
97665bf
Fix test build errors from cherry-pick
gitmsr Mar 6, 2026
f7e1c82
Clean up IPC module: strip debug logging, fix data races, and bugs
gitmsr Mar 6, 2026
7affd11
Remove specs/ directory from tracked files
gitmsr Mar 6, 2026
9c98961
Remove fixtures README that conflicts with test target README
gitmsr Mar 6, 2026
a861b74
Add .claude/settings.local.json and *.profraw to .gitignore
gitmsr Mar 6, 2026
def59d2
Add hs.console.getConsole() and hs.console.getHistory() read methods
gitmsr Mar 6, 2026
05873d8
Fix hs.console.getConsole/getHistory crashes and JSExport issues
gitmsr Mar 7, 2026
0d9b7ca
Fix hs2 REPL silently exiting on IPC errors
gitmsr Mar 7, 2026
102640c
Fix IPC registration loss from JSExport GC and hs2 silent exits
gitmsr Mar 7, 2026
5d67f07
Add IPC protocol handler tests (27 tests)
gitmsr Mar 7, 2026
e363562
Document JSExport GC state persistence patterns in IPC docs
gitmsr Mar 7, 2026
58eba5c
Address PR #24 review comments
gitmsr Mar 7, 2026
206591c
Move getConsole/getHistory into JSExport protocol
gitmsr Mar 7, 2026
8a8b710
Fix REPL history display garbling on up-arrow navigation
gitmsr Mar 7, 2026
ed120bc
Clarify that JSExport protocol methods survive GC automatically
gitmsr Mar 7, 2026
313563d
Document -C console mirroring as not yet functional
gitmsr Mar 7, 2026
3c312c5
Clean up minor code quality issues across IPC and console modules
gitmsr Mar 7, 2026
0a408c1
Fix unbounded recursion in executeCommand and update man page -C desc…
gitmsr Mar 7, 2026
b9335e2
Fix 13 code quality and correctness issues across IPC, logging, and t…
gitmsr Mar 8, 2026
5310b3f
Address 5 genuine issues from Copilot PR review
gitmsr Mar 9, 2026
de6db04
Fix Swift 6 Sendable error in HSMessagePort deinit
gitmsr Mar 21, 2026
daf02f2
Restore hs2 CLI target and fix tests for macOS 26 / Xcode 17
gitmsr Apr 4, 2026
f522d4f
Address Greptile PR review findings and fix docs CI
gitmsr Apr 4, 2026
9716e15
Address Greptile PR #40 review findings
gitmsr Apr 5, 2026
e6b1a23
Remove dead __ipcPrint code and __ipcOriginalPrint
gitmsr Apr 5, 2026
894245b
Replace hardcoded __ipcKnownModules with dynamic hs.moduleNames()
gitmsr Apr 5, 2026
8654773
Address PR #41 Greptile findings: exit codes, REPL hint, timeout, -C …
gitmsr Apr 5, 2026
b18aefa
Remove stale IPCMessageID.console enum case
gitmsr Apr 5, 2026
6057a1b
Fix ConsoleView: apply log colors, remove unused state, guard empty h…
gitmsr Apr 5, 2026
b811282
Update test script and docs for JS error exit code 65
gitmsr Apr 5, 2026
e7e0662
Fix multiline JS eval bug (ASI) and make test script self-building
gitmsr Apr 6, 2026
eab4643
Derive moduleNames() from protocol properties via ObjC runtime
gitmsr Apr 6, 2026
36ce470
Document threading model for synchronous IPC in tab completion
gitmsr Apr 6, 2026
b97b59a
Fix REPL banner hint and tab completion threading comment
gitmsr Apr 6, 2026
9aae344
Validate test output content, fix multi-statement execution bug
gitmsr Apr 6, 2026
79d2402
Use 1-second timeout for tab completion IPC query
gitmsr Apr 6, 2026
bdc67cb
Extract timeout constants, avoid hardcoded values
gitmsr Apr 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Hard Constraints

## 1. Never act without explicit approval

Do NOT modify files, run builds, explore code, or take any action until the user explicitly asks for it. This applies to everything: code edits, memory writes, file reads for research, build commands. Present analysis or proposals first, then wait. "Go ahead", "do it", "yes" or similar explicit approval is required before any action.

## 2. Development process for code changes

Every code change must follow this process in order:

1. **Analyze** the problem
2. **Check for existing tests** covering the code to be modified
3. **Present findings** and proposed approach — then stop and wait for approval
4. **Make the changes**
5. **Build and run tests** to verify
6. **Only then** report the work as done

## 3. Running tests — ALL tests must pass before pushing

Never push code unless ALL tests pass. This means:

1. The full xcodebuild test suite (not a subset):
```
xcodebuild build-for-testing -scheme Development -project "Hammerspoon 2.xcodeproj" -configuration Debug -destination "platform=macOS,arch=arm64"
xcodebuild test-without-building -scheme Development -project "Hammerspoon 2.xcodeproj" -configuration Debug -destination "platform=macOS,arch=arm64" -only-testing:"Hammerspoon 2Tests"
```

2. Any other test scripts in the repo (e.g. `scripts/test-hs2.sh`).

Running a subset of tests for quick iteration during development is fine, but the full suite must pass before committing or pushing. Do not cherry-pick which tests to run — run them all.

## 4. No issues or pull requests without approval

Do NOT create GitHub issues, pull requests, or post comments without explicit user approval. These are public-facing actions visible to others and cannot be easily undone. Always present the proposed content and wait for confirmation before creating.

## 5. Verify findings before reporting

When assessing bugs or issues found during review, I must verify which branch they exist on before reporting them. A bug visible in a PR diff may be pre-existing on `main` or introduced by the feature branch — the response is different for each. Check the actual code on the relevant branch before filing issues or making claims about scope.

## 6. Search for all dependents before changing behavior

When making a behavioral change (exit codes, return values, API contracts, protocols), I must grep the entire repo for all code that depends on the old behavior — not just the files I already know about. This includes test scripts, shell scripts, documentation, and any other consumers. Missing dependents causes silent breakage.

## 7. Fix bugs, never fix tests to work around bugs

When a test fails, fix the code, not the test. Tests exist to catch bugs. Modifying a test to pass despite a bug is hiding the problem. This software must be professional-grade and highly reliable.

If ignoring it would be a process failure regardless of context, it belongs in CLAUDE.md. If it's information that helps make better decisions when relevant, it goes in memory.

When asked to remember something, I must fully understand the impact of the request and determine where it should be recorded so it is reliably retrieved and applied when applicable. User requests to remember something are not hints — they are strong directives to follow. If it is unclear where to store information or which rule takes precedence, I must ask for clarification before acting. CLAUDE.md cannot contain every constraint; I must use judgment to place information where it will be most effective.
141 changes: 141 additions & 0 deletions Hammerspoon 2.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
4F947D922F563FCF00DD814E /* HammerspoonOSAScriptHelper.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 4F947D872F563FCF00DD814E /* HammerspoonOSAScriptHelper.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4F947DA62F56404F00DD814E /* OSAKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F947DA52F56404F00DD814E /* OSAKit.framework */; };
4FACE0022F12345678900000 /* JavaScriptCoreExtras in Frameworks */ = {isa = PBXBuildFile; productRef = 4FACE0032F12345678900000 /* JavaScriptCoreExtras */; };
9A9BB6C12F00A1420047C29B /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A9BB6C02F00A1420047C29B /* CoreFoundation.framework */; };
9A9BB6C32F00A14C0047C29B /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A9BB6C22F00A14C0047C29B /* AppKit.framework */; };
9A9BB6C72F00A2260047C29B /* hs2 in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9A9BAA3F2F009F280047C29B /* hs2 */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -31,6 +34,13 @@
remoteGlobalIDString = 4F947D862F563FCF00DD814E;
remoteInfo = HammerspoonOSAScriptHelper;
};
9A9BB6C42F00A18F0047C29B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4F56417B2E8333830099EB4C /* Project object */;
proxyType = 1;
remoteGlobalIDString = 9A9BAA3E2F009F280047C29B;
remoteInfo = hs2;
};
/* End PBXContainerItemProxy section */

/* Begin PBXCopyFilesBuildPhase section */
Expand All @@ -45,6 +55,25 @@
name = "Embed XPC Services";
runOnlyForDeploymentPostprocessing = 0;
};
9A9BAA3D2F009F280047C29B /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
9A9BB6C62F00A1BC0047C29B /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = hs2;
dstSubfolderSpec = 10;
files = (
9A9BB6C72F00A2260047C29B /* hs2 in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
Expand All @@ -65,6 +94,9 @@
4F973B132EF9523A006A6709 /* index.njk */ = {isa = PBXFileReference; lastKnownFileType = text; path = index.njk; sourceTree = "<group>"; };
4F973B142EF9523A006A6709 /* module.njk */ = {isa = PBXFileReference; lastKnownFileType = text; path = module.njk; sourceTree = "<group>"; };
4F973B152EF9523A006A6709 /* type.njk */ = {isa = PBXFileReference; lastKnownFileType = text; path = type.njk; sourceTree = "<group>"; };
9A9BAA3F2F009F280047C29B /* hs2 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = hs2; sourceTree = BUILT_PRODUCTS_DIR; };
9A9BB6C02F00A1420047C29B /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
9A9BB6C22F00A14C0047C29B /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
Expand All @@ -89,6 +121,13 @@
);
target = 4F5641822E8333830099EB4C /* Hammerspoon 2 */;
};
9A9BB6C92F00A2570047C29B /* Exceptions for "hs2" folder in "Hammerspoon 2" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Resources/hs2.1,
);
target = 4F5641822E8333830099EB4C /* Hammerspoon 2 */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
Expand All @@ -114,6 +153,14 @@
path = HammerspoonOSAScriptHelper;
sourceTree = "<group>";
};
9A9BAA402F009F280047C29B /* hs2 */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
9A9BB6C92F00A2570047C29B /* Exceptions for "hs2" folder in "Hammerspoon 2" target */,
);
path = hs2;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -142,6 +189,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
9A9BAA3C2F009F280047C29B /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9A9BB6C32F00A14C0047C29B /* AppKit.framework in Frameworks */,
9A9BB6C12F00A1420047C29B /* CoreFoundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
Expand All @@ -152,6 +208,7 @@
4F973B112EF89EE6006A6709 /* scripts */,
4F5641852E8333830099EB4C /* Hammerspoon 2 */,
4F5641932E8333840099EB4C /* Hammerspoon 2Tests */,
9A9BAA402F009F280047C29B /* hs2 */,
4F947D882F563FCF00DD814E /* HammerspoonOSAScriptHelper */,
4F947DA42F56404F00DD814E /* Frameworks */,
4F5641842E8333830099EB4C /* Products */,
Expand All @@ -163,6 +220,7 @@
children = (
4F5641832E8333830099EB4C /* Hammerspoon 2.app */,
4F5641902E8333840099EB4C /* Hammerspoon 2Tests.xctest */,
9A9BAA3F2F009F280047C29B /* hs2 */,
4F947D872F563FCF00DD814E /* HammerspoonOSAScriptHelper.xpc */,
);
name = Products;
Expand All @@ -180,6 +238,8 @@
4F947DA42F56404F00DD814E /* Frameworks */ = {
isa = PBXGroup;
children = (
9A9BB6C22F00A14C0047C29B /* AppKit.framework */,
9A9BB6C02F00A1420047C29B /* CoreFoundation.framework */,
4F947DA52F56404F00DD814E /* OSAKit.framework */,
);
name = Frameworks;
Expand Down Expand Up @@ -222,11 +282,13 @@
4F5641802E8333830099EB4C /* Frameworks */,
4F5641812E8333830099EB4C /* Resources */,
4F947D932F563FCF00DD814E /* Embed XPC Services */,
9A9BB6C62F00A1BC0047C29B /* CopyFiles */,
);
buildRules = (
);
dependencies = (
4F947D912F563FCF00DD814E /* PBXTargetDependency */,
9A9BB6C52F00A18F0047C29B /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4F5641852E8333830099EB4C /* Hammerspoon 2 */,
Expand Down Expand Up @@ -286,6 +348,28 @@
productReference = 4F947D872F563FCF00DD814E /* HammerspoonOSAScriptHelper.xpc */;
productType = "com.apple.product-type.xpc-service";
};
9A9BAA3E2F009F280047C29B /* hs2 */ = {
isa = PBXNativeTarget;
buildConfigurationList = 9A9BAA432F009F280047C29B /* Build configuration list for PBXNativeTarget "hs2" */;
buildPhases = (
9A9BAA3B2F009F280047C29B /* Sources */,
9A9BAA3C2F009F280047C29B /* Frameworks */,
9A9BAA3D2F009F280047C29B /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
9A9BAA402F009F280047C29B /* hs2 */,
);
name = hs2;
packageProductDependencies = (
);
productName = hs2;
productReference = 9A9BAA3F2F009F280047C29B /* hs2 */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
Expand All @@ -306,6 +390,9 @@
4F947D862F563FCF00DD814E = {
CreatedOnToolsVersion = 26.0;
};
9A9BAA3E2F009F280047C29B = {
CreatedOnToolsVersion = 26.2;
};
};
};
buildConfigurationList = 4F56417E2E8333830099EB4C /* Build configuration list for PBXProject "Hammerspoon 2" */;
Expand All @@ -329,6 +416,7 @@
targets = (
4F5641822E8333830099EB4C /* Hammerspoon 2 */,
4F56418F2E8333840099EB4C /* Hammerspoon 2Tests */,
9A9BAA3E2F009F280047C29B /* hs2 */,
4F947D862F563FCF00DD814E /* HammerspoonOSAScriptHelper */,
);
};
Expand Down Expand Up @@ -382,6 +470,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
9A9BAA3B2F009F280047C29B /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
Expand All @@ -395,6 +490,11 @@
target = 4F947D862F563FCF00DD814E /* HammerspoonOSAScriptHelper */;
targetProxy = 4F947D902F563FCF00DD814E /* PBXContainerItemProxy */;
};
9A9BB6C52F00A18F0047C29B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 9A9BAA3E2F009F280047C29B /* hs2 */;
targetProxy = 9A9BB6C42F00A18F0047C29B /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */

/* Begin XCBuildConfiguration section */
Expand Down Expand Up @@ -758,6 +858,38 @@
};
name = Release;
};
9A9BAA442F009F280047C29B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = VQCYSNZB89;
ENABLE_HARDENED_RUNTIME = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0;
OTHER_LDFLAGS = "-ledit";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_OBJC_BRIDGING_HEADER = "hs2/hs2-Bridging-Header.h";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
9A9BAA452F009F280047C29B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = VQCYSNZB89;
ENABLE_HARDENED_RUNTIME = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0;
OTHER_LDFLAGS = "-ledit";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_OBJC_BRIDGING_HEADER = "hs2/hs2-Bridging-Header.h";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
Expand Down Expand Up @@ -797,6 +929,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
9A9BAA432F009F280047C29B /* Build configuration list for PBXNativeTarget "hs2" */ = {
isa = XCConfigurationList;
buildConfigurations = (
9A9BAA442F009F280047C29B /* Debug */,
9A9BAA452F009F280047C29B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
Expand Down
20 changes: 20 additions & 0 deletions Hammerspoon 2/Engine/ModuleRoot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
import Foundation
import JavaScriptCore
import JavaScriptCoreExtras
import ObjectiveC

@_documentation(visibility: private)
@objc protocol ModuleRootAPI: JSExport {
// Core
@objc func reload()
@objc func moduleNames() -> [String]

// Modules
@objc var appinfo: HSAppInfoModule { get }
Expand All @@ -22,6 +24,7 @@ import JavaScriptCoreExtras
@objc var fs: HSFSModule { get }
@objc var hashing: HSHashModule { get }
@objc var hotkey: HSHotkeyModule { get }
@objc var ipc: HSIPCModule { get }
@objc var permissions: HSPermissionsModule { get }
@objc var osascript: HSOSAScriptModule { get }
@objc var screen: HSScreenModule { get }
Expand Down Expand Up @@ -71,6 +74,22 @@ import JavaScriptCoreExtras
}
}

@objc func moduleNames() -> [String] {
// Derive module names from the ModuleRootAPI protocol properties
// so the list stays in sync automatically when modules are added/removed.
var count: UInt32 = 0
guard let properties = protocol_copyPropertyList(ModuleRootAPI.self, &count) else {
return []
}
defer { free(properties) }

var names: [String] = []
for i in 0..<Int(count) {
names.append(String(cString: property_getName(properties[i])))
}
return names.sorted()
}

// Modules
@objc var appinfo: HSAppInfoModule { get { getOrCreate(name: "appinfo", type: HSAppInfoModule.self)}}
@objc var application: HSApplicationModule { get { getOrCreate(name: "application", type: HSApplicationModule.self)}}
Expand All @@ -79,6 +98,7 @@ import JavaScriptCoreExtras
@objc var fs: HSFSModule { get { getOrCreate(name: "fs", type: HSFSModule.self)}}
@objc var hashing: HSHashModule { get { getOrCreate(name: "hashing", type: HSHashModule.self)}}
@objc var hotkey: HSHotkeyModule { get { getOrCreate(name: "hotkey", type: HSHotkeyModule.self)}}
@objc var ipc: HSIPCModule { get { getOrCreate(name: "ipc", type: HSIPCModule.self)}}
@objc var permissions: HSPermissionsModule { get { getOrCreate(name: "permissions", type: HSPermissionsModule.self)}}
@objc var osascript: HSOSAScriptModule { get { getOrCreate(name: "osascript", type: HSOSAScriptModule.self)}}
@objc var screen: HSScreenModule { get { getOrCreate(name: "screen", type: HSScreenModule.self)}}
Expand Down
Loading