Existing code to spec

👉 Closed beta notice

The full framework is currently part of Tessl’s closed beta program. You can request access here.

Tessl can be used to create specs from existing code using our document tool. This is useful for a number of reasons:

  • Living documentation: Specs become accessible entrypoints for you when approaching unfamiliar code, or for collaborators who need to be onboarded quickly.

  • Low-friction intro to specs: When starting from code you know, you can quickly gain an instinct for what makes a good spec. Does your generated spec accurately reflect the behavior of the module?

  • Test boost: Specs can be prompted to include tests which will run against the original code.

The document tool is invoked with a --code parameter that accepts a code path, like this:

tessl document --code src/cache.js

This will create a spec at cache.spec.md in your specs/ folder. Here's an example:

Descriptive spec for an in-memory cache
# TTL Cache

A time-to-live cache implementation that automatically expires entries after a specified duration. Provides a Map-like interface with automatic cleanup of expired entries.

## Targets

[@describe](../../src/cache.js)

## Capabilities

### Store key-value pairs with automatic expiration

Stores values in the cache with a configurable time-to-live (TTL) duration. When the TTL expires, entries are automatically removed from the cache.

- Setting a key with default TTL stores the value and sets up automatic expiration
- Setting a key with custom TTL overrides the default expiration time
- Setting the same key multiple times replaces the value and resets the TTL
- Expired entries are automatically removed from the cache

### Retrieve cached values

Provides access to stored values while they are still valid (not expired).

- Getting an existing key returns the stored value
- Getting a non-existent key returns undefined
- Getting an expired key returns undefined

### Check key existence

Determines whether a key exists in the cache.

- Checking an existing key returns true
- Checking a non-existent key returns false
- Checking an expired key returns false

### Manual entry removal

Allows explicit removal of cache entries before their TTL expires.

- Deleting an existing key removes it from the cache and returns true
- Deleting a non-existent key returns false
- Deleting a key cancels its expiration timer

### Cache introspection

Provides methods to inspect the current state of the cache.

- Getting cache size returns the number of stored entries
- Getting cache keys returns an iterator of all stored keys
- Getting cache values returns an iterator of all stored values
- Getting cache entries returns an iterator of all key-value pairs

### Complete cache clearing

Removes all entries from the cache and cancels all pending expiration timers.

- Clearing the cache removes all entries
- Clearing the cache cancels all expiration timers
- Clearing a specific key removes only that entry

## API

```javascript { .api }
/**
 * TTL Cache implementation with automatic expiration
 * @class TTLCache
 */
class TTLCache {
  /**
   * Creates a new TTL cache instance
   * @param {number} [defaultTTL=300000] Default time-to-live in milliseconds (5 minutes)
   */
  constructor(defaultTTL = 300000);

  /**
   * Sets a key-value pair in the cache with optional TTL
   * @param {*} key The cache key
   * @param {*} value The value to cache
   * @param {number} [ttl] Time-to-live in milliseconds, defaults to defaultTTL
   * @returns {TTLCache} Returns this cache instance for chaining
   */
  set(key, value, ttl);

  /**
   * Gets a value from the cache
   * @param {*} key The cache key
   * @returns {*} The cached value or undefined if not found or expired
   */
  get(key);

  /**
   * Checks if a key exists in the cache
   * @param {*} key The cache key
   * @returns {boolean} True if the key exists and hasn't expired
   */
  has(key);

  /**
   * Removes a key from the cache
   * @param {*} key The cache key to remove
   * @returns {boolean} True if the key was found and removed
   */
  delete(key);

  /**
   * Clears the cache or a specific key
   * @param {*} [key] If provided, clears only this key; otherwise clears all
   */
  clear(key);

  /**
   * Gets the number of entries in the cache
   * @returns {number} The current cache size
   */
  size();

  /**
   * Gets an iterator of cache keys
   * @returns {Iterator} Iterator of cache keys
   */
  keys();

  /**
   * Gets an iterator of cache values
   * @returns {Iterator} Iterator of cache values
   */
  values();

  /**
   * Gets an iterator of cache entries
   * @returns {Iterator} Iterator of [key, value] pairs
   */
  entries();
}

module.exports = TTLCache;
```

You'll notice that this spec has a @describe tag at the top, instead of @generate. This means that this spec will never generate code as-is, it is purely descriptive.

You may also want to describe the implementation of the code, not just its behavior. To do this, run the document command over your newly produced spec with the --include-impl-details flag:

tessl document --spec specs/cache.spec.md --include-impl-details

This will add implementation details to the spec, loading context from the @describe path. See an example below:

Descriptive spec with implementation details
# TTL Cache

A time-to-live cache implementation that automatically expires entries after a specified duration. Provides a Map-like interface with automatic cleanup of expired entries.

## Targets

[@describe](../../src/cache.js)

## Capabilities

### Store key-value pairs with automatic expiration

Stores values in the cache with a configurable time-to-live (TTL) duration. When the TTL expires, entries are automatically removed from the cache.

- Setting a key with default TTL stores the value and sets up automatic expiration
- Setting a key with custom TTL overrides the default expiration time
- Setting the same key multiple times replaces the value and resets the TTL
- Expired entries are automatically removed from the cache

### TTL validation and error handling { .impl }

Validates TTL values and handles invalid inputs appropriately.

- Setting a key with null TTL throws TypeError { .impl }
- Setting a key with undefined TTL uses default TTL { .impl }
- Setting a key with negative TTL throws RangeError { .impl }
- Setting a key with zero TTL throws RangeError { .impl }
- Setting a key with non-numeric TTL throws TypeError { .impl }

### Timer management implementation details { .impl }

Handles internal timer lifecycle and cleanup properly.

- Setting a new value for existing key cancels previous timer { .impl }
- Timers are stored internally using setTimeout { .impl }
- All timers are properly cancelled when cache is cleared { .impl }

### Retrieve cached values

Provides access to stored values while they are still valid (not expired).

- Getting an existing key returns the stored value
- Getting a non-existent key returns undefined
- Getting an expired key returns undefined

### Check key existence

Determines whether a key exists in the cache.

- Checking an existing key returns true
- Checking a non-existent key returns false
- Checking an expired key returns false

### Manual entry removal

Allows explicit removal of cache entries before their TTL expires.

- Deleting an existing key removes it from the cache and returns true
- Deleting a non-existent key returns false
- Deleting a key cancels its expiration timer

### Cache introspection

Provides methods to inspect the current state of the cache.

- Getting cache size returns the number of stored entries
- Getting cache keys returns an iterator of all stored keys
- Getting cache values returns an iterator of all stored values
- Getting cache entries returns an iterator of all key-value pairs

### Iterator implementation details { .impl }

Internal implementation of iterator methods follows Map-like patterns.

- Keys iterator returns fresh values excluding expired entries { .impl }
- Values iterator returns fresh values excluding expired entries { .impl }
- Entries iterator returns fresh [key, value] pairs excluding expired entries { .impl }
- Iterators automatically clean up expired entries during iteration { .impl }

### Complete cache clearing

Removes all entries from the cache and cancels all pending expiration timers.

- Clearing the cache removes all entries
- Clearing the cache cancels all expiration timers
- Clearing a specific key removes only that entry

### Clear method overloaded behavior { .impl }

The clear method handles both parameterized and non-parameterized calls.

- Calling clear() with no arguments removes all entries { .impl }
- Calling clear(key) with a key argument removes only that specific entry { .impl }
- Clear method returns undefined regardless of whether entries were found { .impl }

### Internal data structure implementation { .impl }

Uses Map for storage and additional data structures for timer management.

- Internal storage uses native Map for key-value pairs { .impl }
- Timer references stored separately to enable cleanup { .impl }
- Expired entries are lazily cleaned up during access operations { .impl }

## API

```javascript { .api }
/**
 * TTL Cache implementation with automatic expiration
 * @class TTLCache
 */
class TTLCache {
  /**
   * Creates a new TTL cache instance
   * @param {number} [defaultTTL=300000] Default time-to-live in milliseconds (5 minutes)
   */
  constructor(defaultTTL = 300000);

  /**
   * Sets a key-value pair in the cache with optional TTL
   * @param {*} key The cache key
   * @param {*} value The value to cache
   * @param {number} [ttl] Time-to-live in milliseconds, defaults to defaultTTL
   * @returns {TTLCache} Returns this cache instance for chaining
   */
  set(key, value, ttl);

  /**
   * Gets a value from the cache
   * @param {*} key The cache key
   * @returns {*} The cached value or undefined if not found or expired
   */
  get(key);

  /**
   * Checks if a key exists in the cache
   * @param {*} key The cache key
   * @returns {boolean} True if the key exists and hasn't expired
   */
  has(key);

  /**
   * Removes a key from the cache
   * @param {*} key The cache key to remove
   * @returns {boolean} True if the key was found and removed
   */
  delete(key);

  /**
   * Clears the cache or a specific key
   * @param {*} [key] If provided, clears only this key; otherwise clears all
   */
  clear(key);

  /**
   * Gets the number of entries in the cache
   * @returns {number} The current cache size
   */
  size();

  /**
   * Gets an iterator of cache keys
   * @returns {Iterator} Iterator of cache keys
   */
  keys();

  /**
   * Gets an iterator of cache values
   * @returns {Iterator} Iterator of cache values
   */
  values();

  /**
   * Gets an iterator of cache entries
   * @returns {Iterator} Iterator of [key, value] pairs
   */
  entries();
}

module.exports = TTLCache;
```

This looks very similar to our first spec, but this time we see lots of { .impl } blocks. These are specially linked to Tessl's implementation of this spec.

As with most other Tessl tools, document will also accept --context and --prompt parameters, so you can accomplish more complex documentation tasks. For example:

tessl document --code src/cache.js \
               --context src/utils/logger.js \
               --prompt "Include details about how cache events are surfaced in logs."
tessl document --code src/cache.js \
               --context docs/adr/012-cache-strategy.md \
               --prompt "Include an explanation of why we chose LRU over FIFO."

By default specs will not include test links, but you can instruct tessl to include test links by using the prompt. This will make it easy to build tests for code at the same time as building the spec.

For example:

tessl document --spec specs/cache.spec.md --prompt "include test links"

and in the implementation details case:

tessl document --spec specs/cache.spec.md --include-impl-details --prompt "include test links"

This will give you a spec that looks like this:

Now, with your descriptive spec, you have curated context you can use to collaborate with other developers, or even to onboard other agents in new sessions.

Last updated